hyper-component 0.99.6 → 1.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -3
  3. data/Gemfile +4 -3
  4. data/Gemfile.lock +51 -36
  5. data/{misc/how-component-name-lookup-works.md → how-component-name-lookup-works.md} +1 -1
  6. data/hyper-component.gemspec +9 -8
  7. data/lib/hyper-component.rb +31 -43
  8. data/lib/hyperstack/component.rb +145 -0
  9. data/lib/hyperstack/component/auto-import.rb +44 -0
  10. data/lib/hyperstack/component/children.rb +40 -0
  11. data/lib/hyperstack/component/element.rb +129 -0
  12. data/lib/hyperstack/component/event.rb +78 -0
  13. data/lib/hyperstack/component/haml.rb +18 -0
  14. data/lib/hyperstack/component/isomorphic_helpers.rb +235 -0
  15. data/lib/hyperstack/component/jquery.rb +2 -0
  16. data/lib/hyperstack/component/native_library.rb +92 -0
  17. data/lib/hyperstack/component/react_api.rb +142 -0
  18. data/lib/hyperstack/component/server.rb +21 -0
  19. data/lib/hyperstack/component/version.rb +5 -0
  20. data/lib/hyperstack/ext/component/boolean.rb +14 -0
  21. data/lib/{react/ext/opal-jquery → hyperstack/ext/component}/element.rb +17 -12
  22. data/lib/{react/ext → hyperstack/ext/component}/hash.rb +0 -0
  23. data/lib/{react/to_key.rb → hyperstack/ext/component/number.rb} +0 -12
  24. data/lib/hyperstack/ext/component/object.rb +32 -0
  25. data/lib/{reactive-ruby → hyperstack/ext/component}/serializers.rb +0 -0
  26. data/lib/{react/ext → hyperstack/ext/component}/string.rb +0 -0
  27. data/lib/hyperstack/internal/component.rb +16 -0
  28. data/lib/hyperstack/internal/component/class_methods.rb +212 -0
  29. data/lib/hyperstack/internal/component/haml.rb +56 -0
  30. data/lib/hyperstack/internal/component/instance_methods.rb +92 -0
  31. data/lib/hyperstack/internal/component/props_wrapper.rb +125 -0
  32. data/lib/hyperstack/internal/component/rails.rb +11 -0
  33. data/lib/hyperstack/internal/component/rails/component_loader.rb +49 -0
  34. data/lib/hyperstack/internal/component/rails/component_mount.rb +52 -0
  35. data/lib/{reactive-ruby → hyperstack/internal/component}/rails/controller_helper.rb +0 -0
  36. data/lib/hyperstack/internal/component/rails/railtie.rb +24 -0
  37. data/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +52 -0
  38. data/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +52 -0
  39. data/lib/hyperstack/internal/component/react_wrapper.rb +308 -0
  40. data/lib/hyperstack/internal/component/rendering_context.rb +165 -0
  41. data/lib/hyperstack/internal/component/should_component_update.rb +101 -0
  42. data/lib/hyperstack/internal/component/tags.rb +109 -0
  43. data/lib/hyperstack/internal/component/top_level_rails_component.rb +83 -0
  44. data/lib/hyperstack/internal/component/validator.rb +149 -0
  45. data/lib/react/react-source.rb +2 -2
  46. data/unmounting-objects.md +78 -0
  47. metadata +73 -85
  48. data/DOCS.md +0 -1515
  49. data/LICENSE +0 -19
  50. data/README.md +0 -49
  51. data/lib/hyper-component/jquery.rb +0 -2
  52. data/lib/rails-helpers/top_level_rails_component.rb +0 -79
  53. data/lib/react/api.rb +0 -272
  54. data/lib/react/callbacks.rb +0 -42
  55. data/lib/react/children.rb +0 -38
  56. data/lib/react/component.rb +0 -189
  57. data/lib/react/component/api.rb +0 -70
  58. data/lib/react/component/base.rb +0 -13
  59. data/lib/react/component/class_methods.rb +0 -175
  60. data/lib/react/component/dsl_instance_methods.rb +0 -23
  61. data/lib/react/component/params.rb +0 -6
  62. data/lib/react/component/props_wrapper.rb +0 -90
  63. data/lib/react/component/should_component_update.rb +0 -99
  64. data/lib/react/component/tags.rb +0 -116
  65. data/lib/react/config.rb +0 -5
  66. data/lib/react/element.rb +0 -167
  67. data/lib/react/event.rb +0 -76
  68. data/lib/react/native_library.rb +0 -87
  69. data/lib/react/object.rb +0 -15
  70. data/lib/react/ref_callback.rb +0 -31
  71. data/lib/react/rendering_context.rb +0 -149
  72. data/lib/react/server.rb +0 -19
  73. data/lib/react/state_wrapper.rb +0 -23
  74. data/lib/react/test.rb +0 -16
  75. data/lib/react/test/dsl.rb +0 -17
  76. data/lib/react/test/matchers/render_html_matcher.rb +0 -56
  77. data/lib/react/test/rspec.rb +0 -15
  78. data/lib/react/test/session.rb +0 -37
  79. data/lib/react/test/utils.rb +0 -71
  80. data/lib/react/top_level.rb +0 -110
  81. data/lib/react/top_level_render.rb +0 -30
  82. data/lib/react/validator.rb +0 -132
  83. data/lib/reactive-ruby/component_loader.rb +0 -43
  84. data/lib/reactive-ruby/isomorphic_helpers.rb +0 -233
  85. data/lib/reactive-ruby/rails.rb +0 -8
  86. data/lib/reactive-ruby/rails/component_mount.rb +0 -48
  87. data/lib/reactive-ruby/rails/railtie.rb +0 -20
  88. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +0 -46
  89. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +0 -46
  90. data/lib/reactive-ruby/version.rb +0 -5
  91. data/lib/reactrb/auto-import.rb +0 -27
  92. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +0 -3
  93. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +0 -5
  94. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +0 -2
  95. data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +0 -6
  96. data/misc/generators/reactive_ruby/test_app/templates/script/rails +0 -5
  97. data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +0 -13
  98. data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +0 -11
  99. data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +0 -14
  100. data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  101. data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +0 -121
  102. data/misc/hyperloop-logo-small-pink.png +0 -0
  103. data/misc/logo1.png +0 -0
  104. data/misc/logo2.png +0 -0
  105. data/misc/logo3.png +0 -0
  106. data/path_release_steps.md +0 -9
