reactive-ruby 0.7.28 → 0.7.29
Sign up to get free protection for your applications and to get access to all the features.
- 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
|