reactrb 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1c3ebf7952a5d8a3c923961b2c0d58b6c0ecf85
4
- data.tar.gz: 5aa6a7e1fa5dfee1160e2bb0931f316b3d80aa4d
3
+ metadata.gz: 2958d070204fb5e6864522685980645766fe0c7c
4
+ data.tar.gz: a6a28802f1b4711703ee21fce59648ab020d4b68
5
5
  SHA512:
6
- metadata.gz: 11064c43d167ff250020f53fe3ffa3e84dfc1477b06014d2c11703ecc0265601214991e5d9576a362bf2aa027efd81e523833a946cdc729cd747dccdfec682a8
7
- data.tar.gz: 8b8c60d6b48489c5f0008133ef431e0b282f4218c69059f61b07b2ef680cb4629837058ea30b9ee26bbab4e6cf7418e10cd15c3591da4cf15567e0f3b824ae88
6
+ metadata.gz: c9b615808c1a56ea6ce344c902387dc2d27d21e971fbea47e2191439bf7e46ae5efd0f523a5f09a282c863247005f9071d6337a4e521567b1d06cdd95a515620
7
+ data.tar.gz: 6b17254733aaa175a56bfc8ec4a22601af4579758363a941c79bc514fd6276e911e3d89191dd6f48f6d0de4d543639bfe373350dec0ad4e1adc9339abf8e01cf
@@ -0,0 +1,8 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
4
+ Style/MutableConstant:
5
+ Enabled: false
6
+
7
+ Lint/LiteralInCondition:
8
+ Enabled: false
@@ -0,0 +1,145 @@
1
+ #### Notes on how component names are looked up
2
+
3
+ Given:
4
+
5
+ ```ruby
6
+
7
+ class Blat < React::Component::Base
8
+
9
+ render do
10
+ Bar()
11
+ Foo::Bar()
12
+ end
13
+
14
+ end
15
+
16
+ class Bar < React::Component::Base
17
+ end
18
+
19
+ module Foo
20
+
21
+ class Bar < React::Component::Base
22
+
23
+ render do
24
+ Blat()
25
+ Baz()
26
+ end
27
+ end
28
+
29
+ class Baz < React::Component::Base
30
+ end
31
+
32
+ end
33
+ ```
34
+
35
+ The problem is that method lookup is different than constant lookup. We can prove it by running this code:
36
+
37
+ ```ruby
38
+ def try_it(test, &block)
39
+ puts "trying #{test}"
40
+ result = yield
41
+ puts "success#{': '+result.to_s if result}"
42
+ rescue Exception => e
43
+ puts "failed: #{e}"
44
+ ensure
45
+ puts "---------------------------------"
46
+ end
47
+
48
+ module Boom
49
+
50
+ Bar = 12
51
+
52
+ def self.Bar
53
+ puts " Boom::Bar says hi"
54
+ end
55
+
56
+ class Baz
57
+ def doit
58
+ try_it("Bar()") { Bar() }
59
+ try_it("Boom::Bar()") {Boom::Bar()}
60
+ try_it("Bar") { Bar }
61
+ try_it("Boom::Bar") { Boom::Bar }
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+
68
+ Boom::Baz.new.doit
69
+ ```
70
+
71
+ which prints:
72
+
73
+ ```text
74
+ trying Bar()
75
+ failed: Bar: undefined method `Bar' for #<Boom::Baz:0x774>
76
+ ---------------------------------
77
+ trying Boom::Bar()
78
+ Boom::Bar says hi
79
+ success
80
+ ---------------------------------
81
+ trying Bar
82
+ success: 12
83
+ ---------------------------------
84
+ trying Boom::Bar
85
+ success: 12
86
+ ---------------------------------
87
+ ```
88
+
89
+ [try-it](http://opalrb.org/try/?code:def%20try_it(test%2C%20%26block)%0A%20%20puts%20%22trying%20%23%7Btest%7D%22%0A%20%20result%20%3D%20yield%0A%20%20puts%20%22success%23%7B%27%3A%20%27%2Bresult.to_s%20if%20result%7D%22%0Arescue%20Exception%20%3D%3E%20e%0A%20%20puts%20%22failed%3A%20%23%7Be%7D%22%0Aensure%0A%20%20puts%20%22---------------------------------%22%0Aend%0A%0Amodule%20Boom%0A%20%20%0A%20%20Bar%20%3D%2012%0A%20%20%0A%20%20def%20self.Bar%0A%20%20%20%20puts%20%22%20%20%20Boom%3A%3ABar%20says%20hi%22%0A%20%20end%0A%0A%20%20class%20Baz%0A%20%20%20%20def%20doit%0A%20%20%20%20%20%20try_it(%22Bar()%22)%20%7B%20Bar()%20%7D%0A%20%20%20%20%20%20try_it(%22Boom%3A%3ABar()%22)%20%7BBoom%3A%3ABar()%7D%0A%20%20%20%20%20%20try_it(%22Bar%22)%20%7B%20Bar%20%7D%0A%20%20%20%20%20%20try_it(%22Boom%3A%3ABar%22)%20%7B%20Boom%3A%3ABar%20%7D%0A%20%20%20%20end%0A%20%20end%0Aend%0A%20%20%0A%0A%0ABoom%3A%3ABaz.new.doit)
90
+
91
+
92
+ What we need to do is:
93
+
94
+ 1. when defining a component class `Foo`, also define in the same scope that Foo is being defined a method `self.Foo` that will accept Foo's params and child block, and render it.
95
+
96
+ 2. As long as a name is qualified with at least one scope (i.e. `ModName::Foo()`) everything will work out, but if we say just `Foo()` then the only way I believe out of this is to handle it via method_missing, and let method_missing do a const_get on the method_name (which will return the class) and then render that component.
97
+
98
+ #### details
99
+
100
+ To define `self.Foo` in the same scope level as the class `Foo`, we need code like this:
101
+
102
+ ```ruby
103
+ def register_component_dsl_method(component)
104
+ split_name = component.name && component.name.split('::')
105
+ return unless split_name && split_name.length > 2
106
+ component_name = split_name.last
107
+ parent = split_name.inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
108
+ class << parent
109
+ define_method component_name do |*args, &block|
110
+ React::RenderingContext.render(name, *args, &block)
111
+ end
112
+ define_method "#{component_name}_as_node" do |*args, &block|
113
+ React::Component.deprecation_warning("..._as_node is deprecated. Render component and then use the .node method instead")
114
+ send(component_name, *args, &block).node
115
+ end
116
+ end
117
+ end
118
+
119
+ module React
120
+ module Component
121
+ def self.included(base)
122
+ ...
123
+ register_component_dsl_method(base.name)
124
+ end
125
+ end
126
+ end
127
+ ```
128
+
129
+ The component's method_missing function will look like this:
130
+
131
+ ```ruby
132
+ def method_missing(name, *args, &block)
133
+ if name =~ /_as_node$/
134
+ React::Component.deprecation_warning("..._as_node is deprecated. Render component and then use the .node method instead")
135
+ method_missing(name.gsub(/_as_node$/,""), *args, &block).node
136
+ else
137
+ component = const_get name if defined? name
138
+ React::RenderingContext.render(nil, component, *args, &block)
139
+ end
140
+ end
141
+ ```
142
+
143
+ ### other related issues
144
+
145
+ The Kernel#p method conflicts with the <p> tag. However the p method can be invoked on any object so we are going to go ahead and use it, and deprecate the para method.
data/config.ru CHANGED
@@ -18,7 +18,6 @@ else
18
18
  run Opal::Server.new { |s|
19
19
  s.main = 'opal/rspec/sprockets_runner'
20
20
  s.append_path 'spec'
21
- #s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
22
21
  s.debug = true
23
22
  s.index_path = 'spec/index.html.erb'
24
23
  }
@@ -1,11 +1,38 @@
1
1
  require 'react/native_library'
2
2
 
3
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.)
4
15
  class API
5
16
  @@component_classes = {}
6
17
 
7
18
  def self.import_native_component(opal_class, native_class)
8
- @@component_classes[opal_class.to_s] = native_class
19
+ @@component_classes[opal_class] = native_class
20
+ end
21
+
22
+ def self.eval_native_react_component(name)
23
+ component = `eval(name)`
24
+ raise "#{name} is not defined" if `#{component} === undefined`
25
+ unless `#{component}.prototype !== undefined` &&
26
+ (`!!#{component}.prototype.isReactComponent` || `!!#{component}.prototype.render`)
27
+ raise 'does not appear to be a native react component'
28
+ end
29
+ component
30
+ end
31
+
32
+ def self.native_react_component?(name)
33
+ eval_native_react_component(name)
34
+ rescue
35
+ nil
9
36
  end
