reactrb 0.8.8 → 0.9.0

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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +24 -3
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +1154 -3
  5. data/.travis.yml +20 -0
  6. data/Appraisals +20 -0
  7. data/CHANGELOG.md +28 -3
  8. data/Gemfile +4 -5
  9. data/README.md +6 -9
  10. data/Rakefile +6 -1
  11. data/config.ru +7 -6
  12. data/gemfiles/opal_0.8_react_13.gemfile +13 -0
  13. data/gemfiles/opal_0.8_react_14.gemfile +13 -0
  14. data/gemfiles/opal_0.8_react_15.gemfile +13 -0
  15. data/gemfiles/opal_0.9_react_13.gemfile +13 -0
  16. data/gemfiles/opal_0.9_react_14.gemfile +13 -0
  17. data/gemfiles/opal_0.9_react_15.gemfile +13 -0
  18. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +1 -1
  19. data/lib/rails-helpers/top_level_rails_component.rb +1 -1
  20. data/lib/react-sources/react-server.js +2 -0
  21. data/lib/react/api.rb +13 -12
  22. data/lib/react/children.rb +30 -0
  23. data/lib/react/component.rb +27 -46
  24. data/lib/react/component/class_methods.rb +28 -32
  25. data/lib/react/component/dsl_instance_methods.rb +4 -34
  26. data/lib/react/component/params.rb +6 -0
  27. data/lib/react/component/props_wrapper.rb +22 -27
  28. data/lib/react/component/should_component_update.rb +98 -0
  29. data/lib/react/component/tags.rb +45 -4
  30. data/lib/react/element.rb +26 -13
  31. data/lib/react/object.rb +15 -0
  32. data/lib/react/react-source.rb +9 -0
  33. data/lib/react/rendering_context.rb +97 -93
  34. data/lib/react/state.rb +27 -21
  35. data/lib/react/test.rb +16 -0
  36. data/lib/react/test/dsl.rb +17 -0
  37. data/lib/react/test/matchers/render_html_matcher.rb +49 -0
  38. data/lib/react/test/rspec.rb +15 -0
  39. data/lib/react/test/session.rb +46 -0
  40. data/lib/react/top_level.rb +50 -14
  41. data/lib/react/validator.rb +5 -5
  42. data/lib/reactive-ruby/isomorphic_helpers.rb +0 -7
  43. data/lib/reactive-ruby/version.rb +1 -1
  44. data/lib/reactrb.rb +14 -14
  45. data/lib/reactrb/deep-compare.rb +24 -0
  46. data/lib/sources/react-latest.js +2 -0
  47. data/lib/sources/react-v13.js +4 -1
  48. data/lib/sources/react-v14.js +3 -84
  49. data/lib/sources/react-v15.js +3 -0
  50. data/logo1.png +0 -0
  51. data/logo2.png +0 -0
  52. data/logo3.png +0 -0
  53. data/path_release_steps.md +1 -1
  54. data/reactrb.gemspec +2 -3
  55. data/spec/react/children_spec.rb +76 -0
  56. data/spec/react/component/base_spec.rb +3 -7
  57. data/spec/react/component_spec.rb +181 -60
  58. data/spec/react/dsl_spec.rb +26 -19
  59. data/spec/react/element_spec.rb +16 -1
  60. data/spec/react/native_library_spec.rb +20 -0
  61. data/spec/react/opal_jquery_extensions_spec.rb +27 -0
  62. data/spec/react/param_declaration_spec.rb +47 -78
  63. data/spec/react/react_spec.rb +7 -9
  64. data/spec/react/state_spec.rb +29 -0
  65. data/spec/react/test/dsl_spec.rb +43 -0
  66. data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
  67. data/spec/react/test/rspec_spec.rb +62 -0
  68. data/spec/react/test/session_spec.rb +100 -0
  69. data/spec/react/test/utils_spec.rb +45 -0
  70. data/spec/react/top_level_component_spec.rb +33 -5
  71. data/spec/react/tutorial/tutorial_spec.rb +5 -5
  72. data/spec/react/validator_spec.rb +10 -13
  73. data/spec/reactive-ruby/component_loader_spec.rb +3 -0
  74. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +5 -4
  75. data/spec/spec_helper.rb +6 -3
  76. data/spec/support/react/spec_helpers.rb +9 -2
  77. metadata +47 -124
  78. data/example/examples/Gemfile +0 -7
  79. data/example/examples/app/basics.js.rb +0 -42
  80. data/example/examples/app/items.rb +0 -11
  81. data/example/examples/app/jquery.js +0 -5
  82. data/example/examples/app/nodes.rb +0 -61
  83. data/example/examples/app/react-router.js +0 -6
  84. data/example/examples/app/react_api_demo.rb +0 -29
  85. data/example/examples/app/rerendering.rb +0 -72
  86. data/example/examples/app/reuse.rb +0 -59
  87. data/example/examples/app/show.rb +0 -52
  88. data/example/examples/config.ru +0 -38
  89. data/example/rails-tutorial/.gitignore +0 -17
  90. data/example/rails-tutorial/Gemfile +0 -51
  91. data/example/rails-tutorial/README.rdoc +0 -28
  92. data/example/rails-tutorial/Rakefile +0 -6
  93. data/example/rails-tutorial/app/assets/images/.keep +0 -0
  94. data/example/rails-tutorial/app/assets/javascripts/application.rb +0 -15
  95. data/example/rails-tutorial/app/assets/stylesheets/application.css +0 -15
  96. data/example/rails-tutorial/app/controllers/application_controller.rb +0 -6
  97. data/example/rails-tutorial/app/controllers/concerns/.keep +0 -0
  98. data/example/rails-tutorial/app/controllers/home_controller.rb +0 -6
  99. data/example/rails-tutorial/app/helpers/application_helper.rb +0 -2
  100. data/example/rails-tutorial/app/mailers/.keep +0 -0
  101. data/example/rails-tutorial/app/models/.keep +0 -0
  102. data/example/rails-tutorial/app/models/concerns/.keep +0 -0
  103. data/example/rails-tutorial/app/views/components.rb +0 -3
  104. data/example/rails-tutorial/app/views/components/home/show.rb +0 -47
  105. data/example/rails-tutorial/app/views/layouts/application.html.erb +0 -14
  106. data/example/rails-tutorial/bin/bundle +0 -3
  107. data/example/rails-tutorial/bin/rails +0 -8
  108. data/example/rails-tutorial/bin/rake +0 -8
  109. data/example/rails-tutorial/bin/setup +0 -29
  110. data/example/rails-tutorial/bin/spring +0 -15
  111. data/example/rails-tutorial/config.ru +0 -4
  112. data/example/rails-tutorial/config/application.rb +0 -26
  113. data/example/rails-tutorial/config/boot.rb +0 -3
  114. data/example/rails-tutorial/config/database.yml +0 -25
  115. data/example/rails-tutorial/config/environment.rb +0 -5
  116. data/example/rails-tutorial/config/environments/development.rb +0 -41
  117. data/example/rails-tutorial/config/environments/production.rb +0 -79
  118. data/example/rails-tutorial/config/environments/test.rb +0 -42
  119. data/example/rails-tutorial/config/initializers/assets.rb +0 -11
  120. data/example/rails-tutorial/config/initializers/backtrace_silencers.rb +0 -7
  121. data/example/rails-tutorial/config/initializers/cookies_serializer.rb +0 -3
  122. data/example/rails-tutorial/config/initializers/filter_parameter_logging.rb +0 -4
  123. data/example/rails-tutorial/config/initializers/inflections.rb +0 -16
  124. data/example/rails-tutorial/config/initializers/mime_types.rb +0 -4
  125. data/example/rails-tutorial/config/initializers/session_store.rb +0 -3
  126. data/example/rails-tutorial/config/initializers/wrap_parameters.rb +0 -14
  127. data/example/rails-tutorial/config/locales/en.yml +0 -23
  128. data/example/rails-tutorial/config/routes.rb +0 -59
  129. data/example/rails-tutorial/config/secrets.yml +0 -22
  130. data/example/rails-tutorial/db/seeds.rb +0 -7
  131. data/example/rails-tutorial/lib/assets/.keep +0 -0
  132. data/example/rails-tutorial/lib/tasks/.keep +0 -0
  133. data/example/rails-tutorial/log/.keep +0 -0
  134. data/example/rails-tutorial/public/404.html +0 -67
  135. data/example/rails-tutorial/public/422.html +0 -67
  136. data/example/rails-tutorial/public/500.html +0 -66
  137. data/example/rails-tutorial/public/favicon.ico +0 -0
  138. data/example/rails-tutorial/public/robots.txt +0 -5
  139. data/example/rails-tutorial/test/controllers/.keep +0 -0
  140. data/example/rails-tutorial/test/fixtures/.keep +0 -0
  141. data/example/rails-tutorial/test/helpers/.keep +0 -0
  142. data/example/rails-tutorial/test/integration/.keep +0 -0
  143. data/example/rails-tutorial/test/mailers/.keep +0 -0
  144. data/example/rails-tutorial/test/models/.keep +0 -0
  145. data/example/rails-tutorial/test/test_helper.rb +0 -10
  146. data/example/rails-tutorial/vendor/assets/javascripts/.keep +0 -0
  147. data/example/rails-tutorial/vendor/assets/stylesheets/.keep +0 -0
  148. data/example/sinatra-tutorial/.DS_Store +0 -0
  149. data/example/sinatra-tutorial/Gemfile +0 -5
  150. data/example/sinatra-tutorial/README.md +0 -8
  151. data/example/sinatra-tutorial/_comments.json +0 -42
  152. data/example/sinatra-tutorial/app/example.rb +0 -290
  153. data/example/sinatra-tutorial/app/jquery.js +0 -5
  154. data/example/sinatra-tutorial/config.ru +0 -58
  155. data/example/sinatra-tutorial/public/base.css +0 -62
  156. data/example/todos/Gemfile +0 -11
  157. data/example/todos/README.md +0 -37
  158. data/example/todos/Rakefile +0 -8
  159. data/example/todos/app/application.rb +0 -22
  160. data/example/todos/app/components/app.react.rb +0 -61
  161. data/example/todos/app/components/footer.react.rb +0 -31
  162. data/example/todos/app/components/todo_item.react.rb +0 -46
  163. data/example/todos/app/components/todo_list.react.rb +0 -25
  164. data/example/todos/app/models/todo.rb +0 -19
  165. data/example/todos/config.ru +0 -14
  166. data/example/todos/index.html.haml +0 -16
  167. data/example/todos/spec/todo_spec.rb +0 -28
  168. data/example/todos/vendor/base.css +0 -410
  169. data/example/todos/vendor/bg.png +0 -0
  170. data/example/todos/vendor/jquery.js +0 -4
