hyper-react 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +27 -0
- data/.gitignore +36 -0
- data/.rubocop.yml +1159 -0
- data/.travis.yml +29 -0
- data/Appraisals +20 -0
- data/CHANGELOG.md +93 -0
- data/Gemfile +6 -0
- data/LICENSE +19 -0
- data/README.md +121 -0
- data/Rakefile +33 -0
- data/UPGRADING.md +24 -0
- data/component-name-lookup.md +145 -0
- data/config.ru +25 -0
- data/gemfiles/opal_0.8_react_13.gemfile +13 -0
- data/gemfiles/opal_0.8_react_14.gemfile +13 -0
- data/gemfiles/opal_0.8_react_15.gemfile +13 -0
- data/gemfiles/opal_0.9_react_13.gemfile +13 -0
- data/gemfiles/opal_0.9_react_14.gemfile +13 -0
- data/gemfiles/opal_0.9_react_15.gemfile +13 -0
- data/hyper-react.gemspec +43 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +109 -0
- data/lib/hyper-react.rb +52 -0
- data/lib/rails-helpers/top_level_rails_component.rb +54 -0
- data/lib/react-sources/react-server.js +2 -0
- data/lib/react/api.rb +162 -0
- data/lib/react/callbacks.rb +42 -0
- data/lib/react/children.rb +30 -0
- data/lib/react/component.rb +139 -0
- data/lib/react/component/api.rb +50 -0
- data/lib/react/component/base.rb +9 -0
- data/lib/react/component/class_methods.rb +214 -0
- data/lib/react/component/dsl_instance_methods.rb +27 -0
- data/lib/react/component/params.rb +6 -0
- data/lib/react/component/props_wrapper.rb +83 -0
- data/lib/react/component/should_component_update.rb +98 -0
- data/lib/react/component/tags.rb +144 -0
- data/lib/react/element.rb +168 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/hash.rb +13 -0
- data/lib/react/native_library.rb +92 -0
- data/lib/react/object.rb +15 -0
- data/lib/react/observable.rb +29 -0
- data/lib/react/react-source.rb +9 -0
- data/lib/react/rendering_context.rb +142 -0
- data/lib/react/state.rb +190 -0
- data/lib/react/test.rb +16 -0
- data/lib/react/test/dsl.rb +17 -0
- data/lib/react/test/matchers/render_html_matcher.rb +49 -0
- data/lib/react/test/rspec.rb +15 -0
- data/lib/react/test/session.rb +46 -0
- data/lib/react/top_level.rb +132 -0
- data/lib/react/validator.rb +136 -0
- data/lib/reactive-ruby/component_loader.rb +49 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +197 -0
- data/lib/reactive-ruby/rails.rb +7 -0
- data/lib/reactive-ruby/rails/component_mount.rb +46 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +15 -0
- data/lib/reactive-ruby/rails/railtie.rb +14 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/lib/reactrb/auto-import.rb +32 -0
- data/lib/reactrb/deep-compare.rb +24 -0
- data/lib/reactrb/new-event-name-convention.rb +11 -0
- data/lib/sources/react-latest.js +21169 -0
- data/lib/sources/react-v13.js +21645 -0
- data/lib/sources/react-v14.js +20821 -0
- data/lib/sources/react-v15.js +21170 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- data/spec/controller_helper_spec.rb +34 -0
- data/spec/index.html.erb +10 -0
- data/spec/react/callbacks_spec.rb +106 -0
- data/spec/react/children_spec.rb +76 -0
- data/spec/react/component/base_spec.rb +32 -0
- data/spec/react/component_spec.rb +872 -0
- data/spec/react/dsl_spec.rb +296 -0
- data/spec/react/element_spec.rb +136 -0
- data/spec/react/event_spec.rb +24 -0
- data/spec/react/native_library_spec.rb +344 -0
- data/spec/react/observable_spec.rb +7 -0
- data/spec/react/opal_jquery_extensions_spec.rb +66 -0
- data/spec/react/param_declaration_spec.rb +258 -0
- data/spec/react/react_spec.rb +209 -0
- data/spec/react/state_spec.rb +55 -0
- data/spec/react/test/dsl_spec.rb +43 -0
- data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
- data/spec/react/test/rspec_spec.rb +62 -0
- data/spec/react/test/session_spec.rb +100 -0
- data/spec/react/test/utils_spec.rb +45 -0
- data/spec/react/top_level_component_spec.rb +96 -0
- data/spec/react/tutorial/tutorial_spec.rb +36 -0
- data/spec/react/validator_spec.rb +124 -0
- data/spec/reactive-ruby/component_loader_spec.rb +71 -0
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
- data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +10 -0
- data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
- data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
- data/spec/spec_helper.rb +115 -0
- data/spec/support/react/spec_helpers.rb +64 -0
- data/spec/vendor/es5-shim.min.js +6 -0
- data/spec/vendor/jquery-2.2.4.min.js +4 -0
- metadata +387 -0
data/lib/react/api.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'react/native_library'
|
2
|
+
|
3
|
+
module React
|
4
|
+
# Provides the internal mechanisms to interface between reactrb and native components
|
5
|
+
# the code will attempt to create a js component wrapper on any rb class that has a
|
6
|
+
# render (or possibly _render_wrapper) method. The mapping between rb and js components
|
7
|
+
# is kept in the @@component_classes hash.
|
8
|
+
|
9
|
+
# Also provides the mechanism to build react elements
|
10
|
+
|
11
|
+
# TOOO - the code to deal with components should be moved to a module that will be included
|
12
|
+
# in a class which will then create the JS component for that class. That module will then
|
13
|
+
# be included in React::Component, but can be used by any class wanting to become a react
|
14
|
+
# component (but without other DSL characteristics.)
|
15
|
+
class API
|
16
|
+
@@component_classes = {}
|
17
|
+
|
18
|
+
def self.import_native_component(opal_class, native_class)
|
19
|
+
opal_class.instance_variable_set("@native_import", true)
|
20
|
+
@@component_classes[opal_class] = native_class
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.eval_native_react_component(name)
|
24
|
+
component = `eval(name)`
|
25
|
+
raise "#{name} is not defined" if `#{component} === undefined`
|
26
|
+
is_component_class = `#{component}.prototype !== undefined` &&
|
27
|
+
(`!!#{component}.prototype.isReactComponent` ||
|
28
|
+
`!!#{component}.prototype.render`)
|
29
|
+
is_functional_component = `typeof #{component} === "function"`
|
30
|
+
is_not_using_react_v13 = `!window.React.version.match(/0\.13/)`
|
31
|
+
unless is_component_class || (is_not_using_react_v13 && is_functional_component)
|
32
|
+
raise 'does not appear to be a native react component'
|
33
|
+
end
|
34
|
+
component
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.native_react_component?(name)
|
38
|
+
eval_native_react_component(name)
|
39
|
+
rescue
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.create_native_react_class(type)
|
44
|
+
raise "Provided class should define `render` method" if !(type.method_defined? :render)
|
45
|
+
render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
|
46
|
+
# 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
|
+
React.createClass({
|
49
|
+
displayName: #{type.name},
|
50
|
+
propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
|
51
|
+
getDefaultProps: function(){
|
52
|
+
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
|
53
|
+
},
|
54
|
+
mixins: #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`},
|
55
|
+
statics: #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`},
|
56
|
+
componentWillMount: function() {
|
57
|
+
var instance = this._getOpalInstance.apply(this);
|
58
|
+
return #{`instance`.component_will_mount if type.method_defined? :component_will_mount};
|
59
|
+
},
|
60
|
+
componentDidMount: function() {
|
61
|
+
var instance = this._getOpalInstance.apply(this);
|
62
|
+
return #{`instance`.component_did_mount if type.method_defined? :component_did_mount};
|
63
|
+
},
|
64
|
+
componentWillReceiveProps: function(next_props) {
|
65
|
+
var instance = this._getOpalInstance.apply(this);
|
66
|
+
return #{`instance`.component_will_receive_props(Hash.new(`next_props`)) if type.method_defined? :component_will_receive_props};
|
67
|
+
},
|
68
|
+
shouldComponentUpdate: function(next_props, next_state) {
|
69
|
+
var instance = this._getOpalInstance.apply(this);
|
70
|
+
return #{`instance`.should_component_update?(Hash.new(`next_props`), Hash.new(`next_state`)) if type.method_defined? :should_component_update?};
|
71
|
+
},
|
72
|
+
componentWillUpdate: function(next_props, next_state) {
|
73
|
+
var instance = this._getOpalInstance.apply(this);
|
74
|
+
return #{`instance`.component_will_update(Hash.new(`next_props`), Hash.new(`next_state`)) if type.method_defined? :component_will_update};
|
75
|
+
},
|
76
|
+
componentDidUpdate: function(prev_props, prev_state) {
|
77
|
+
var instance = this._getOpalInstance.apply(this);
|
78
|
+
return #{`instance`.component_did_update(Hash.new(`prev_props`), Hash.new(`prev_state`)) if type.method_defined? :component_did_update};
|
79
|
+
},
|
80
|
+
componentWillUnmount: function() {
|
81
|
+
var instance = this._getOpalInstance.apply(this);
|
82
|
+
return #{`instance`.component_will_unmount if type.method_defined? :component_will_unmount};
|
83
|
+
},
|
84
|
+
_getOpalInstance: function() {
|
85
|
+
if (this.__opalInstance == undefined) {
|
86
|
+
var instance = #{type.new(`this`)};
|
87
|
+
} else {
|
88
|
+
var instance = this.__opalInstance;
|
89
|
+
}
|
90
|
+
this.__opalInstance = instance;
|
91
|
+
return instance;
|
92
|
+
},
|
93
|
+
render: function() {
|
94
|
+
var instance = this._getOpalInstance.apply(this);
|
95
|
+
return #{`instance`.send(render_fn).to_n};
|
96
|
+
}
|
97
|
+
})
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.create_element(type, properties = {}, &block)
|
102
|
+
params = []
|
103
|
+
|
104
|
+
# Component Spec, Normal DOM, String or Native Component
|
105
|
+
if @@component_classes[type]
|
106
|
+
params << @@component_classes[type]
|
107
|
+
elsif type.kind_of?(Class)
|
108
|
+
params << create_native_react_class(type)
|
109
|
+
elsif React::Component::Tags::HTML_TAGS.include?(type)
|
110
|
+
params << type
|
111
|
+
elsif type.is_a? String
|
112
|
+
return React::Element.new(type)
|
113
|
+
else
|
114
|
+
raise "#{type} not implemented"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Convert Passed in properties
|
118
|
+
properties = convert_props(properties)
|
119
|
+
params << properties.shallow_to_n
|
120
|
+
|
121
|
+
# Children Nodes
|
122
|
+
if block_given?
|
123
|
+
[yield].flatten.each do |ele|
|
124
|
+
params << ele.to_n
|
125
|
+
end
|
126
|
+
end
|
127
|
+
React::Element.new(`React.createElement.apply(null, #{params})`, type, properties, block)
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.clear_component_class_cache
|
131
|
+
@@component_classes = {}
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.convert_props(properties)
|
135
|
+
raise "Component parameters must be a hash. Instead you sent #{properties}" unless properties.is_a? Hash
|
136
|
+
props = {}
|
137
|
+
properties.map do |key, value|
|
138
|
+
if key == "class_name" && value.is_a?(Hash)
|
139
|
+
props[lower_camelize(key)] = `React.addons.classSet(#{value.to_n})`
|
140
|
+
elsif key == "class"
|
141
|
+
props["className"] = value
|
142
|
+
elsif ["style", "dangerously_set_inner_HTML"].include? key
|
143
|
+
props[lower_camelize(key)] = value.to_n
|
144
|
+
elsif React::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
|
145
|
+
value.each { |k, v| props["#{key}-#{k.tr('_', '-')}"] = v.to_n }
|
146
|
+
else
|
147
|
+
props[React.html_attr?(lower_camelize(key)) ? lower_camelize(key) : key] = value
|
148
|
+
end
|
149
|
+
end
|
150
|
+
props
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def self.lower_camelize(snake_cased_word)
|
156
|
+
words = snake_cased_word.split('_')
|
157
|
+
result = [words.first]
|
158
|
+
result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] })
|
159
|
+
result.join('')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module React
|
4
|
+
module Callbacks
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def run_callback(name, *args)
|
10
|
+
self.class.callbacks_for(name).each do |callback|
|
11
|
+
if callback.is_a?(Proc)
|
12
|
+
instance_exec(*args, &callback)
|
13
|
+
else
|
14
|
+
send(callback, *args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def define_callback(callback_name)
|
21
|
+
attribute_name = "_#{callback_name}_callbacks"
|
22
|
+
class_attribute(attribute_name)
|
23
|
+
self.send("#{attribute_name}=", [])
|
24
|
+
define_singleton_method(callback_name) do |*args, &block|
|
25
|
+
callbacks = self.send(attribute_name)
|
26
|
+
callbacks.concat(args)
|
27
|
+
callbacks.push(block) if block_given?
|
28
|
+
self.send("#{attribute_name}=", callbacks)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def callbacks_for(callback_name)
|
33
|
+
attribute_name = "_#{callback_name}_callbacks"
|
34
|
+
if superclass.respond_to? :callbacks_for
|
35
|
+
superclass.callbacks_for(callback_name)
|
36
|
+
else
|
37
|
+
[]
|
38
|
+
end + self.send(attribute_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module React
|
2
|
+
class Children
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(children)
|
6
|
+
@children = children
|
7
|
+
end
|
8
|
+
|
9
|
+
def each(&block)
|
10
|
+
return to_enum(__callee__) { length } unless block_given?
|
11
|
+
return [] unless length > 0
|
12
|
+
collection = []
|
13
|
+
%x{
|
14
|
+
React.Children.forEach(#{@children}, function(context){
|
15
|
+
#{
|
16
|
+
element = React::Element.new(`context`)
|
17
|
+
block.call(element)
|
18
|
+
collection << element
|
19
|
+
}
|
20
|
+
})
|
21
|
+
}
|
22
|
+
collection
|
23
|
+
end
|
24
|
+
|
25
|
+
def length
|
26
|
+
@length ||= `React.Children.count(#{@children})`
|
27
|
+
end
|
28
|
+
alias_method :size, :length
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'react/ext/string'
|
2
|
+
require 'react/ext/hash'
|
3
|
+
require 'active_support/core_ext/class/attribute'
|
4
|
+
require 'react/callbacks'
|
5
|
+
require 'react/rendering_context'
|
6
|
+
require 'react/observable'
|
7
|
+
require 'react/state'
|
8
|
+
require 'react/component/api'
|
9
|
+
require 'react/component/class_methods'
|
10
|
+
require 'react/component/props_wrapper'
|
11
|
+
require 'native'
|
12
|
+
|
13
|
+
module React
|
14
|
+
module Component
|
15
|
+
def self.included(base)
|
16
|
+
base.include(API)
|
17
|
+
base.include(Callbacks)
|
18
|
+
base.include(Tags)
|
19
|
+
base.include(DslInstanceMethods)
|
20
|
+
base.include(ShouldComponentUpdate)
|
21
|
+
base.class_eval do
|
22
|
+
class_attribute :initial_state
|
23
|
+
define_callback :before_mount
|
24
|
+
define_callback :after_mount
|
25
|
+
define_callback :before_receive_props
|
26
|
+
define_callback :before_update
|
27
|
+
define_callback :after_update
|
28
|
+
define_callback :before_unmount
|
29
|
+
end
|
30
|
+
base.extend(ClassMethods)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.deprecation_warning(message)
|
34
|
+
@deprecation_messages ||= []
|
35
|
+
message = "Warning: Deprecated feature used in #{name}. #{message}"
|
36
|
+
unless @deprecation_messages.include? message
|
37
|
+
@deprecation_messages << message
|
38
|
+
IsomorphicHelpers.log message, :warning
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(native_element)
|
43
|
+
@native = native_element
|
44
|
+
end
|
45
|
+
|
46
|
+
def emit(event_name, *args)
|
47
|
+
params["_on#{event_name.to_s.event_camelize}"].call(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def component_will_mount
|
51
|
+
IsomorphicHelpers.load_context(true) if IsomorphicHelpers.on_opal_client?
|
52
|
+
set_state! initial_state if initial_state
|
53
|
+
State.initialize_states(self, initial_state)
|
54
|
+
State.set_state_context_to(self) { run_callback(:before_mount) }
|
55
|
+
rescue Exception => e
|
56
|
+
self.class.process_exception(e, self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def component_did_mount
|
60
|
+
State.set_state_context_to(self) do
|
61
|
+
run_callback(:after_mount)
|
62
|
+
State.update_states_to_observe
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
self.class.process_exception(e, self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def component_will_receive_props(next_props)
|
69
|
+
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
|
70
|
+
# for now we are just using it to clear processed_params
|
71
|
+
State.set_state_context_to(self) { self.run_callback(:before_receive_props, Hash.new(next_props)) }
|
72
|
+
rescue Exception => e
|
73
|
+
self.class.process_exception(e, self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def component_will_update(next_props, next_state)
|
77
|
+
State.set_state_context_to(self) { self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state)) }
|
78
|
+
rescue Exception => e
|
79
|
+
self.class.process_exception(e, self)
|
80
|
+
end
|
81
|
+
|
82
|
+
def component_did_update(prev_props, prev_state)
|
83
|
+
State.set_state_context_to(self) do
|
84
|
+
self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
|
85
|
+
State.update_states_to_observe
|
86
|
+
end
|
87
|
+
rescue Exception => e
|
88
|
+
self.class.process_exception(e, self)
|
89
|
+
end
|
90
|
+
|
91
|
+
def component_will_unmount
|
92
|
+
State.set_state_context_to(self) do
|
93
|
+
self.run_callback(:before_unmount)
|
94
|
+
State.remove
|
95
|
+
end
|
96
|
+
rescue Exception => e
|
97
|
+
self.class.process_exception(e, self)
|
98
|
+
end
|
99
|
+
|
100
|
+
attr_reader :waiting_on_resources
|
101
|
+
|
102
|
+
def update_react_js_state(object, name, value)
|
103
|
+
if object
|
104
|
+
name = "#{object.class}.#{name}" unless object == self
|
105
|
+
set_state(
|
106
|
+
'***_state_updated_at-***' => Time.now.to_f,
|
107
|
+
name => value
|
108
|
+
)
|
109
|
+
else
|
110
|
+
set_state name => value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def render
|
115
|
+
raise 'no render defined'
|
116
|
+
end unless method_defined?(:render)
|
117
|
+
|
118
|
+
def _render_wrapper
|
119
|
+
State.set_state_context_to(self, true) do
|
120
|
+
element = React::RenderingContext.render(nil) { render || '' }
|
121
|
+
@waiting_on_resources =
|
122
|
+
element.waiting_on_resources if element.respond_to? :waiting_on_resources
|
123
|
+
element
|
124
|
+
end
|
125
|
+
# rubocop:disable Lint/RescueException # we want to catch all exceptions regardless
|
126
|
+
rescue Exception => e
|
127
|
+
# rubocop:enable Lint/RescueException
|
128
|
+
self.class.process_exception(e, self)
|
129
|
+
end
|
130
|
+
|
131
|
+
def watch(value, &on_change)
|
132
|
+
Observable.new(value, on_change)
|
133
|
+
end
|
134
|
+
|
135
|
+
def define_state(*args, &block)
|
136
|
+
State.initialize_states(self, self.class.define_state(*args, &block))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module React
|
2
|
+
module Component
|
3
|
+
module API
|
4
|
+
def dom_node
|
5
|
+
if !(`typeof ReactDOM === 'undefined' || typeof ReactDOM.findDOMNode === 'undefined'`)
|
6
|
+
`ReactDOM.findDOMNode(#{self}.native)` # v0.14.0
|
7
|
+
elsif !(`typeof React.findDOMNode === 'undefined'`)
|
8
|
+
`React.findDOMNode(#{self}.native)` # v0.13.0
|
9
|
+
else
|
10
|
+
`#{self}.native.getDOMNode` # v0.12.0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def mounted?
|
15
|
+
`#{self}.native.isMounted()`
|
16
|
+
end
|
17
|
+
|
18
|
+
def force_update!
|
19
|
+
`#{self}.native.forceUpdate()`
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_props(prop, &block)
|
23
|
+
set_or_replace_state_or_prop(prop, 'setProps', &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_props!(prop, &block)
|
27
|
+
set_or_replace_state_or_prop(prop, 'replaceProps', &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_state(state, &block)
|
31
|
+
set_or_replace_state_or_prop(state, 'setState', &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_state!(state, &block)
|
35
|
+
set_or_replace_state_or_prop(state, 'replaceState', &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def set_or_replace_state_or_prop(state_or_prop, method, &block)
|
41
|
+
raise "No native ReactComponent associated" unless @native
|
42
|
+
%x{
|
43
|
+
#{@native}[#{method}](#{state_or_prop.shallow_to_n}, function(){
|
44
|
+
#{block.call if block}
|
45
|
+
});
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module React
|
2
|
+
module Component
|
3
|
+
# class level methods (macros) for components
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def reactrb_component?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def backtrace(*args)
|
11
|
+
@dont_catch_exceptions = (args[0] == :none)
|
12
|
+
@backtrace_off = @dont_catch_exceptions || (args[0] == :off)
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_exception(e, component, reraise = @dont_catch_exceptions)
|
16
|
+
unless @dont_catch_exceptions
|
17
|
+
message = ["Exception raised while rendering #{component}: #{e.message}"]
|
18
|
+
if e.backtrace && e.backtrace.length > 1 && !@backtrace_off
|
19
|
+
append_backtrace(message, e.backtrace)
|
20
|
+
end
|
21
|
+
`console.error(#{message.join("\n")})`
|
22
|
+
end
|
23
|
+
raise e if reraise
|
24
|
+
end
|
25
|
+
|
26
|
+
def append_backtrace(message_array, backtrace)
|
27
|
+
message_array << " #{backtrace[0]}"
|
28
|
+
backtrace[1..-1].each { |line| message_array << line }
|
29
|
+
end
|
30
|
+
|
31
|
+
def render(container = nil, params = {}, &block)
|
32
|
+
if container
|
33
|
+
container = container.type if container.is_a? React::Element
|
34
|
+
define_method :render do
|
35
|
+
React::RenderingContext.render(container, params) { instance_eval(&block) if block }
|
36
|
+
end
|
37
|
+
else
|
38
|
+
define_method(:render) { instance_eval(&block) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# method missing will assume the method is a class name, and will treat this a render of
|
43
|
+
# of the component, i.e. Foo::Bar.baz === Foo::Bar().baz
|
44
|
+
|
45
|
+
def method_missing(name, *args, &children)
|
46
|
+
Object.method_missing(name, *args, &children) unless args.empty?
|
47
|
+
React::RenderingContext.render(
|
48
|
+
self, class: React::Element.haml_class_name(name), &children
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def validator
|
53
|
+
@validator ||= Validator.new(props_wrapper)
|
54
|
+
end
|
55
|
+
|
56
|
+
def prop_types
|
57
|
+
if self.validator
|
58
|
+
{
|
59
|
+
_componentValidator: %x{
|
60
|
+
function(props, propName, componentName) {
|
61
|
+
var errors = #{validator.validate(Hash.new(`props`))};
|
62
|
+
var error = new Error(#{"In component `#{name}`\n" + `errors`.join("\n")});
|
63
|
+
return #{`errors`.count > 0 ? `error` : `undefined`};
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
else
|
68
|
+
{}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def default_props
|
73
|
+
validator.default_props
|
74
|
+
end
|
75
|
+
|
76
|
+
def params(&block)
|
77
|
+
validator.build(&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def props_wrapper
|
81
|
+
@props_wrapper ||= Class.new(PropsWrapper)
|
82
|
+
end
|
83
|
+
|
84
|
+
def param(*args)
|
85
|
+
if args[0].is_a? Hash
|
86
|
+
options = args[0]
|
87
|
+
name = options.first[0]
|
88
|
+
default = options.first[1]
|
89
|
+
options.delete(name)
|
90
|
+
options.merge!({default: default})
|
91
|
+
else
|
92
|
+
name = args[0]
|
93
|
+
options = args[1] || {}
|
94
|
+
end
|
95
|
+
if options[:default]
|
96
|
+
validator.optional(name, options)
|
97
|
+
else
|
98
|
+
validator.requires(name, options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def collect_other_params_as(name)
|
103
|
+
validator.allow_undefined_props = true
|
104
|
+
validator_in_lexical_scope = validator
|
105
|
+
props_wrapper.define_method(name) do
|
106
|
+
@_all_others ||= validator_in_lexical_scope.undefined_props(props)
|
107
|
+
end
|
108
|
+
|
109
|
+
validator_in_lexial_scope = validator
|
110
|
+
props_wrapper.define_method(name) do
|
111
|
+
@_all_others ||= validator_in_lexial_scope.undefined_props(props)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def define_state(*states, &block)
|
116
|
+
default_initial_value = (block && block.arity == 0) ? yield : nil
|
117
|
+
states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
|
118
|
+
states.each { |name| states_hash[name] = default_initial_value }
|
119
|
+
(self.initial_state ||= {}).merge! states_hash
|
120
|
+
states_hash.each do |name, initial_value|
|
121
|
+
define_state_methods(self, name, &block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def export_state(*states, &block)
|
126
|
+
default_initial_value = (block && block.arity == 0) ? yield : nil
|
127
|
+
states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
|
128
|
+
states.each { |name| states_hash[name] = default_initial_value }
|
129
|
+
State.initialize_states(self, states_hash)
|
130
|
+
states_hash.each do |name, initial_value|
|
131
|
+
define_state_methods(self, name, self, &block)
|
132
|
+
define_state_methods(singleton_class, name, self, &block)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def define_state_methods(this, name, from = nil, &block)
|
137
|
+
this.define_method("#{name}") do
|
138
|
+
React::Component.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? || from == this
|
139
|
+
State.get_state(from || self, name)
|
140
|
+
end
|
141
|
+
this.define_method("#{name}=") do |new_state|
|
142
|
+
React::Component.deprecation_warning "Direct assignment to state `#{name}`. Use `#{(from && from != this) ? from : 'state'}.#{name}!` instead."
|
143
|
+
yield name, State.get_state(from || self, name), new_state if block && block.arity > 0
|
144
|
+
State.set_state(from || self, name, new_state)
|
145
|
+
end
|
146
|
+
this.define_method("#{name}!") do |*args|
|
147
|
+
React::Component.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? or from == this
|
148
|
+
if args.count > 0
|
149
|
+
yield name, State.get_state(from || self, name), args[0] if block && block.arity > 0
|
150
|
+
current_value = State.get_state(from || self, name)
|
151
|
+
State.set_state(from || self, name, args[0])
|
152
|
+
current_value
|
153
|
+
else
|
154
|
+
current_state = State.get_state(from || self, name)
|
155
|
+
yield name, State.get_state(from || self, name), current_state if block && block.arity > 0
|
156
|
+
State.set_state(from || self, name, current_state)
|
157
|
+
Observable.new(current_state) do |update|
|
158
|
+
yield name, State.get_state(from || self, name), update if block && block.arity > 0
|
159
|
+
State.set_state(from || self, name, update)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def native_mixin(item)
|
166
|
+
native_mixins << item
|
167
|
+
end
|
168
|
+
|
169
|
+
def native_mixins
|
170
|
+
@native_mixins ||= []
|
171
|
+
end
|
172
|
+
|
173
|
+
def static_call_back(name, &block)
|
174
|
+
static_call_backs[name] = block
|
175
|
+
end
|
176
|
+
|
177
|
+
def static_call_backs
|
178
|
+
@static_call_backs ||= {}
|
179
|
+
end
|
180
|
+
|
181
|
+
def export_component(opts = {})
|
182
|
+
export_name = (opts[:as] || name).split('::')
|
183
|
+
first_name = export_name.first
|
184
|
+
Native(`window`)[first_name] = add_item_to_tree(
|
185
|
+
Native(`window`)[first_name],
|
186
|
+
[React::API.create_native_react_class(self)] + export_name[1..-1].reverse
|
187
|
+
).to_n
|
188
|
+
end
|
189
|
+
|
190
|
+
def imports(component_name)
|
191
|
+
React::API.import_native_component(
|
192
|
+
self, React::API.eval_native_react_component(component_name)
|
193
|
+
)
|
194
|
+
define_method(:render) {} # define a dummy render method - will never be called...
|
195
|
+
rescue Exception => e # rubocop:disable Lint/RescueException : we need to catch everything!
|
196
|
+
raise "#{self} cannot import '#{component_name}': #{e.message}."
|
197
|
+
# rubocop:enable Lint/RescueException
|
198
|
+
ensure
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
def add_item_to_tree(current_tree, new_item)
|
203
|
+
if Native(current_tree).class != Native::Object || new_item.length == 1
|
204
|
+
new_item.inject { |a, e| { e => a } }
|
205
|
+
else
|
206
|
+
Native(current_tree)[new_item.last] = add_item_to_tree(
|
207
|
+
Native(current_tree)[new_item.last], new_item[0..-2]
|
208
|
+
)
|
209
|
+
current_tree
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|