reactrb 0.8.3 → 0.8.4

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