Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

Commit 64b457c

Browse files
committed
closes #271 #269 #268 #267
1 parent b9653fa commit 64b457c

14 files changed

+228
-140
lines changed

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
source 'https://rubygems.org'
22
gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master"
3+
gem 'hyperloop-config', git: 'https://github.com/ruby-hyperloop/hyperloop-config.git', branch: 'edge'
34
gemspec

lib/hyper-react.rb

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,25 @@ module Hyperloop
1111
class Component
1212
end
1313
end
14-
require 'react/top_level'
15-
require 'react/top_level_render'
14+
require 'native'
1615
require 'react/observable'
1716
require 'react/validator'
17+
require 'react/element'
18+
require 'react/api'
1819
require 'react/component'
1920
require 'react/component/dsl_instance_methods'
2021
require 'react/component/should_component_update'
2122
require 'react/component/tags'
2223
require 'react/component/base'
23-
require 'react/element'
2424
require 'react/event'
25-
require 'react/api'
2625
require 'react/rendering_context'
2726
require 'react/state'
2827
require 'react/object'
2928
require 'react/to_key'
3029
require 'react/ext/opal-jquery/element'
3130
require 'reactive-ruby/isomorphic_helpers'
31+
require 'react/top_level'
32+
require 'react/top_level_render'
3233
require 'rails-helpers/top_level_rails_component'
3334
require 'reactive-ruby/version'
3435
module Hyperloop

lib/react/api.rb