@@ -6,21 +6,21 @@ module React
6
6
  def reactrb_component?
7
7
  true
8
8
  end
9
-
9
+
10
10
  def backtrace(*args)
11
11
  @dont_catch_exceptions = (args[0] == :none)
12
12
  @backtrace_off = @dont_catch_exceptions || (args[0] == :off)
13
13
  end
14
14
 
15
- def process_exception(e, component, reraise = nil)
16
- message = ["Exception raised while rendering #{component}"]
17
- if e.backtrace && e.backtrace.length > 1 && !@backtrace_off
18
- append_backtrace(message, e.backtrace)
19
- else
20
- message[0] += ": #{e.message}"
15
+ def process_exception(e, component, reraise = @dont_catch_exceptions)
16
+ unless @dont_catch_exceptions
17
+ message = ["Exception raised while rendering #{component}: #{e.message}"]
18
+ if e.backtrace && e.backtrace.length > 1 && !@backtrace_off
19
+ append_backtrace(message, e.backtrace)
20
+ end
21
+ `console.error(#{message.join("\n")})`
21
22
  end
22
- `console.error(#{message.join("\n")})`
23
- raise e if reraise || @dont_catch_exceptions
23
+ raise e if reraise
24
24
  end
25
25
 
26
26
  def append_backtrace(message_array, backtrace)
