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.
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