10
37
 
11
38
  def self.create_native_react_class(type)
@@ -74,7 +101,7 @@ module React
74
101
  params << @@component_classes[type]
75
102
  elsif type.kind_of?(Class)
76
103
  params << create_native_react_class(type)
77
- elsif HTML_TAGS.include?(type)
104
+ elsif React::Component::Tags::HTML_TAGS.include?(type)
78
105
  params << type
79
106
  elsif type.is_a? String
80
107
  return React::Element.new(type)
@@ -108,6 +135,8 @@ module React
108
135
  props["className"] = value
109
136
  elsif ["style", "dangerously_set_inner_HTML"].include? key
110
137
  props[lower_camelize(key)] = value.to_n
138
+ elsif React::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
139
+ value.each { |k, v| props["#{key}-#{k.tr('_', '-')}"] = v.to_n }
111
140
  else
112
141
  props[React::ATTRIBUTES.include?(lower_camelize(key)) ? lower_camelize(key) : key] = value
113
142
  end
@@ -15,6 +15,8 @@ module React
15
15
  def self.included(base)
16
16
  base.include(API)
17
17
  base.include(Callbacks)
18
+ base.include(Tags)
19
+ base.include(DslInstanceMethods)
18
20
  base.class_eval do
19
21
  class_attribute :initial_state