@@ -29,17 +29,28 @@ module React
29
29
  end
30
30
 
31
31
  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
34
35
  React::RenderingContext.render(container, params) { instance_eval(&block) if block }
35
- else
36
- instance_eval(&block)
37
36
  end
37
+ else
38
+ define_method(:render) { instance_eval(&block) }
38
39
  end
39
40
  end
40
41
 
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
+
41
52
  def validator
42
- @validator ||= Validator.new(self)
53
+ @validator ||= Validator.new(props_wrapper)
43
54
  end
44
55
 
45
56
  def prop_types
@@ -70,10 +81,6 @@ module React
70
81
  @props_wrapper ||= Class.new(PropsWrapper)
71
82
  end
72
83
 
73
- def define_param(name, param_type)
74
- props_wrapper.define_param(name, param_type, self)
75
- end
76
-
77
84
  def param(*args)
78
85
  if args[0].is_a? Hash
79
86
  options = args[0]
@@ -92,22 +99,11 @@ module React
92
99
  end
93
100
  end
94
101
 
95
- def required_param(name, options = {})
96
- React::Component.deprecation_warning "`required_param` is deprecated, use `param` instead."
97
- validator.requires(name, options)
98
- end
99
-
100
- alias_method :require_param, :required_param
101
-
102
- def optional_param(name, options = {})
103
- React::Component.deprecation_warning "`optional_param` is deprecated, use `param param_name: default_value` instead."
104
- validator.optional(name, options)
105
- end
106
-
107
102
  def collect_other_params_as(name)