+87-75
Original file line numberDiff line numberDiff line change
@@ -44,94 +44,106 @@ def self.create_native_react_class(type)
4444
raise "Provided class should define `render` method" if !(type.method_defined? :render)
4545
render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
4646
# this was hashing type.to_s, not sure why but .to_s does not work as it Foo::Bar::View.to_s just returns "View"
47-
@@component_classes[type] ||= %x{
48-
class extends React.Component {
49-
constructor(props) {
50-
super(props);
51-
this.mixins = #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`};
52-
this.statics = #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`};
53-
this.state = {};
54-
this.__opalInstanceInitializedState = false;
55-
this.__opalInstanceSyncSetState = true;
56-
this.__opalInstance = #{type.new(`this`)};
57-
this.__opalInstanceInitializedState = true;
58-
this.__opalInstanceSyncSetState = false;
59-
this.__name = #{type.name};
60-
}
61-
static get displayName() {
62-
if (typeof this.__name != "undefined") {
63-
return this.__name;
64-
} else {
65-
return #{type.name};
66-
}
67-
}
68-
static set displayName(name) {
69-
this.__name = name;
70-
}
71-
static get defaultProps() {
72-
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
73-
}
74-
static get propTypes() {
75-
return #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`};
76-
}
77-
componentWillMount() {
78-
if (#{type.method_defined? :component_will_mount}) {
47+
48+
@@component_classes[type] ||= begin
49+
comp = %x{
50+
class extends React.Component {
51+
constructor(props) {
52+
super(props);
53+
this.mixins = #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`};
54+
this.statics = #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`};
55+
this.state = {};
56+
this.__opalInstanceInitializedState = false;
7957
this.__opalInstanceSyncSetState = true;
80-
this.__opalInstance.$component_will_mount();
58+
this.__opalInstance = #{type.new(`this`)};
59+
this.__opalInstanceInitializedState = true;
8160
this.__opalInstanceSyncSetState = false;
61+
this.__name = #{type.name};
8262
}
83-
}
84-
componentDidMount() {
85-
this.__opalInstance.is_mounted = true
86-
if (#{type.method_defined? :component_did_mount}) {
87-
this.__opalInstanceSyncSetState = false;
88-
this.__opalInstance.$component_did_mount();
63+
static get displayName() {
64+
if (typeof this.__name != "undefined") {
65+
return this.__name;
66+
} else {
67+
return #{type.name};
68+
}
8969
}
90-
}
91-
componentWillReceiveProps(next_props) {
92-
if (#{type.method_defined? :component_will_receive_props}) {
93-
this.__opalInstanceSyncSetState = true;
94-
this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props));
95-
this.__opalInstanceSyncSetState = false;
70+
static set displayName(name) {
71+
this.__name = name;
9672
}
97-
}
98-
shouldComponentUpdate(next_props, next_state) {
99-
if (#{type.method_defined? :should_component_update?}) {
100-
this.__opalInstanceSyncSetState = false;
101-
return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
102-
} else { return true; }
103-
}
104-
componentWillUpdate(next_props, next_state) {
105-
if (#{type.method_defined? :component_will_update}) {
106-
this.__opalInstanceSyncSetState = false;
107-
this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
73+
static get defaultProps() {
74+
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
10875
}
109-
}
110-
componentDidUpdate(prev_props, prev_state) {
111-
if (#{type.method_defined? :component_did_update}) {
112-
this.__opalInstanceSyncSetState = false;
113-
this.__opalInstance.$component_did_update(Opal.Hash.$new(prev_props), Opal.Hash.$new(prev_state));
76+
static get propTypes() {
77+
return #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`};
11478
}
115-
}
116-
componentWillUnmount() {
117-
if (#{type.method_defined? :component_will_unmount}) {
79+
componentWillMount() {
80+
if (#{type.method_defined? :component_will_mount}) {
81+
this.__opalInstanceSyncSetState = true;
82+
this.__opalInstance.$component_will_mount();
83+
this.__opalInstanceSyncSetState = false;
84+
}
85+
}
86+
componentDidMount() {
87+
this.__opalInstance.is_mounted = true
88+
if (#{type.method_defined? :component_did_mount}) {
89+
this.__opalInstanceSyncSetState = false;
90+
this.__opalInstance.$component_did_mount();
91+
}
92+
}
93+
componentWillReceiveProps(next_props) {
94+
if (#{type.method_defined? :component_will_receive_props}) {
95+
this.__opalInstanceSyncSetState = true;
96+
this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props));
97+
this.__opalInstanceSyncSetState = false;
98+
}
99+
}
100+
shouldComponentUpdate(next_props, next_state) {
101+
if (#{type.method_defined? :should_component_update?}) {
102+
this.__opalInstanceSyncSetState = false;
103+
return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
104+
} else { return true; }
105+
}
106+
componentWillUpdate(next_props, next_state) {
107+
if (#{type.method_defined? :component_will_update}) {
108+
this.__opalInstanceSyncSetState = false;
109+
this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
110+
}
111+
}
112+
componentDidUpdate(prev_props, prev_state) {
113+
if (#{type.method_defined? :component_did_update}) {
114+
this.__opalInstanceSyncSetState = false;
115+
this.__opalInstance.$component_did_update(Opal.Hash.$new(prev_props), Opal.Hash.$new(prev_state));
116+
}
117+
}
118+
componentWillUnmount() {
119+
if (#{type.method_defined? :component_will_unmount}) {
120+
this.__opalInstanceSyncSetState = false;
121+
this.__opalInstance.$component_will_unmount();
122+
}
123+
this.__opalInstance.is_mounted = false;
124+
}
125+
126+
render() {
118127
this.__opalInstanceSyncSetState = false;
119-
this.__opalInstance.$component_will_unmount();
128+
return this.__opalInstance.$send(render_fn).$to_n();
120129
}
121-
this.__opalInstance.is_mounted = false;
122130
}
123-
componentDidCatch(error, info) {
124-
if (#{type.method_defined? :component_did_catch}) {
131+
}
132+
# check to see if there is an after_error callback. If there is add a
133+
# componentDidCatch handler. Because legacy behavior is to allow any object
134+
# that responds to render to act as a component we have to make sure that
135+
# we have a callbacks_for method. This all becomes much easier once issue
136+
# #270 is resolved.
137+
if type.respond_to?(:callbacks_for) && type.callbacks_for(:after_error) != []
138+
%x{
139+
comp.prototype.componentDidCatch = function(error, info) {
125140
this.__opalInstanceSyncSetState = false;
126141
this.__opalInstance.$component_did_catch(error, Opal.Hash.$new(info));
127142
}
128143
}
129-
render() {
130-
this.__opalInstanceSyncSetState = false;
131-
return this.__opalInstance.$send(render_fn).$to_n();
132-
}
133-
}
134-
}
144+
end
145+
comp
146+
end
135147
end
136148

137149
def self.create_element(type, properties = {}, &block)
@@ -182,7 +194,7 @@ def self.convert_props(properties)
182194

183195
elsif key == "key"
184196
props["key"] = value.to_key
185-
197+
186198
elsif key == 'ref' && value.is_a?(Proc)
187199
props[key] = %x{
188200
function(dom_node){

lib/react/children.rb

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ def initialize(children)
66
@children = children
77
end
88

9+
def render
10+
each(&:render)
11+
end
12+
13+
def to_proc
14+
-> () { render }
15+
end
16+
917
def each(&block)
1018
return to_enum(__callee__) { length } unless block_given?
1119
return [] unless length > 0

lib/react/component.rb

+6-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
require 'react/component/api'
99
require 'react/component/class_methods'
1010
require 'react/component/props_wrapper'
11-
require 'native'
1211

1312
module Hyperloop
1413
class Component
@@ -70,10 +69,13 @@ def component_will_receive_props(next_props)
7069
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
7170
# for now we are just using it to clear processed_params
7271
React::State.set_state_context_to(self) { self.run_callback(:before_receive_props, next_props) }
72+
@_receiving_props = true
7373
end
7474

7575
def component_will_update(next_props, next_state)
7676
React::State.set_state_context_to(self) { self.run_callback(:before_update, next_props, next_state) }
77+
params._reset_all_others_cache if @_receiving_props
78+
@_receiving_props = false
7779
end
7880

7981
def component_did_update(prev_props, prev_state)
@@ -92,15 +94,7 @@ def component_will_unmount
9294

9395
def component_did_catch(error, info)
9496
React::State.set_state_context_to(self) do
95-
if self.class.callbacks_for(:after_error) == []
96-
if `typeof error.$backtrace === "function"`
97-
`console.error(error.$backtrace().$join("\n"))`
98-
else
99-
`console.error(error, info)`
100-
end
101-
else
102-
self.run_callback(:after_error, error, info)
103-
end
97+
self.run_callback(:after_error, error, info)
10498
end
10599
end
106100

@@ -109,7 +103,7 @@ def component_did_catch(error, info)
109103
def update_react_js_state(object, name, value)
110104
if object
111105
name = "#{object.class}.#{name}" unless object == self
112-
# Date.now() has only millisecond precision, if several notifications of
106+
# Date.now() has only millisecond precision, if several notifications of
113107
# observer happen within a millisecond, updates may get lost.
114108
# to mitigate this the Math.random() appends some random number
115109
# this way notifactions will happen as expected by the rest of hyperloop
@@ -125,7 +119,7 @@ def update_react_js_state(object, name, value)
125119
def set_state_synchronously?
126120
@native.JS[:__opalInstanceSyncSetState]
127121
end
128-
122+
129123
def render
130124
raise 'no render defined'
131125
end unless method_defined?(:render)

lib/react/component/class_methods.rb

+2-11
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,7 @@ def param(*args)
9292
end
9393

9494
def collect_other_params_as(name)
95-
validator.allow_undefined_props = true
96-
validator_in_lexical_scope = validator
97-
props_wrapper.define_method(name) do
98-
@_all_others ||= validator_in_lexical_scope.undefined_props(props)
99-
end
100-
101-
validator_in_lexial_scope = validator
102-
props_wrapper.define_method(name) do
103-
@_all_others ||= validator_in_lexial_scope.undefined_props(props)
104-
end
95+
validator.all_other_params(name) { props }
10596
end
10697

10798
def define_state(*states, &block)
@@ -173,7 +164,7 @@ def add_item_to_tree(current_tree, new_item)
173164
end
174165
end
175166

176-
def to_n
167+
def to_n
177168
React::API.class_eval('@@component_classes')[self]
178169
end
179170
end

lib/react/component/props_wrapper.rb

+12
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ def self.define_param(name, param_type)
4444
end
4545
end
4646

47+
def self.define_all_others(name)
48+
define_method("#{name}") do
49+
@_all_others_cache ||= yield(props)
50+
end
51+
end
52+
53+
4754
def initialize(component)
4855
@component = component
4956
end
@@ -52,6 +59,11 @@ def [](prop)
5259
props[prop]
5360
end
5461

62+
63+
def _reset_all_others_cache
64+
@_all_others_cache = nil
65+
end
66+
5567
private
5668

5769
def fetch_from_cache(name)

lib/react/component/tags.rb

+20-3
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ def present(component, *params, &children)
2020
React::RenderingContext.render(component, *params, &children)
2121
end
2222

23-
# define each predefined tag as an instance method
23+
# define each predefined tag (downcase) as an instance method (deprecated) and as a component (upcase)
2424
HTML_TAGS.each do |tag|
25+
26+
# deprecated - remove
2527
if tag == 'p'
2628
define_method(tag) do |*params, &children|
2729
if children || params.count == 0 || (params.count == 1 && params.first.is_a?(Hash))
@@ -35,8 +37,23 @@ def present(component, *params, &children)
3537
React::RenderingContext.render(tag, *params, &children)
3638
end
3739
end
38-
alias_method tag.upcase, tag
39-
const_set tag.upcase, tag
40+
41+
# new style: allows custom hooks to be added and/or the render method to
42+
# be modified. i.e. see how hyper-mesh deals with defaultValues in input tags
43+
44+
klass = Class.new(Hyperloop::Component) do
45+
# its complicated but the automatic inclusion of the Mixin is setup after all
46+
# the files are loaded, so at this point we have to manually load it.
47+
include Hyperloop::Component::Mixin
48+
collect_other_params_as :opts
49+
# we simply pass along all the params and children with the tag string name
50+
render { React::RenderingContext.render(tag, params.opts, &children) }
51+
52+
# after_error do |error, info|
53+
# raise error
54+
# end
55+
end
56+
const_set(tag.upcase, klass)
4057
end
4158

4259
def self.html_tag_class_for(tag)

0 commit comments

Comments
 (0)