Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

Commit 0701f8c

Browse files
committed
merged and closes #150 #149
2 parents 5e8d9a1 + 1100e83 commit 0701f8c

22 files changed

+1075
-431
lines changed

.rubocop.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Metrics/LineLength:
2+
Max: 100
3+
4+
Style/MutableConstant:
5+
Enabled: false
6+
7+
Lint/LiteralInCondition:
8+
Enabled: false

component-name-lookup.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

config.ru

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ else
1818
run Opal::Server.new { |s|
1919
s.main = 'opal/rspec/sprockets_runner'
2020
s.append_path 'spec'
21-
#s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
2221
s.debug = true
2322
s.index_path = 'spec/index.html.erb'
2423
}

lib/react/api.rb

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
11
require 'react/native_library'
22

33
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.)
415
class API
516
@@component_classes = {}
617

718
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 && !!#{component}.prototype.isReactComponent`
26+
raise 'does not appear to be a native react component'
27+
end
28+
component
29+
end
30+
31+
def self.native_react_component?(name)
32+
eval_native_react_component(name)
33+
rescue
34+
nil
935
end
1036

1137
def self.create_native_react_class(type)
@@ -74,7 +100,7 @@ def self.create_element(type, properties = {}, &block)
74100
params << @@component_classes[type]
75101
elsif type.kind_of?(Class)
76102
params << create_native_react_class(type)
77-
elsif HTML_TAGS.include?(type)
103+
elsif React::Component::Tags::HTML_TAGS.include?(type)
78104
params << type
79105
elsif type.is_a? String
80106
return React::Element.new(type)

lib/react/component.rb

Lines changed: 8 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ module Component
1515
def self.included(base)
1616
base.include(API)
1717
base.include(Callbacks)
18+
base.include(Tags)
19+
base.include(DslInstanceMethods)
1820
base.class_eval do
1921
class_attribute :initial_state
2022
define_callback :before_mount
@@ -25,29 +27,6 @@ def self.included(base)
2527
define_callback :before_unmount
2628
end
2729
base.extend(ClassMethods)
28-
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
50-
end
5130
end
5231

5332
def initialize(native_element)
@@ -58,64 +37,6 @@ def render
5837
raise "no render defined"
5938
end unless method_defined?(:render)
6039

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-
11940
def update_react_js_state(object, name, value)
12041
if object
12142
set_state({"***_state_updated_at-***" => Time.now.to_f, "#{object.class.to_s+'.' unless object == self}#{name}" => value})
@@ -206,46 +127,14 @@ def component_will_unmount
206127
self.class.process_exception(e, self)
207128
end
208129

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
130+
attr_reader :waiting_on_resources
243131

244-
if name == "_p_tag"
245-
name = "p"
132+
def _render_wrapper
133+
State.set_state_context_to(self) do
134+
React::RenderingContext.render(nil) {render || ""}.tap { |element| @waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources }
246135
end
247-
248-
React::RenderingContext.build_or_render(node_only, name, *args, &block)
136+
rescue Exception => e
137+
self.class.process_exception(e, self)
249138
end
250139

251140
def watch(value, &on_change)
@@ -256,14 +145,5 @@ def define_state(*args, &block)
256145
State.initialize_states(self, self.class.define_state(*args, &block))
257146
end
258147

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
268148
end
269149
end

0 commit comments

Comments
 (0)