108
103
  validator.allow_undefined_props = true
109
- define_method(name) do
110
- @_all_others ||= self.class.validator.undefined_props(props)
104
+ validator_in_lexical_scope = validator
105
+ props_wrapper.define_method(name) do
106
+ @_all_others ||= validator_in_lexical_scope.undefined_props(props)
111
107
  end
112
108
 
113
109
  validator_in_lexial_scope = validator
@@ -1,44 +1,14 @@
1
+ require "react/children"
2
+
1
3
  module React
2
4
  module Component
3
5
  module DslInstanceMethods
4
6
  def children
5
- if `#{@native}.props.children==undefined`
6
- nodes = []
7
- else
8
- nodes = [`#{@native}.props.children`].flatten
9
- end
10
- class << nodes
11
- include Enumerable
12
-
13
- def to_n
14
- self
15
- end
16
-
17
- def each(&block)
18
- if block_given?
19
- %x{
20
- React.Children.forEach(#{self.to_n}, function(context){
21
- #{yield React::Element.new(`context`)}
22
- })
23
- }
24
- nil
25
- else
26
- Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
27
- %x{
28
- React.Children.forEach(#{self.to_n}, function(context){
29
- #{y << Element.new(`context`)}
30
- })
31
- }
32
- end
33
- end
34
- end
35
- end
36
-
37
- nodes
7
+ Children.new(`#{@native}.props.children`)
38
8
  end
39
9
 
40
10
  def params
41
- @props_wrapper
11
+ @params ||= self.class.props_wrapper.new(self)
42
12
  end
43
13
 
44
14
  def props
@@ -0,0 +1,6 @@
1
+ module React
2
+ module Component
3
+ module Params
4
+ end
5
+ end
6
+ end
@@ -7,16 +7,10 @@ module React
7
7
  end
