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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +6 -0
  3. data/Gemfile.lock +4 -1
  4. data/README.md +132 -68
  5. data/Rakefile +5 -2
  6. data/example/examples/Gemfile +0 -2
  7. data/example/rails-tutorial/Gemfile +3 -2
  8. data/lib/generators/reactive_ruby/test_app/templates/application.rb +11 -0
  9. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/application.rb +2 -0
  10. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
  11. data/lib/generators/reactive_ruby/test_app/templates/boot.rb +6 -0
  12. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  13. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  14. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  15. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +105 -0
  16. data/lib/rails-helpers/top_level_rails_component.rb +9 -16
  17. data/lib/{reactive-ruby → react}/api.rb +8 -65
  18. data/lib/{reactive-ruby → react}/callbacks.rb +0 -0
  19. data/lib/react/component.rb +266 -0
  20. data/lib/react/component/api.rb +48 -0
  21. data/lib/react/component/class_methods.rb +183 -0
  22. data/lib/{reactive-ruby → react}/element.rb +10 -11
  23. data/lib/{reactive-ruby → react}/event.rb +0 -0
  24. data/lib/{reactive-ruby → react}/ext/hash.rb +0 -0
  25. data/lib/{reactive-ruby → react}/ext/string.rb +0 -0
  26. data/lib/react/native_library.rb +57 -0
  27. data/lib/{reactive-ruby → react}/observable.rb +0 -4
  28. data/lib/{reactive-ruby → react}/rendering_context.rb +0 -6
  29. data/lib/{reactive-ruby → react}/state.rb +3 -6
  30. data/lib/{reactive-ruby → react}/top_level.rb +2 -3
  31. data/lib/react/validator.rb +127 -0
  32. data/lib/reactive-ruby.rb +20 -20
  33. data/lib/reactive-ruby/version.rb +1 -1
  34. data/{opal-spec/reactjs → spec}/index.html.erb +1 -1
  35. data/{opal-spec → spec/react}/callbacks_spec.rb +8 -9
  36. data/{opal-spec → spec/react}/component_spec.rb +170 -120
  37. data/spec/react/dsl_spec.rb +16 -0
  38. data/{opal-spec → spec/react}/element_spec.rb +7 -20
  39. data/{opal-spec → spec/react}/event_spec.rb +3 -1
  40. data/spec/react/native_library_spec.rb +10 -0
  41. data/spec/react/param_declaration_spec.rb +18 -0
  42. data/{opal-spec → spec/react}/react_spec.rb +3 -2
  43. data/spec/react/react_state_spec.rb +22 -0
  44. data/spec/react/top_level_component_spec.rb +68 -0
  45. data/{opal-spec → spec/react}/tutorial/tutorial_spec.rb +11 -13
  46. data/{opal-spec → spec/react}/validator_spec.rb +50 -4
  47. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +22 -4
  48. data/spec/spec_helper.rb +51 -0
  49. data/spec/support/react/spec_helpers.rb +57 -0
  50. data/spec/vendor/es5-shim.min.js +6 -0
  51. metadata +56 -24
  52. data/lib/reactive-ruby/component.rb +0 -502
  53. data/lib/reactive-ruby/validator.rb +0 -99
  54. data/old-readme +0 -220
  55. 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("::","").split("::").inject(Module) { |scope, next_const| scope.const_get(next_const) } rescue nil
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
- module React
1
+ require 'react/native_library'
2
2
 
3
- class NativeLibrary
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