20
22
  define_callback :before_mount
@@ -25,28 +27,14 @@ module React
25
27
  define_callback :before_unmount
26
28
  end
27
29
  base.extend(ClassMethods)
30
+ end
28
31
 
29
- if base.name
30
- parent = base.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
31
-
32
- class << parent
33
- def method_missing(n, *args, &block)
34
- name = n
35
- if name =~ /_as_node$/
36
- node_only = true
37
- name = name.gsub(/_as_node$/, "")
38
- end
39
- begin
40
- name = const_get(name)
41
- rescue Exception
42
- name = nil
43
- end
44
- unless name && name.method_defined?(:render)
45
- return super
46
- end
47
- React::RenderingContext.build_or_render(node_only, name, *args, &block)
48
- end
49
- end
32
+ def self.deprecation_warning(message)
33
+ @deprecation_messages ||= []
34
+ message = "Warning: Deprecated feature used in #{name}. #{message}"
35
+ unless @deprecation_messages.include? message
36
+ @deprecation_messages << message
37
+ IsomorphicHelpers.log message, :warning
50
38
  end
51
39
  end
52
40
 
@@ -58,64 +46,6 @@ module React
58
46
  raise "no render defined"
59
47
  end unless method_defined?(:render)
60
48
 
61
- def deprecated_params_method(name, *args, &block)
62
- self.class.deprecation_warning "Direct access to param `#{name}`. Use `params.#{name}` instead."
63
- params.send(name, *args, &block)
64
- end
65
-
66
- def children
67
- nodes = if `#{@native}.props.children==undefined`
68
- []
69
- else
70
- [`#{@native}.props.children`].flatten
71
- end
72
- class << nodes
73
- include Enumerable
74
-
75
- def to_n
76
- self
77
- end
78
-
79
- def each(&block)
80
- if block_given?
81
- %x{
82
- React.Children.forEach(#{self.to_n}, function(context){
83
- #{block.call(React::Element.new(`context`))}
84
- })
85
- }
86
- nil
87
- else
88
- Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
89
- %x{
90
- React.Children.forEach(#{self.to_n}, function(context){
91
- #{y << React::Element.new(`context`)}
92
- })
93
- }
94
- end
95
- end
96
- end
97
- end
98
-
99
- nodes
100
- end
101
-
102
- def params
103
- @props_wrapper
104
- end
105
-
106
- def props
107
- Hash.new(`#{@native}.props`)
108
- end
109
-
110
- def refs
111
- Hash.new(`#{@native}.refs`)
112
- end
113
-
114
- def state
115
- #raise "No native ReactComponent associated" unless @native
116
- @state_wrapper ||= StateWrapper.new(@native, self)
117
- end
118
-
119
49
  def update_react_js_state(object, name, value)