8
8
 
9
9
  class PropsWrapper
10
- attr_reader :props
10
+ attr_reader :component
11
11
 
12
- def self.define_param(name, param_type, owner)
13
- owner.define_method("#{name}") do |*args, &block|
14
- deprecated_params_method("#{name}", *args, &block)
15
- end
12
+ def self.define_param(name, param_type)
16
13
  if param_type == Observable
17
- owner.define_method("#{name}!") do |*args|
18
- deprecated_params_method("#{name}!", *args)
19
- end
20
14
  define_method("#{name}") do
21
15
  value_for(name)
22
16
  end
@@ -39,10 +33,8 @@ module React
39
33
  end
40
34
  else
41
35
  define_method("#{name}") do
42
- if @processed_params.has_key? name
43
- @processed_params[name]
44
- else
45
- @processed_params[name] = if param_type.respond_to? :_react_param_conversion
36
+ fetch_from_cache(name) do
37
+ if param_type.respond_to? :_react_param_conversion
46
38
  param_type._react_param_conversion props[name]
47
39
  elsif param_type.is_a?(Array) &&
48
40
  param_type[0].respond_to?(:_react_param_conversion)
@@ -57,21 +49,8 @@ module React
57
49
  end
58
50
  end
59
51
 
60
- def unchanged_processed_params(new_props)
61
- Hash[
62
- *@processed_params.collect do |key, value|
63
- [key, value] if @props[key].equal? new_props[key] # `#{@props[key]} == #{new_props[key]}`
64
- end.compact.flatten(1)
65
- ]
66
- end
67
-
68
- def initialize(props, current_props_wrapper=nil)
69
- @props = props || {}
70
- @processed_params = if current_props_wrapper
71
- current_props_wrapper.unchanged_processed_params(props)
72
- else
73
- {}
74
- end
52
+ def initialize(component)
53
+ @component = component
75
54
  end
76
55
 
77
56
  def [](prop)
@@ -80,6 +59,22 @@ module React
80
59
 
81
60
  private
82
61
 
62
+ def fetch_from_cache(name)
63
+ last, value = cache[name]
64
+ return value if last.equal?(props[name])
65
+ yield.tap do |value|
66
+ cache[name] = [props[name], value]
67
+ end
68
+ end
69
+
70
+ def cache
71
+ @cache ||= Hash.new { |h, k| h[k] = [] }
72
+ end
73
+
74
+ def props
75
+ component.props
76
+ end
77
+
83
78
  def value_for(name)
84
79
  self[name].instance_variable_get("@value") if self[name]
85
80
  end