@@ -0,0 +1,52 @@
1
+ require 'react/server_rendering/environment_container'
2
+ require 'react/server_rendering/manifest_container'
3
+ require 'react/server_rendering/webpacker_manifest_container'
4
+
5
+ module Hyperstack
6
+ module Internal
7
+ module Component
8
+ module Rails
9
+ module ServerRendering
10
+ class HyperTestAssetContainer
11
+ def find_asset(logical_path)
12
+ ::Rails.cache.read(logical_path)
13
+ end
14
+ end
15
+
16
+ class HyperAssetContainer
17
+ def initialize
18
+ @ass_containers = []
19
+ if assets_precompiled?
20
+ @ass_containers << React::ServerRendering::ManifestContainer.new if React::ServerRendering::ManifestContainer.compatible?
21
+ else
22
+ @ass_containers << React::ServerRendering::EnvironmentContainer.new if ::Rails.application.assets
23
+ end
24
+ if React::ServerRendering::WebpackerManifestContainer.compatible?
25
+ @ass_containers << React::ServerRendering::WebpackerManifestContainer.new
26
+ end
27
+ @ass_containers << HyperTestAssetContainer.new if ::Rails.env.test?
28
+ end
29
+
30
+ def find_asset(logical_path)
31
+ @ass_containers.each do |ass|
32
+ begin
33
+ asset = ass.find_asset(logical_path)
34
+ return asset if asset && asset != ''
35
+ rescue
36
+ next # no asset found, try the next container
37
+ end
38
+ end
39
+ raise "No asset found for #{logical_path}, tried: #{@ass_containers.map { |c| c.class.name }.join(', ')}"
40
+ end
41
+
42
+ private
43
+
44
+ def assets_precompiled?
45
+ !::Rails.application.config.assets.compile
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,308 @@
1
+ require 'hyperstack/component/native_library'
2
+
3
+ module Hyperstack
4
+ module Internal
5
+ module Component
6
+ # contains the name of all HTML tags, and the mechanism to register a component
7
+ # Provides the internal mechanisms to interface between reactrb and native components
8
+ # the code will attempt to create a js component wrapper on any rb class that has a
9
+ # render (or possibly _render_wrapper) method. The mapping between rb and js components
10
+ # is kept in the @@component_classes hash.
11
+
12
+ # Also provides the mechanism to build react elements
13
+
14
+ # TOOO - the code to deal with components should be moved to a module that will be included
15
+ # in a class which will then create the JS component for that class. That module will then
16
+ # be included in React::Component, but can be used by any class wanting to become a react
17
+ # component (but without other DSL characteristics.)
18
+ class ReactWrapper
19
+ @@component_classes = {}
20
+
21
+ def self.import_native_component(opal_class, native_class)
22
+ opal_class.instance_variable_set("@native_import", true)
23
+ @@component_classes[opal_class] = native_class
24
+ end
25
+
26
+ def self.eval_native_react_component(name)
27
+ component = `eval(name)`
28
+ raise "#{name} is not defined" if `#{component} === undefined`
29
+ is_component_class = `#{component}.prototype !== undefined` &&
30
+ (`!!#{component}.prototype.isReactComponent` ||
31
+ `!!#{component}.prototype.render`)
32
+ is_functional_component = `typeof #{component} === "function"`
33
+ has_render_method = `typeof #{component}.render === "function"`
34
+ unless is_component_class || is_functional_component || has_render_method
35
+ raise 'does not appear to be a native react component'
36
+ end
37
+ component
38
+ end
39
+
40
+ def self.native_react_component?(name = nil)
41
+ return false unless name
42
+ eval_native_react_component(name)
43
+ true
44
+ rescue
45
+ false
46
+ end
47
+
48
+ def self.add_after_error_hook(klass)
49
+ add_after_error_hook_to_native(@@component_classes[klass])
50
+ end
51
+
52
+ def self.add_after_error_hook_to_native(native_comp)
53
+ return unless native_comp
54
+ %x{
55
+ native_comp.prototype.componentDidCatch = function(error, info) {
56
+ this.__opalInstanceSyncSetState = false;
57
+ this.__opalInstance.$component_did_catch(error, Opal.Hash.$new(info));
58
+ }
59
+ }
60
+ end
61
+
62
+ def self.create_native_react_class(type)
63
+ raise "Provided class should define `render` method" if !(type.method_defined? :render)
64
+ render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
65
+ # this was hashing type.to_s, not sure why but .to_s does not work as it Foo::Bar::View.to_s just returns "View"
66
+
67
+ @@component_classes[type] ||= begin
68
+ comp = %x{
69
+ class extends React.Component {
70
+ constructor(props) {
71
+ super(props);
72
+ this.mixins = #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`};
73
+ this.statics = #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`};
74
+ this.state = {};
75
+ this.__opalInstanceInitializedState = false;
76
+ this.__opalInstanceSyncSetState = true;
77
+ this.__opalInstance = #{type.new(`this`)};
78
+ this.__opalInstanceInitializedState = true;
79
+ this.__opalInstanceSyncSetState = false;
80
+ this.__name = #{type.name};
81
+ }
82
+ static get displayName() {
83
+ if (typeof this.__name != "undefined") {
84
+ return this.__name;
85
+ } else {
86
+ return #{type.name};
87
+ }
88
+ }
89
+ static set displayName(name) {
90
+ this.__name = name;
91
+ }
92
+ static get defaultProps() {
93
+ return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
94
+ }
95
+ static get propTypes() {
96
+ return #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`};
97
+ }
98
+ componentWillMount() {
99
+ if (#{type.method_defined? :component_will_mount}) {
100
+ this.__opalInstanceSyncSetState = true;
101
+ this.__opalInstance.$component_will_mount();
102
+ this.__opalInstanceSyncSetState = false;
103
+ }
104
+ }
105
+ componentDidMount() {
106
+ this.__opalInstance.__hyperstack_component_is_mounted = true
107
+ if (#{type.method_defined? :component_did_mount}) {
108
+ this.__opalInstanceSyncSetState = false;
109
+ this.__opalInstance.$component_did_mount();
110
+ }
111
+ }
112
+ componentWillReceiveProps(next_props) {
113
+ if (#{type.method_defined? :component_will_receive_props}) {
114
+ this.__opalInstanceSyncSetState = true;
115
+ this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props));
116
+ this.__opalInstanceSyncSetState = false;
117
+ }
118
+ }
119
+ shouldComponentUpdate(next_props, next_state) {
120
+ if (#{type.method_defined? :should_component_update?}) {
121
+ this.__opalInstanceSyncSetState = false;
122
+ return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
123
+ } else { return true; }
124
+ }
125
+ componentWillUpdate(next_props, next_state) {
126
+ if (#{type.method_defined? :component_will_update}) {
127
+ this.__opalInstanceSyncSetState = false;
128
+ this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
129
+ }
130
+ }
131
+ componentDidUpdate(prev_props, prev_state) {
132
+ if (#{type.method_defined? :component_did_update}) {
133
+ this.__opalInstanceSyncSetState = false;
134
+ this.__opalInstance.$component_did_update(Opal.Hash.$new(prev_props), Opal.Hash.$new(prev_state));
135
+ }
136
+ }
137
+ componentWillUnmount() {
138
+ if (#{type.method_defined? :component_will_unmount}) {
139
+ this.__opalInstanceSyncSetState = false;
140
+ this.__opalInstance.$component_will_unmount();
141
+ }
142
+ this.__opalInstance.__hyperstack_component_is_mounted = false;
143
+ }
144
+
145
+ render() {
146
+ this.__opalInstanceSyncSetState = false;
147
+ return this.__opalInstance.$send(render_fn).$to_n();
148
+ }
149
+ }
150
+ }
151
+ # check to see if there is an after_error callback. If there is add a
152
+ # componentDidCatch handler. Because legacy behavior is to allow any object
153
+ # that responds to render to act as a component we have to make sure that
154
+ # we have a callbacks_for method. This all becomes much easier once issue
155
+ # #270 is resolved.
156
+ if type.respond_to?(:callbacks_for) && type.callbacks_for(:after_error) != []
157
+ add_after_error_hook_to_native comp
158
+ end
159
+ comp
160
+ end
161
+ end
162
+
163
+ def self.create_element(type, *args, &block)
164
+ params = []
165
+
166
+ # Component Spec, Normal DOM, String or Native Component
167
+ ncc = @@component_classes[type]
168
+ if ncc
169
+ params << ncc
170
+ elsif type.is_a?(Class)
171
+ params << create_native_react_class(type)
172
+ elsif block_given? || Tags::HTML_TAGS.include?(type)
173
+ params << type
174
+ elsif type.is_a?(String)
175
+ return Hyperstack::Component::Element.new(type)
176
+ else
177
+ raise "#{type} not implemented"
178
+ end
179
+
180
+ # Convert Passed in properties
181
+ properties = convert_props(args)
182
+ params << properties.shallow_to_n
183
+
184
+ # Children Nodes
185
+ if block
186
+ a = [block.call].flatten
187
+ %x{
188
+ for(var i=0, l=a.length; i<l; i++) {
189
+ params.push(a[i].$to_n());
190
+ }
191
+ }
192
+ end
193
+ Hyperstack::Component::Element.new(`React.createElement.apply(null, #{params})`, type, properties, block)
194
+ end
195
+
196
+ def self.clear_component_class_cache
197
+ @@component_classes = {}
198
+ end
199
+
200
+ def self.convert_props(args)
201
+ # merge args together into a single properties hash
202
+ properties = {}
203
+ args.each do |arg|
204
+ if arg.is_a? String
205
+ properties[arg] = true
206
+ elsif arg.is_a? Hash
207
+ arg.each do |key, value|
208
+ if ['class', 'className', 'class_name'].include? key
209
+ next unless value
210
+
211
+ if value.is_a?(String)
212
+ value = value.split(' ')
213
+ elsif !value.is_a?(Array)
214
+ raise "The class param must be a string or array of strings"
215
+ end
216
+
217
+ properties['className'] = [*properties['className'], *value]
218
+ elsif key == 'style'
219
+ next unless value
220
+
221
+ if !value.is_a?(Hash)
222
+ raise "The style param must be a Hash"
223
+ end
224
+
225
+ properties['style'] = (properties['style'] || {}).merge(value)
226
+ elsif Hyperstack::Component::ReactAPI::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
227
+ properties[key] = (properties[key] || {}).merge(value)
228
+ else
229
+ properties[key] = value
230
+ end
231
+ end
232
+ end
233
+ end
234
+ # process properties according to react rules
235
+ props = {}
236
+ properties.each do |key, value|
237
+ if ["style", "dangerously_set_inner_HTML"].include? key
238
+ props[lower_camelize(key)] = value.to_n
239
+
240
+ elsif key == "className"
241
+ props[key] = value.join(' ')
242
+
243
+ elsif key == "key"
244
+ props["key"] = value.to_key
245
+
246
+ elsif key == 'ref'
247
+ unless value.respond_to?(:call)
248
+ raise "The ref and dom params must be given a Proc.\n"\
249
+ "If you want to capture the ref in an instance variable use the `set` method.\n"\
250
+ "For example `ref: set(:TheRef)` will capture assign the ref to `@TheRef`\n"
251
+ end
252
+ props[key] = %x{
253
+ function(dom_node){
254
+ if (dom_node !== null && dom_node.__opalInstance !== undefined && dom_node.__opalInstance !== null) {
255
+ #{ Hyperstack::Internal::State::Mapper.ignore_mutations { value.call(`dom_node.__opalInstance`) } };
256
+ } else if(dom_node !== null && ReactDOM.findDOMNode !== undefined && dom_node.nodeType === undefined) {
257
+ #{ Hyperstack::Internal::State::Mapper.ignore_mutations { value.call(`ReactDOM.findDOMNode(dom_node)`) } };
258
+ } else if(dom_node !== null){
259
+ #{ Hyperstack::Internal::State::Mapper.ignore_mutations { value.call(`dom_node`) } };
260
+ }
261
+ }
262
+ }
263
+
264
+ elsif key == 'dom'
265
+ unless value.respond_to?(:call)
266
+ raise "The ref and dom params must be given a Proc.\n"\
267
+ "If you want to capture the dom node in an instance variable use the `set` method.\n"\
268
+ "For example `dom: set(:DomNode)` will assign the dom node to `@DomNode`\n"
269
+ end
270
+ unless Module.const_defined? 'Element'
271
+ raise "You must include 'hyperstack/component/jquery' "\
272
+ "in your manifest to use the `dom` reference key.\n"\
273
+ 'For example if using rails include '\
274
+ "`config.import 'hyperstack/component/jquery', client_only: true`"\
275
+ 'in your config/initializer/hyperstack.rb file'
276
+ end
277
+ props[:ref] = %x{
278
+ function(dom_node){
279
+ if (dom_node !== null && dom_node.__opalInstance !== undefined && dom_node.__opalInstance !== null) {
280
+ #{ Hyperstack::Internal::State::Mapper.ignore_mutations { value.call(::Element[`dom_node.__opalInstance`]) } };
281
+ } else if(dom_node !== null && ReactDOM.findDOMNode !== undefined && dom_node.nodeType === undefined) {
282
+ #{ Hyperstack::Internal::State::Mapper.ignore_mutations { value.call(::Element[`ReactDOM.findDOMNode(dom_node)`]) } };
283
+ } else if(dom_node !== null) {
284
+ #{ Hyperstack::Internal::State::Mapper.ignore_mutations { value.call(::Element[`dom_node`]) } };
285
+ }
286
+ }
287
+ }
288
+
289
+ elsif Hyperstack::Component::ReactAPI::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
290
+ value.each { |k, v| props["#{key}-#{k.gsub(/__|_/, '__' => '_', '_' => '-')}"] = v.to_n }
291
+ else
292
+ props[Hyperstack::Component::ReactAPI.html_attr?(lower_camelize(key)) ? lower_camelize(key) : key] = value
293
+ end
294
+ end
295
+ props
296
+ end
297
+
298
+ private
299
+
300
+ def self.lower_camelize(snake_cased_word)
301
+ words = snake_cased_word.split('_')
302
+ result = [words.first]
303
+ result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] }).join('')
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,165 @@
1
+ module Hyperstack
2
+ module Internal
3
+ module Component
4
+ class RenderingContext
5
+ class << self
6
+ attr_accessor :waiting_on_resources
7
+
8
+ def render(name, *args, &block)
9
+ was_outer_most = !@not_outer_most
10
+ @not_outer_most = true
11
+ remove_nodes_from_args(args)
12
+ @buffer ||= [] unless @buffer
13
+ if block
14
+ element = build do
15
+ saved_waiting_on_resources = waiting_on_resources
16
+ self.waiting_on_resources = nil
17
+ run_child_block(name.nil?, &block)
18
+ if name
19
+ buffer = @buffer.dup
20
+ ReactWrapper.create_element(name, *args) { buffer }.tap do |element|
21
+ element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
22
+ element.waiting_on_resources ||= waiting_on_resources if buffer.last.is_a?(String)
23
+ end
24
+ elsif @buffer.last.is_a? Hyperstack::Component::Element
25
+ @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
26
+ else
27
+ buffer_s = @buffer.last.to_s
28
+ RenderingContext.render(:span) { buffer_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
29
+ end
30
+ end
31
+ elsif name.is_a? Hyperstack::Component::Element
32
+ element = name
33
+ else
34
+ element = ReactWrapper.create_element(name, *args)
35
+ element.waiting_on_resources = waiting_on_resources
36
+ end
37
+ @buffer << element
38
+ self.waiting_on_resources = nil
39
+ element
40
+ ensure
41
+ @not_outer_most = @buffer = nil if was_outer_most
42
+ end
43
+
44
+ def build
45
+ current = @buffer
46
+ @buffer = []
47
+ return_val = yield @buffer
48
+ @buffer = current
49
+ return_val
50
+ end
51
+
52
+ def delete(element)
53
+ @buffer.delete(element)
54
+ element
55
+ end
56
+ alias as_node delete
57
+
58
+ def rendered?(element)
59
+ @buffer.include? element
60
+ end
61
+
62
+ def replace(e1, e2)
63
+ @buffer[@buffer.index(e1)] = e2
64
+ end
65
+
66
+ def remove_nodes_from_args(args)
67
+ args[0].each do |key, value|
68
+ begin
69
+ value.delete if value.is_a?(Hyperstack::Component::Element) # deletes Element from buffer
70
+ rescue Exception
71
+ end
72
+ end if args[0] && args[0].is_a?(Hash)
73
+ end
74
+
75
+ # run_child_block gathers the element(s) generated by a child block.
76
+ # for example when rendering this div: div { "hello".span; "goodby".span }
77
+ # two child Elements will be generated.
78
+ #
79
+ # the final value of the block should either be
80
+ # 1 an object that responds to :acts_as_string?
81
+ # 2 a string,
82
+ # 3 an element that is NOT yet pushed on the rendering buffer
83
+ # 4 or the last element pushed on the buffer
84
+ #
85
+ # in case 1 we render a span
86
+ # in case 2 we automatically push the string onto the buffer
87
+ # in case 3 we also push the Element onto the buffer IF the buffer is empty
88
+ # case 4 requires no special processing
89
+ #
90
+ # Once we have taken care of these special cases we do a check IF we are in an
91
+ # outer rendering scope. In this case react only allows us to generate 1 Element
92
+ # so we insure that is the case, and also check to make sure that element in the buffer
93
+ # is the element returned
94
+
95
+ def run_child_block(is_outer_scope)
96
+ result = yield
97
+ if result.respond_to?(:acts_as_string?) && result.acts_as_string?
98
+ # hyper-mesh DummyValues respond to acts_as_string, and must
99
+ # be converted to spans INSIDE the parent, otherwise the waiting_on_resources
100
+ # flag will get set in the wrong context
101
+ RenderingContext.render(:span) { result.to_s }
102
+ elsif result.is_a?(String) || (result.is_a?(Hyperstack::Component::Element) && @buffer.empty?)
103
+ @buffer << result
104
+ end
105
+ raise_render_error(result) if is_outer_scope && @buffer != [result]
106
+ end
107
+
108
+ # heurestically raise a meaningful error based on the situation
109
+
110
+ def raise_render_error(result)
111
+ improper_render 'A different element was returned than was generated within the DSL.',
112
+ 'Possibly improper use of Element#delete.' if @buffer.count == 1
113
+ improper_render "Instead #{@buffer.count} elements were generated.",
114
+ 'Do you want to wrap your elements in a div?' if @buffer.count > 1
115
+ improper_render "Instead the component #{result} was returned.",
116
+ "Did you mean #{result}()?" if result.try :hyper_component?
117
+ improper_render "Instead the #{result.class} #{result} was returned.",
118
+ 'You may need to convert this to a string.'
119
+ end
120
+
121
+ def improper_render(message, solution)
122
+ raise "a component's render method must generate and return exactly 1 element or a string.\n"\
123
+ " #{message} #{solution}"
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ class Object
132
+ [:span, :td, :th, :while_loading].each do |tag|
133
+ define_method(tag) do |*args, &block|
134
+ args.unshift(tag)
135
+ # legacy hyperloop allowed tags to be lower case as well so if self is a component
136
+ # then this is just a DSL method for example:
137
+ # render(:div) do
138
+ # span { 'foo' }
139
+ # end
140
+ # in this case self is just the component being rendered, so span is just a method
141
+ # in the component.
142
+ # If we fully deprecate lowercase tags, then this next line can go...
143
+ return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component?
144
+ Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s }
145
+ end
146
+ end
147
+
148
+
149
+ def para(*args, &block)
150
+ args.unshift(:p)
151
+ # see above comment
152
+ return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component?
153
+ Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s }
154
+ end
155
+
156
+ def br
157
+ # see above comment
158
+ return send(:br) if respond_to?(:hyper_component?) && hyper_component?
159
+ Hyperstack::Internal::Component::RenderingContext.render(:span) do
160
+ Hyperstack::Internal::Component::RenderingContext.render(to_s)
161
+ Hyperstack::Internal::Component::RenderingContext.render(:br)
162
+ end
163
+ end
164
+
165
+ end