120
50
  if object
121
51
  set_state({"***_state_updated_at-***" => Time.now.to_f, "#{object.class.to_s+'.' unless object == self}#{name}" => value})
@@ -206,46 +136,14 @@ module React
206
136
  self.class.process_exception(e, self)
207
137
  end
208
138
 
209
- def p(*args, &block)
210
- if block || args.count == 0 || (args.count == 1 && args.first.is_a?(Hash))
211
- _p_tag(*args, &block)
212
- else
213
- Kernel.p(*args)
214
- end
215
- end
216
-
217
- def component?(name)
218
- name_list = name.split("::")
219
- scope_list = self.class.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }.reverse
220
- scope_list.each do |scope|
221
- component = name_list.inject(scope) do |scope, class_name|
222
- scope.const_get(class_name)
223
- end rescue nil
224
- return component if component && component.method_defined?(:render)
225
- end
226
- nil
227
- end
228
-
229
- def method_missing(n, *args, &block)
230
- return props[n] if props.key? n # TODO deprecate and remove - done so that params shadow tags, no longer needed
231
- name = n
232
- if name =~ /_as_node$/
233
- node_only = true
234
- name = name.gsub(/_as_node$/, "")
235
- end
236
- unless (HTML_TAGS.include?(name) || name == 'present' || name == '_p_tag' || (name = component?(name, self)))
237
- return super
238
- end
239
-
240
- if name == "present"
241
- name = args.shift
242
- end
139
+ attr_reader :waiting_on_resources
243
140
 
244
- if name == "_p_tag"
245
- name = "p"
141
+ def _render_wrapper
142
+ State.set_state_context_to(self) do
143
+ React::RenderingContext.render(nil) {render || ""}.tap { |element| @waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources }
246
144
  end
247
-
248
- React::RenderingContext.build_or_render(node_only, name, *args, &block)
145
+ rescue Exception => e
146
+ self.class.process_exception(e, self)
249
147
  end
250
148
 
251
149
  def watch(value, &on_change)
@@ -256,14 +154,5 @@ module React
256
154
  State.initialize_states(self, self.class.define_state(*args, &block))
257
155
  end
258
156
 
259
- attr_reader :waiting_on_resources
260
-
261
- def _render_wrapper
262
- State.set_state_context_to(self) do
263
- React::RenderingContext.render(nil) {render || ""}.tap { |element| @waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources }
264
- end
265
- rescue Exception => e
266
- self.class.process_exception(e, self)
267
- end
268
157
  end
269
158
  end
@@ -1,29 +1,35 @@
1
1
  module React
2
2
  module Component
3
+ # class level methods (macros) for components
3
4
  module ClassMethods
4
5
  def backtrace(*args)
5
- @backtrace_off = (args[0] == :off)
6
+ @dont_catch_exceptions = (args[0] == :none)
7
+ @backtrace_off = @dont_catch_exceptions || (args[0] == :off)
6
8
  end
7
9
 
8
10
  def process_exception(e, component, reraise = nil)
9
11
  message = ["Exception raised while rendering #{component}"]
10
- if e.backtrace && e.backtrace.length > 1 && !@backtrace_off # seems like e.backtrace is empty in safari???
11
- message << " #{e.backtrace[0]}"
12
- message += e.backtrace[1..-1].collect { |line| line }
12
+ if e.backtrace && e.backtrace.length > 1 && !@backtrace_off
13
+ append_backtrace(message, e.backtrace)
13
14
  else
14
15
  message[0] += ": #{e.message}"
15
16
  end
16
- message = message.join("\n")
17
- `console.error(message)`
18
- raise e if reraise
17
+ `console.error(#{message.join("\n")})`
18
+ raise e if reraise || @dont_catch_exceptions
19
19
  end
20
20
 