@@ -0,0 +1,98 @@
1
+ module React
2
+ module Component
3
+ #
4
+ # React assumes all components should update, unless a component explicitly overrides
5
+ # the shouldComponentUpdate method. Reactrb does an explicit check doing a shallow
6
+ # compare of params, and using a timestamp to determine if state has changed.
7
+
8
+ # If needed components can provide their own #needs_update? method which will be
9
+ # passed the next params and state opal hashes.
10
+
11
+ # Attached to these hashes is a #changed? method that returns whether the hash contains
12
+ # changes as calculated by the base mechanism. This way implementations of #needs_update?
13
+ # can use the base comparison mechanism as needed.
14
+
15
+ # For example
16
+ # def needs_update?(next_params, next_state)
17
+ # # use a special comparison method
18
+ # return false if next_state.changed? || next_params.changed?
19
+ # # do some other special checks
20
+ # end
21
+
22
+ # Note that beginning in 0.9 we will use standard ruby compare on all params further reducing
23
+ # the need for needs_update?
24
+ #
25
+ module ShouldComponentUpdate
26
+ def should_component_update?(native_next_props, native_next_state)
27
+ State.set_state_context_to(self, false) do
28
+ next_params = Hash.new(native_next_props)
29
+ # rubocop:disable Style/DoubleNegation # we must return true/false to js land
30
+ if respond_to?(:needs_update?)
31
+ !!call_needs_update(next_params, native_next_state)
32
+ else
33
+ !!(props_changed?(next_params) || native_state_changed?(native_next_state))
34
+ end
35
+ # rubocop:enable Style/DoubleNegation
36
+ end
37
+ end
38
+
39
+ # create opal hashes for next params and state, and attach
40
+ # the changed? method to each hash
41
+
42
+ def call_needs_update(next_params, native_next_state)
43
+ component = self
44
+ next_params.define_singleton_method(:changed?) do
45
+ component.props_changed?(self)
46
+ end
47
+ next_state = Hash.new(native_next_state)
48
+ next_state.define_singleton_method(:changed?) do
49
+ component.native_state_changed?(native_next_state)
50
+ end
51
+ needs_update?(next_params, next_state)
52
+ end
53
+
54
+ # Whenever state changes, reactrb updates a timestamp on the state object.
55
+ # We can rapidly check for state changes comparing the incoming state time_stamp
56
+ # with the current time stamp.
57
+
58
+ # Different versions of react treat empty state differently, so we first
59
+ # convert anything that looks like an empty state to "false" for consistency.
60
+
61
+ # Then we test if one state is empty and the other is not, then we return false.
62
+ # Then we test if both states are empty we return true.
63
+ # If either state does not have a time stamp then we have to assume a change.
64
+ # Otherwise we check time stamps
65
+
66
+ # rubocop:disable Metrics/MethodLength # for effeciency we want this to be one method
67
+ def native_state_changed?(next_state)
68
+ %x{
69
+ var current_state = #{@native}.state
70
+ var normalized_next_state =
71
+ !#{next_state} || Object.keys(#{next_state}).length === 0 || #{nil} == #{next_state} ?
72
+ false : #{next_state}
73
+ var normalized_current_state =
74
+ !current_state || Object.keys(current_state).length === 0 || #{nil} == current_state ?
75
+ false : current_state
76
+ if (!normalized_current_state != !normalized_next_state) return(true)
77
+ if (!normalized_current_state && !normalized_next_state) return(false)
78
+ if (!normalized_current_state['***_state_updated_at-***'] ||
79
+ !normalized_next_state['***_state_updated_at-***']) return(true)
80
+ return (normalized_current_state['***_state_updated_at-***'] !=
81
+ normalized_next_state['***_state_updated_at-***'])
82
+ }
83
+ end
84
+ # rubocop:enable Metrics/MethodLength
85
+
86
+ # Do a shallow compare on the two hashes. Starting in 0.9 we will do a deep compare.
87
+
88
+ def props_changed?(next_params)
89
+ Component.deprecation_warning(
90
+ "Using shallow incoming params comparison.\n"\
91
+ 'Do a require "reactrb/deep-compare, to get 0.9 behavior'
92
+ )
93
+ (props.keys.sort != next_params.keys.sort) ||
94
+ next_params.detect { |k, v| `#{v} != #{@native}.props[#{k}]` }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -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
+
1
13
  module React
2
14
  module Component
3
15
  # contains the name of all HTML tags, and the mechanism to register a component
@@ -10,7 +22,10 @@ module React
10
22
  main map mark menu menuitem meta meter nav noscript object ol optgroup option
11
23
  output p param picture pre progress q rp rt ruby s samp script section select
12
24
  small source span strong style sub summary sup table tbody td textarea tfoot th
13
- thead time title tr track u ul var video wbr)
25
+ thead time title tr track u ul var video wbr) +
26
+ # The SVG Tags
27
+ %w(circle clipPath defs ellipse g line linearGradient mask path pattern polygon polyline
28
+ radialGradient rect stop svg text tspan)
14
29
 
15
30
  # note: any tag can end in _as_node but this is deprecated
