hyper-react 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|