21
- def deprecation_warning(message)
22
- @deprecation_messages ||= []
23
- message = "Warning: Deprecated feature used in #{self.name}. #{message}"
24
- unless @deprecation_messages.include? message
25
- @deprecation_messages << message
26
- IsomorphicHelpers.log message, :warning
21
+ def append_backtrace(message_array, backtrace)
22
+ message_array << " #{backtrace[0]}"
23
+ backtrace[1..-1].each { |line| message_array << line }
24
+ end
25
+
26
+ def render(container = nil, params = {}, &block)
27
+ define_method :render do
28
+ if container
29
+ React::RenderingContext.render(container, params) { instance_eval(&block) if block }
30
+ else
31
+ instance_eval(&block)
32
+ end
27
33
  end
28
34
  end
29
35
 
@@ -82,14 +88,14 @@ module React
82
88
  end
83
89
 
84
90
  def required_param(name, options = {})
85
- deprecation_warning "`required_param` is deprecated, use `param` instead."
91
+ React::Component.deprecation_warning "`required_param` is deprecated, use `param` instead."
86
92
  validator.requires(name, options)
87
93
  end
88
94
 
89
95
  alias_method :require_param, :required_param
90
96
 
91
97
  def optional_param(name, options = {})
92
- deprecation_warning "`optional_param` is deprecated, use `param param_name: default_value` instead."
98
+ React::Component.deprecation_warning "`optional_param` is deprecated, use `param param_name: default_value` instead."
93
99
  validator.optional(name, options)
94
100
  end
95
101
 
@@ -128,16 +134,16 @@ module React
128
134
 
129
135
  def define_state_methods(this, name, from = nil, &block)
130
136
  this.define_method("#{name}") do
131
- self.class.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? || from == this
137
+ React::Component.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? || from == this
132
138
  State.get_state(from || self, name)
133
139
  end
134
140
  this.define_method("#{name}=") do |new_state|
135
- self.class.deprecation_warning "Direct assignment to state `#{name}`. Use `#{(from && from != this) ? from : 'state'}.#{name}!` instead."
141
+ React::Component.deprecation_warning "Direct assignment to state `#{name}`. Use `#{(from && from != this) ? from : 'state'}.#{name}!` instead."
136
142
  yield name, State.get_state(from || self, name), new_state if block && block.arity > 0
137
143
  State.set_state(from || self, name, new_state)
138
144
  end
139
145
  this.define_method("#{name}!") do |*args|
140
- self.class.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? or from == this
146
+ React::Component.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? or from == this
141
147
  if args.count > 0
142
148
  yield name, State.get_state(from || self, name), args[0] if block && block.arity > 0
143
149
  current_value = State.get_state(from || self, name)
@@ -172,16 +178,33 @@ module React
172
178
  end
173
179
 
174
180
  def export_component(opts = {})
175
- export_name = (opts[:as] || name).split("::")
181
+ export_name = (opts[:as] || name).split('::')
176
182
  first_name = export_name.first
177
- Native(`window`)[first_name] = add_item_to_tree(Native(`window`)[first_name], [React::API.create_native_react_class(self)] + export_name[1..-1].reverse).to_n
183
+ Native(`window`)[first_name] = add_item_to_tree(
184
+ Native(`window`)[first_name],
185
+ [React::API.create_native_react_class(self)] + export_name[1..-1].reverse
186
+ ).to_n
187
+ end
188
+
189
+ def imports(component_name)
190
+ React::API.import_native_component(
191
+ self, React::API.eval_native_react_component(component_name)
192
+ )
193
+ define_method(:render) {} # define a dummy render method - will never be called...
194
+ rescue Exception => e # rubocop:disable Lint/RescueException : we need to catch everything!
195
+ raise "#{self} cannot import '#{component_name}': #{e.message}."
196
+ # rubocop:enable Lint/RescueException
197
+ ensure
198
+ self
178
199
  end
179
200
 
180
201
  def add_item_to_tree(current_tree, new_item)
181
202
  if Native(current_tree).class != Native::Object || new_item.length == 1
182
- new_item.inject { |memo, sub_name| { sub_name => memo } }
203
+ new_item.inject { |a, e| { e => a } }
183
204
  else
184
- Native(current_tree)[new_item.last] = add_item_to_tree(Native(current_tree)[new_item.last], new_item[0..-2])
205
+ Native(current_tree)[new_item.last] = add_item_to_tree(
206
+ Native(current_tree)[new_item.last], new_item[0..-2]
207
+ )
185
208
  current_tree
186
209
  end
187
210
  end