16
31
 
@@ -26,18 +41,42 @@ module React
26
41
 
27
42
  # define each predefined tag as an instance method
28
43
 
44
+
45
+
46
+
29
47
  HTML_TAGS.each do |tag|
30
48
  define_method(tag) do |*params, &children|
31
- React::RenderingContext.render(tag, *params, &children)
49
+ if tag == 'p'
50
+ if children || params.count == 0 || (params.count == 1 && params.first.is_a?(Hash))
51
+ React::RenderingContext.render(tag, *params, &children)
52
+ else
53
+ Kernel.p(*params)
54
+ end
55
+ else
56
+ React::RenderingContext.render(tag, *params, &children)
57
+ end
58
+ end
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)
32
66
  end
33
- alias_method tag.upcase, tag
34
- const_set tag.upcase, tag
35
67
  # handle deprecated _as_node style
36
68
  define_method("#{tag}_as_node") do |*params, &children|
37
69
  React::RenderingContext.build_only(tag, *params, &children)
38
70
  end
39
71
  end
40
72
 
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
+
41
80
  # use method_missing to look up component names in the form of "Foo(..)"
42
81
  # where there is no preceeding scope.
43
82
 
@@ -92,6 +131,8 @@ module React
92
131
 
93
132
  def lookup_const(name)
94
133
  return nil unless name =~ /^[A-Z]/
134
+ #html_tag = React::Component::Tags.html_tag_class(name)
135
+ #return html_tag if html_tag
95
136
  scopes = self.class.name.to_s.split('::').inject([Module]) do |nesting, next_const|
96
137
  nesting + [nesting.last.const_get(next_const)]
97
138
  end.reverse
@@ -36,7 +36,7 @@ module React
36
36
 
37
37
  def on(*event_names, &block)
38
38
  event_names.each { |event_name| merge_event_prop!(event_name, &block) }
39
- @native = `React.cloneElement(#{to_n}, #{properties.to_n})`
39
+ @native = `React.cloneElement(#{to_n}, #{properties.shallow_to_n})`
40
40
  self
41
41
  end
42
42
 
@@ -44,15 +44,14 @@ module React
44
44
  # Used for elements that are not yet in DOM, i.e. they are provided as children
45
45
  # or they have been explicitly removed from the rendering context using the delete method.
46
46
 
47
- def render(props = {})
47
+ def render(props = {}, &new_block)
48
48
  if props.empty?
49
49
  React::RenderingContext.render(self)
50
50
  else
51
+ props = API.convert_props(props)
51
52
  React::RenderingContext.render(
52
- Element.new(
53
- `React.cloneElement(#{to_n}, #{API.convert_props(props)})`,
54
- type, properties.merge(props), block
55
- )
53
+ Element.new(`React.cloneElement(#{to_n}, #{props.shallow_to_n})`,
54
+ type, properties.merge(props), block),
56
55
  )
57
56
  end
58
57
  end
@@ -78,20 +77,34 @@ module React
78
77
  # params may be provide to each class (but typically only to the last for easy reading.)
79
78
 
80
79
  def method_missing(class_name, args = {}, &new_block)
81
- class_name = class_name.gsub(/__|_/, '__' => '_', '_' => '-')
82
- new_props = properties.dup
83
- new_props[:class] = "\
84
- #{class_name} #{new_props[:class]} #{args.delete(:class)} #{args.delete(:className)}\
85
- ".split(' ').uniq.join(' ')
86
- new_props.merge! args
80
+ return dup.render.method_missing(class_name, args, &new_block) unless rendered?
87
81
  React::RenderingContext.replace(
88
82
  self,
89
- 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
90
86
  )
91
87
  end
92
88
 
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
+
93
97
  private
94
98
 
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
+
95
108
  # built in events, events going to native components, and events going to reactrb
96
109
 
97
110
  # built in events will have their event param translated to the Event wrapper