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

Commit bcc36f7

Browse files
committed
Squash changes of baf71b4 95397b4 ea6271d 08c4028
1 parent 91d17f3 commit bcc36f7

File tree

7 files changed

+195
-109
lines changed

7 files changed

+195
-109
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ Whitespace conventions:
1818
- 1 spaces before normal text
1919
-->
2020

21+
## [0.8.9] - Unreleased
22+
23+
### Fixed
24+
25+
- Gets rid of react warnings about updating state during render (#155)
26+
- Multiple HAML classes (i.e. div.foo.bar) was not working (regression introduced in 0.8.8)
27+
- Don't send nil (null) to form components as the value string (#157)
28+
- Process `params` (props) correctly when using `Element#on` or `Element#render` (#158)
29+
- Deprecate shallow param compare (#156)
30+
31+
2132
## [0.8.8] - 2016-07-13
2233

2334
### Added

lib/react/component/class_methods.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,26 @@ def append_backtrace(message_array, backtrace)
2929
end
3030

3131
def render(container = nil, params = {}, &block)
32-
define_method :render do
33-
if container
32+
if container
33+
container = container.type if container.is_a? React::Element
34+
define_method :render do
3435
React::RenderingContext.render(container, params) { instance_eval(&block) if block }
35-
else
36-
instance_eval(&block)
3736
end
37+
else
38+
define_method(:render) { instance_eval(&block) }
3839
end
3940
end
4041

42+
# method missing will assume the method is a class name, and will treat this a render of
43+
# of the component, i.e. Foo::Bar.baz === Foo::Bar().baz
44+
45+
def method_missing(name, *args, &children)
46+
Object.method_missing(name, *args, &children) unless args.empty?
47+
React::RenderingContext.render(
48+
self, class: React::Element.haml_class_name(name), &children
49+
)
50+
end
51+
4152
def validator
4253
@validator ||= Validator.new(props_wrapper)
4354
end

lib/react/component/tags.rb

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# class HtmlTagWrapper
2+
# def initialize(name)
3+
# @name = name
4+
# end
5+
# def to_s
6+
# @name
7+
# end
8+
# def method_missing(n)
9+
#
10+
# end
11+
12+
113
module React
214
module Component
315
# contains the name of all HTML tags, and the mechanism to register a component
@@ -29,6 +41,9 @@ def present_as_node(component, *params, &children)
2941

3042
# define each predefined tag as an instance method
3143

44+
45+
46+
3247
HTML_TAGS.each do |tag|
3348
define_method(tag) do |*params, &children|
3449
if tag == 'p'
@@ -41,14 +56,27 @@ def present_as_node(component, *params, &children)
4156
React::RenderingContext.render(tag, *params, &children)
4257
end
4358
end
44-
alias_method tag.upcase, tag
45-
const_set tag.upcase, tag
59+
if tag != :div
60+
alias_method tag.upcase, tag
61+
const_set tag.upcase, tag
62+
else
63+
alias_method tag.upcase, tag
64+
#const_set tag.upcase, React.create_element(tag)
65+
#Object.const_set tag.upcase, Class.new(HtmlTagWrapper)
66+
end
4667
# handle deprecated _as_node style
4768
define_method("#{tag}_as_node") do |*params, &children|
4869
React::RenderingContext.build_only(tag, *params, &children)
4970
end
5071
end
5172

73+
def self.html_tag_class_for(tag)
74+
downcased_tag = tag.downcase
75+
if tag =~ /[A-Z]+/ && HTML_TAGS.include?(downcased_tag)
76+
Object.const_set tag, React.create_element(downcased_tag)
77+
end
78+
end
79+
5280
# use method_missing to look up component names in the form of "Foo(..)"
5381
# where there is no preceeding scope.
5482

@@ -103,6 +131,8 @@ def find_component(name)
103131

104132
def lookup_const(name)
105133
return nil unless name =~ /^[A-Z]/
134+
#html_tag = React::Component::Tags.html_tag_class(name)
135+
#return html_tag if html_tag
106136
scopes = self.class.name.to_s.split('::').inject([Module]) do |nesting, next_const|
107137
nesting + [nesting.last.const_get(next_const)]
108138
end.reverse

lib/react/element.rb

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ def on(*event_names, &block)
4444
# Used for elements that are not yet in DOM, i.e. they are provided as children
4545
# or they have been explicitly removed from the rendering context using the delete method.
4646

47-
def render(props = {})
47+
def render(props = {}, &new_block)
4848
if props.empty?
4949
React::RenderingContext.render(self)
5050
else
5151
props = API.convert_props(props)
5252
React::RenderingContext.render(
5353
Element.new(`React.cloneElement(#{to_n}, #{props.shallow_to_n})`,
54-
type, properties.merge(props), block)
54+
type, properties.merge(props), block),
5555
)
5656
end
5757
end
@@ -77,20 +77,34 @@ def as_node
7777
# params may be provide to each class (but typically only to the last for easy reading.)
7878

7979
def method_missing(class_name, args = {}, &new_block)
80-
class_name = class_name.gsub(/__|_/, '__' => '_', '_' => '-')
81-
new_props = properties.dup
82-
new_props[:className] = "\
83-
#{class_name} #{new_props[:className]} #{args.delete(:class)} #{args.delete(:className)}\
84-
".split(' ').uniq.join(' ')
85-
new_props.merge! args
80+
return dup.render.method_missing(class_name, args, &new_block) unless rendered?
8681
React::RenderingContext.replace(
8782
self,
88-
RenderingContext.build { RenderingContext.render(type, new_props, &new_block) }
83+
RenderingContext.build do
84+
RenderingContext.render(type, build_new_properties(class_name, args), &new_block)
85+
end
8986
)
9087
end
9188

89+
def rendered?
90+
React::RenderingContext.rendered? self
91+
end
92+
93+
def self.haml_class_name(class_name)
94+
class_name.gsub(/__|_/, '__' => '_', '_' => '-')
95+
end
96+
9297
private
9398

99+
def build_new_properties(class_name, args)
100+
class_name = self.class.haml_class_name(class_name)
101+
new_props = properties.dup
102+
new_props[:className] = "\
103+
#{class_name} #{new_props[:className]} #{args.delete(:class)} #{args.delete(:className)}\
104+
".split(' ').uniq.join(' ')
105+
new_props.merge! args
106+
end
107+
94108
# built in events, events going to native components, and events going to reactrb
95109

96110
# built in events will have their event param translated to the Event wrapper

lib/react/object.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Lazy load HTML tag constants in the form DIV or A
2+
# This is needed to allow for a HAML expression like this DIV.my_class
3+
class Object
4+
class << self
5+
alias _reactrb_tag_original_const_missing const_missing
6+
7+
def const_missing(const_name)
8+
# Opal uses const_missing to initially define things,
9+
# so we always call the original, and respond to the exception
10+
_reactrb_tag_original_const_missing(const_name)
11+
rescue StandardError => e
12+
React::Component::Tags.html_tag_class_for(const_name) || raise(e)
13+
end
14+
end
15+
end

lib/react/rendering_context.rb

Lines changed: 97 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,113 +2,117 @@ module React
22
class RenderingContext
33
class << self
44
attr_accessor :waiting_on_resources
5-
end
65

7-
def self.build_only(name, *args, &block)
8-
React::Component.deprecation_warning(
9-
'..._as_node is deprecated. Render component and then use the .node method instead'
10-
)
11-
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
12-
end
6+
def build_only(name, *args, &block)
7+
React::Component.deprecation_warning(
8+
'..._as_node is deprecated. Render component and then use the .node method instead'
9+
)
10+
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
11+
end
1312

14-
def self.render(name, *args, &block)
15-
remove_nodes_from_args(args)
16-
@buffer ||= [] unless @buffer
17-
if block
18-
element = build do
19-
saved_waiting_on_resources = waiting_on_resources
20-
self.waiting_on_resources = nil
21-
run_child_block(name.nil?, &block)
22-
if name
23-
buffer = @buffer.dup
24-
React.create_element(name, *args) { buffer }.tap do |element|
25-
element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
13+
def render(name, *args, &block)
14+
remove_nodes_from_args(args)
15+
@buffer ||= [] unless @buffer
16+
if block
17+
element = build do
18+
saved_waiting_on_resources = waiting_on_resources
19+
self.waiting_on_resources = nil
20+
run_child_block(name.nil?, &block)
21+
if name
22+
buffer = @buffer.dup
23+
React.create_element(name, *args) { buffer }.tap do |element|
24+
element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
25+
end
26+
elsif @buffer.last.is_a? React::Element
27+
@buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
28+
else
29+
@buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
2630
end
27-
elsif @buffer.last.is_a? React::Element
28-
@buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
29-
else
30-
@buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
3131
end
32+
elsif name.is_a? React::Element
33+
element = name
34+
else
35+
element = React.create_element(name, *args)
36+
element.waiting_on_resources = waiting_on_resources
3237
end
33-
elsif name.is_a? React::Element
34-
element = name
35-
else
36-
element = React.create_element(name, *args)
37-
element.waiting_on_resources = waiting_on_resources
38+
@buffer << element
39+
self.waiting_on_resources = nil
40+
element
3841
end
39-
@buffer << element
40-
self.waiting_on_resources = nil
41-
element
42-
end
4342

44-
def self.build
45-
current = @buffer
46-
@buffer = []
47-
return_val = yield @buffer
48-
@buffer = current
49-
return_val
50-
end
43+
def build
44+
current = @buffer
45+
@buffer = []
46+
return_val = yield @buffer
47+
@buffer = current
48+
return_val
49+
end
5150

52-
def self.as_node(element)
53-
@buffer.delete(element)
54-
element
55-
end
51+
def as_node(element)
52+
@buffer.delete(element)
53+
element
54+
end
5655

57-
class << self; alias delete as_node; end
56+
alias delete as_node
5857

59-
def self.replace(e1, e2)
60-
@buffer[@buffer.index(e1)] = e2
61-
end
58+
def rendered?(element)
59+
@buffer.include? element
60+
end
6261

63-
def self.remove_nodes_from_args(args)
64-
args[0].each do |key, value|
65-
value.as_node if value.is_a?(Element) rescue nil
66-
end if args[0] && args[0].is_a?(Hash)
67-
end
62+
def replace(e1, e2)
63+
@buffer[@buffer.index(e1)] = e2
64+
end
6865

69-
# run_child_block gathers the element(s) generated by a child block.
70-
# for example when rendering this div: div { "hello".span; "goodby".span }
71-
# two child Elements will be generated.
72-
#
73-
# the final value of the block should either be
74-
# 1 an object that responds to :acts_as_string?
75-
# 2 a string,
76-
# 3 an element that is NOT yet pushed on the rendering buffer
77-
# 4 or the last element pushed on the buffer
78-
#
79-
# in case 1 we change the object to a string, and then it becomes case 2
80-
# in case 2 we automatically push the string onto the buffer
81-
# in case 3 we also push the Element onto the buffer IF the buffer is empty
82-
# case 4 requires no special processing
83-
#
84-
# Once we have taken care of these special cases we do a check IF we are in an
85-
# outer rendering scope. In this case react only allows us to generate 1 Element
86-
# so we insure that is the case, and also check to make sure that element in the buffer
87-
# is the element returned
88-
89-
def self.run_child_block(is_outer_scope)
90-
result = yield
91-
result = result.to_s if result.try :acts_as_string?
92-
@buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
93-
raise_render_error(result) if is_outer_scope && @buffer != [result]
94-
end
66+
def remove_nodes_from_args(args)
67+
args[0].each do |key, value|
68+
value.as_node if value.is_a?(Element) rescue nil
69+
end if args[0] && args[0].is_a?(Hash)
70+
end
9571

96-
# heurestically raise a meaningful error based on the situation
97-
98-
def self.raise_render_error(result)
99-
improper_render 'A different element was returned than was generated within the DSL.',
100-
'Possibly improper use of Element#delete.' if @buffer.count == 1
101-
improper_render "Instead #{@buffer.count} elements were generated.",
102-
'Do you want to wrap your elements in a div?' if @buffer.count > 1
103-
improper_render "Instead the component #{result} was returned.",
104-
"Did you mean #{result}()?" if result.try :reactrb_component?
105-
improper_render "Instead the #{result.class} #{result} was returned.",
106-
'You may need to convert this to a string.'
107-
end
72+
# run_child_block gathers the element(s) generated by a child block.
73+
# for example when rendering this div: div { "hello".span; "goodby".span }
74+
# two child Elements will be generated.
75+
#
76+
# the final value of the block should either be
77+
# 1 an object that responds to :acts_as_string?
78+
# 2 a string,
79+
# 3 an element that is NOT yet pushed on the rendering buffer
80+
# 4 or the last element pushed on the buffer
81+
#
82+
# in case 1 we change the object to a string, and then it becomes case 2
83+
# in case 2 we automatically push the string onto the buffer
84+
# in case 3 we also push the Element onto the buffer IF the buffer is empty
85+
# case 4 requires no special processing
86+
#
87+
# Once we have taken care of these special cases we do a check IF we are in an
88+
# outer rendering scope. In this case react only allows us to generate 1 Element
89+
# so we insure that is the case, and also check to make sure that element in the buffer
90+
# is the element returned
91+
92+
def run_child_block(is_outer_scope)
93+
result = yield
94+
result = result.to_s if result.try :acts_as_string?
95+
@buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
96+
raise_render_error(result) if is_outer_scope && @buffer != [result]
97+
end
98+
99+
# heurestically raise a meaningful error based on the situation
108100

109-
def self.improper_render(message, solution)
110-
raise "a component's render method must generate and return exactly 1 element or a string.\n"\
111-
" #{message} #{solution}"
101+
def raise_render_error(result)
102+
improper_render 'A different element was returned than was generated within the DSL.',
103+
'Possibly improper use of Element#delete.' if @buffer.count == 1
104+
improper_render "Instead #{@buffer.count} elements were generated.",
105+
'Do you want to wrap your elements in a div?' if @buffer.count > 1
106+
improper_render "Instead the component #{result} was returned.",
107+
"Did you mean #{result}()?" if result.try :reactrb_component?
108+
improper_render "Instead the #{result.class} #{result} was returned.",
109+
'You may need to convert this to a string.'
110+
end
111+
112+
def improper_render(message, solution)
113+
raise "a component's render method must generate and return exactly 1 element or a string.\n"\
114+
" #{message} #{solution}"
115+
end
112116
end
113117
end
114118

0 commit comments

Comments
 (0)