reactive-ruby 0.7.28 → 0.7.29
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 +4 -4
- data/.codeclimate.yml +6 -0
- data/Gemfile.lock +4 -1
- data/README.md +132 -68
- data/Rakefile +5 -2
- data/example/examples/Gemfile +0 -2
- data/example/rails-tutorial/Gemfile +3 -2
- data/lib/generators/reactive_ruby/test_app/templates/application.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -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/test_app_generator.rb +105 -0
- data/lib/rails-helpers/top_level_rails_component.rb +9 -16
- data/lib/{reactive-ruby → react}/api.rb +8 -65
- data/lib/{reactive-ruby → react}/callbacks.rb +0 -0
- data/lib/react/component.rb +266 -0
- data/lib/react/component/api.rb +48 -0
- data/lib/react/component/class_methods.rb +183 -0
- data/lib/{reactive-ruby → react}/element.rb +10 -11
- data/lib/{reactive-ruby → react}/event.rb +0 -0
- data/lib/{reactive-ruby → react}/ext/hash.rb +0 -0
- data/lib/{reactive-ruby → react}/ext/string.rb +0 -0
- data/lib/react/native_library.rb +57 -0
- data/lib/{reactive-ruby → react}/observable.rb +0 -4
- data/lib/{reactive-ruby → react}/rendering_context.rb +0 -6
- data/lib/{reactive-ruby → react}/state.rb +3 -6
- data/lib/{reactive-ruby → react}/top_level.rb +2 -3
- data/lib/react/validator.rb +127 -0
- data/lib/reactive-ruby.rb +20 -20
- data/lib/reactive-ruby/version.rb +1 -1
- data/{opal-spec/reactjs → spec}/index.html.erb +1 -1
- data/{opal-spec → spec/react}/callbacks_spec.rb +8 -9
- data/{opal-spec → spec/react}/component_spec.rb +170 -120
- data/spec/react/dsl_spec.rb +16 -0
- data/{opal-spec → spec/react}/element_spec.rb +7 -20
- data/{opal-spec → spec/react}/event_spec.rb +3 -1
- data/spec/react/native_library_spec.rb +10 -0
- data/spec/react/param_declaration_spec.rb +18 -0
- data/{opal-spec → spec/react}/react_spec.rb +3 -2
- data/spec/react/react_state_spec.rb +22 -0
- data/spec/react/top_level_component_spec.rb +68 -0
- data/{opal-spec → spec/react}/tutorial/tutorial_spec.rb +11 -13
- data/{opal-spec → spec/react}/validator_spec.rb +50 -4
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +22 -4
- data/spec/spec_helper.rb +51 -0
- data/spec/support/react/spec_helpers.rb +57 -0
- data/spec/vendor/es5-shim.min.js +6 -0
- metadata +56 -24
- data/lib/reactive-ruby/component.rb +0 -502
- data/lib/reactive-ruby/validator.rb +0 -99
- data/old-readme +0 -220
- data/opal-spec/spec_helper.rb +0 -29
@@ -1,50 +1,45 @@
|
|
1
1
|
module React
|
2
|
-
|
3
2
|
class TopLevelRailsComponent
|
4
|
-
|
5
3
|
include React::Component
|
6
|
-
|
4
|
+
|
7
5
|
def self.search_path
|
8
6
|
@search_path ||= [Module]
|
9
7
|
end
|
10
|
-
|
8
|
+
|
11
9
|
export_component
|
12
|
-
|
10
|
+
|
13
11
|
required_param :component_name
|
14
12
|
required_param :controller
|
15
13
|
required_param :render_params
|
16
|
-
|
14
|
+
|
17
15
|
backtrace :on
|
18
|
-
|
16
|
+
|
19
17
|
def render
|
20
18
|
paths_searched = []
|
21
19
|
if component_name.start_with? "::"
|
22
|
-
paths_searched << component_name.gsub("
|
23
|
-
component = component_name.gsub("
|
20
|
+
paths_searched << component_name.gsub(/^\:\:/,"")
|
21
|
+
component = component_name.gsub(/^\:\:/,"").split("::").inject(Module) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
24
22
|
return present component, render_params if component and component.method_defined? :render
|
25
23
|
else
|
26
24
|
self.class.search_path.each do |path|
|
27
25
|
# try each path + controller + component_name
|
28
26
|
paths_searched << "#{path.name + '::' unless path == Module}#{controller}::#{component_name}"
|
29
|
-
component = "#{controller}::#{component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const) } rescue nil
|
27
|
+
component = "#{controller}::#{component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
30
28
|
return present component, render_params if component and component.method_defined? :render
|
31
29
|
end
|
32
30
|
self.class.search_path.each do |path|
|
33
31
|
# then try each path + component_name
|
34
32
|
paths_searched << "#{path.name + '::' unless path == Module}#{component_name}"
|
35
|
-
component = "#{component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const) } rescue nil
|
33
|
+
component = "#{component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
36
34
|
return present component, render_params if component and component.method_defined? :render
|
37
35
|
end
|
38
36
|
end
|
39
37
|
raise "Could not find component class '#{component_name}' for controller '#{controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
|
40
38
|
end
|
41
|
-
|
42
39
|
end
|
43
|
-
|
44
40
|
end
|
45
41
|
|
46
42
|
class Module
|
47
|
-
|
48
43
|
def add_to_react_search_path(replace_search_path = nil)
|
49
44
|
if replace_search_path
|
50
45
|
React::TopLevelRailsComponent.search_path = [self]
|
@@ -52,10 +47,8 @@ class Module
|
|
52
47
|
React::TopLevelRailsComponent.search_path << self
|
53
48
|
end
|
54
49
|
end
|
55
|
-
|
56
50
|
end
|
57
51
|
|
58
52
|
module Components
|
59
53
|
add_to_react_search_path
|
60
54
|
end
|
61
|
-
|
@@ -1,77 +1,20 @@
|
|
1
|
-
|
1
|
+
require 'react/native_library'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
def self.renames_and_exclusions
|
6
|
-
@renames_and_exclusions ||= {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.libraries
|
10
|
-
@libraries ||= []
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.const_missing(name)
|
14
|
-
if renames_and_exclusions.has_key? name
|
15
|
-
if native_name = renames_and_exclusions[name]
|
16
|
-
native_name
|
17
|
-
else
|
18
|
-
super
|
19
|
-
end
|
20
|
-
else
|
21
|
-
libraries.each do |library|
|
22
|
-
native_name = "#{library}.#{name}"
|
23
|
-
native_component = `eval(#{native_name})` rescue nil
|
24
|
-
React::API.import_native_component(name, native_component) and return name if native_component and `native_component != undefined`
|
25
|
-
end
|
26
|
-
name
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.method_missing(n, *args, &block)
|
31
|
-
name = n
|
32
|
-
if name =~ /_as_node$/
|
33
|
-
node_only = true
|
34
|
-
name = name.gsub(/_as_node$/, "")
|
35
|
-
end
|
36
|
-
unless name = const_get(name)
|
37
|
-
return super
|
38
|
-
end
|
39
|
-
if node_only
|
40
|
-
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
|
41
|
-
else
|
42
|
-
React::RenderingContext.render(name, *args, &block)
|
43
|
-
end
|
44
|
-
rescue
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.imports(library)
|
48
|
-
libraries << library
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.rename(rename_list={})
|
52
|
-
renames_and_exclusions.merge!(rename_list.invert)
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.exclude(*exclude_list)
|
56
|
-
renames_and_exclusions.merge(Hash[exclude_list.map {|k| [k, nil]}])
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
3
|
+
module React
|
61
4
|
class API
|
62
|
-
|
63
5
|
@@component_classes = {}
|
64
|
-
|
65
|
-
def self.import_native_component(opal_class, native_class)
|
6
|
+
|
7
|
+
def self.import_native_component(opal_class, native_class)
|
66
8
|
@@component_classes[opal_class.to_s] = native_class
|
67
|
-
end
|
68
|
-
|
9
|
+
end
|
10
|
+
|
69
11
|
def self.create_native_react_class(type)
|
70
12
|
raise "Provided class should define `render` method" if !(type.method_defined? :render)
|
71
13
|
render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
|
72
14
|
# 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"
|
73
15
|
@@component_classes[type] ||= %x{
|
74
16
|
React.createClass({
|
17
|
+
displayName: #{type.name},
|
75
18
|
propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
|
76
19
|
getDefaultProps: function(){
|
77
20
|
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
|
@@ -154,7 +97,7 @@ module React
|
|
154
97
|
def self.clear_component_class_cache
|
155
98
|
@@component_classes = {}
|
156
99
|
end
|
157
|
-
|
100
|
+
|
158
101
|
def self.convert_props(properties)
|
159
102
|
raise "Component parameters must be a hash. Instead you sent #{properties}" unless properties.is_a? Hash
|
160
103
|
props = {}
|
File without changes
|
@@ -0,0 +1,266 @@
|
|
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 'native'
|
11
|
+
|
12
|
+
module React
|
13
|
+
module Component
|
14
|
+
def self.included(base)
|
15
|
+
base.include(API)
|
16
|
+
base.include(React::Callbacks)
|
17
|
+
base.class_eval do
|
18
|
+
class_attribute :initial_state
|
19
|
+
define_callback :before_mount
|
20
|
+
define_callback :after_mount
|
21
|
+
define_callback :before_receive_props
|
22
|
+
define_callback :before_update
|
23
|
+
define_callback :after_update
|
24
|
+
define_callback :before_unmount
|
25
|
+
|
26
|
+
def render
|
27
|
+
raise "no render defined"
|
28
|
+
end unless method_defined? :render
|
29
|
+
|
30
|
+
def children
|
31
|
+
nodes = [`#{@native}.props.children`].flatten
|
32
|
+
class << nodes
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
def to_n
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def each(&block)
|
40
|
+
if block_given?
|
41
|
+
%x{
|
42
|
+
React.Children.forEach(#{self.to_n}, function(context){
|
43
|
+
#{block.call(React::Element.new(`context`))}
|
44
|
+
})
|
45
|
+
}
|
46
|
+
nil
|
47
|
+
else
|
48
|
+
Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
|
49
|
+
%x{
|
50
|
+
React.Children.forEach(#{self.to_n}, function(context){
|
51
|
+
#{y << React::Element.new(`context`)}
|
52
|
+
})
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
nodes
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
base.extend(ClassMethods)
|
64
|
+
|
65
|
+
if base.name
|
66
|
+
parent = base.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
|
67
|
+
|
68
|
+
class << parent
|
69
|
+
|
70
|
+
def method_missing(n, *args, &block)
|
71
|
+
name = n
|
72
|
+
if name =~ /_as_node$/
|
73
|
+
node_only = true
|
74
|
+
name = name.gsub(/_as_node$/, "")
|
75
|
+
end
|
76
|
+
begin
|
77
|
+
name = const_get(name)
|
78
|
+
rescue Exception
|
79
|
+
name = nil
|
80
|
+
end
|
81
|
+
unless name and name.method_defined? :render
|
82
|
+
return super
|
83
|
+
end
|
84
|
+
if node_only
|
85
|
+
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
|
86
|
+
else
|
87
|
+
React::RenderingContext.render(name, *args, &block)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def initialize(native_element)
|
96
|
+
@native = native_element
|
97
|
+
end
|
98
|
+
|
99
|
+
def params
|
100
|
+
Hash.new(`#{@native}.props`)
|
101
|
+
end
|
102
|
+
|
103
|
+
def refs
|
104
|
+
Hash.new(`#{@native}.refs`)
|
105
|
+
end
|
106
|
+
|
107
|
+
def state
|
108
|
+
raise "No native ReactComponent associated" unless @native
|
109
|
+
Hash.new(`#{@native}.state`)
|
110
|
+
end
|
111
|
+
|
112
|
+
def update_react_js_state(object, name, value)
|
113
|
+
if object
|
114
|
+
set_state({"***_state_updated_at-***" => Time.now.to_f, "#{object.class.to_s+'.' unless object == self}#{name}" => value})
|
115
|
+
else
|
116
|
+
set_state({name => value})
|
117
|
+
end rescue nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def emit(event_name, *args)
|
121
|
+
self.params["_on#{event_name.to_s.event_camelize}"].call(*args)
|
122
|
+
end
|
123
|
+
|
124
|
+
def component_will_mount
|
125
|
+
IsomorphicHelpers.load_context(true) if IsomorphicHelpers.on_opal_client?
|
126
|
+
@processed_params = {}
|
127
|
+
set_state! initial_state if initial_state
|
128
|
+
React::State.initialize_states(self, initial_state)
|
129
|
+
React::State.set_state_context_to(self) { self.run_callback(:before_mount) }
|
130
|
+
rescue Exception => e
|
131
|
+
self.class.process_exception(e, self)
|
132
|
+
end
|
133
|
+
|
134
|
+
def component_did_mount
|
135
|
+
React::State.set_state_context_to(self) do
|
136
|
+
self.run_callback(:after_mount)
|
137
|
+
React::State.update_states_to_observe
|
138
|
+
end
|
139
|
+
rescue Exception => e
|
140
|
+
self.class.process_exception(e, self)
|
141
|
+
end
|
142
|
+
|
143
|
+
def component_will_receive_props(next_props)
|
144
|
+
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
|
145
|
+
# for now we are just using it to clear processed_params
|
146
|
+
React::State.set_state_context_to(self) { self.run_callback(:before_receive_props, Hash.new(next_props)) }
|
147
|
+
@processed_params = {}
|
148
|
+
rescue Exception => e
|
149
|
+
self.class.process_exception(e, self)
|
150
|
+
end
|
151
|
+
|
152
|
+
def props_changed?(next_props)
|
153
|
+
return true unless params.keys.sort == next_props.keys.sort
|
154
|
+
params.detect { |k, v| `#{next_props[k]} != #{params[k]}`}
|
155
|
+
end
|
156
|
+
|
157
|
+
def should_component_update?(next_props, next_state)
|
158
|
+
React::State.set_state_context_to(self) do
|
159
|
+
next_props = Hash.new(next_props)
|
160
|
+
if self.respond_to?(:needs_update?)
|
161
|
+
!!self.needs_update?(next_props, Hash.new(next_state))
|
162
|
+
elsif false # switch to true to force updates per standard react
|
163
|
+
true
|
164
|
+
elsif props_changed? next_props
|
165
|
+
true
|
166
|
+
elsif `!next_state != !#{@native}.state`
|
167
|
+
true
|
168
|
+
elsif `!next_state && !#{@native}.state`
|
169
|
+
false
|
170
|
+
elsif `next_state["***_state_updated_at-***"] != #{@native}.state["***_state_updated_at-***"]`
|
171
|
+
true
|
172
|
+
else
|
173
|
+
false
|
174
|
+
end.to_n
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def component_will_update(next_props, next_state)
|
179
|
+
React::State.set_state_context_to(self) { self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state)) }
|
180
|
+
rescue Exception => e
|
181
|
+
self.class.process_exception(e, self)
|
182
|
+
end
|
183
|
+
|
184
|
+
def component_did_update(prev_props, prev_state)
|
185
|
+
React::State.set_state_context_to(self) do
|
186
|
+
self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
|
187
|
+
React::State.update_states_to_observe
|
188
|
+
end
|
189
|
+
rescue Exception => e
|
190
|
+
self.class.process_exception(e, self)
|
191
|
+
end
|
192
|
+
|
193
|
+
def component_will_unmount
|
194
|
+
React::State.set_state_context_to(self) do
|
195
|
+
self.run_callback(:before_unmount)
|
196
|
+
React::State.remove
|
197
|
+
end
|
198
|
+
rescue Exception => e
|
199
|
+
self.class.process_exception(e, self)
|
200
|
+
end
|
201
|
+
|
202
|
+
def p(*args, &block)
|
203
|
+
if block || args.count == 0 || (args.count == 1 && args.first.is_a?(Hash))
|
204
|
+
_p_tag(*args, &block)
|
205
|
+
else
|
206
|
+
Kernel.p(*args)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def component?(name)
|
211
|
+
name_list = name.split("::")
|
212
|
+
scope_list = self.class.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }.reverse
|
213
|
+
scope_list.each do |scope|
|
214
|
+
component = name_list.inject(scope) do |scope, class_name|
|
215
|
+
scope.const_get(class_name)
|
216
|
+
end rescue nil
|
217
|
+
return component if component and component.method_defined? :render
|
218
|
+
end
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
|
222
|
+
def method_missing(n, *args, &block)
|
223
|
+
return params[n] if params.key? n
|
224
|
+
name = n
|
225
|
+
if name =~ /_as_node$/
|
226
|
+
node_only = true
|
227
|
+
name = name.gsub(/_as_node$/, "")
|
228
|
+
end
|
229
|
+
unless (React::HTML_TAGS.include?(name) || name == 'present' || name == '_p_tag' || (name = component?(name, self)))
|
230
|
+
return super
|
231
|
+
end
|
232
|
+
|
233
|
+
if name == "present"
|
234
|
+
name = args.shift
|
235
|
+
end
|
236
|
+
|
237
|
+
if name == "_p_tag"
|
238
|
+
name = "p"
|
239
|
+
end
|
240
|
+
|
241
|
+
if node_only
|
242
|
+
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
|
243
|
+
else
|
244
|
+
React::RenderingContext.render(name, *args, &block)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def watch(value, &on_change)
|
249
|
+
React::Observable.new(value, on_change)
|
250
|
+
end
|
251
|
+
|
252
|
+
def define_state(*args, &block)
|
253
|
+
React::State.initialize_states(self, self.class.define_state(*args, &block))
|
254
|
+
end
|
255
|
+
|
256
|
+
attr_reader :waiting_on_resources
|
257
|
+
|
258
|
+
def _render_wrapper
|
259
|
+
React::State.set_state_context_to(self) do
|
260
|
+
RenderingContext.render(nil) {render || ""}.tap { |element| @waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources }
|
261
|
+
end
|
262
|
+
rescue Exception => e
|
263
|
+
self.class.process_exception(e, self)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module React
|
2
|
+
module Component
|
3
|
+
module API
|
4
|
+
def dom_node
|
5
|
+
if `typeof React.findDOMNode === 'undefined'`
|
6
|
+
`#{self}.native.getDOMNode` # v0.12.0
|
7
|
+
else
|
8
|
+
`React.findDOMNode(#{self}.native)` # v0.13.0
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def mounted?
|
13
|
+
`#{self}.native.isMounted()`
|
14
|
+
end
|
15
|
+
|
16
|
+
def force_update!
|
17
|
+
`#{self}.native.forceUpdate()`
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_props(prop, &block)
|
21
|
+
set_or_replace_state_or_prop(prop, 'setProps', &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_props!(prop, &block)
|
25
|
+
set_or_replace_state_or_prop(prop, 'replaceProps', &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_state(state, &block)
|
29
|
+
set_or_replace_state_or_prop(state, 'setState', &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_state!(state, &block)
|
33
|
+
set_or_replace_state_or_prop(state, 'replaceState', &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def set_or_replace_state_or_prop(state_or_prop, method, &block)
|
39
|
+
raise "No native ReactComponent associated" unless @native
|
40
|
+
%x{
|
41
|
+
#{@native}[#{method}](#{state_or_prop.shallow_to_n}, function(){
|
42
|
+
#{block.call if block}
|
43
|
+
});
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|