hyper-component 0.99.6 → 1.0.alpha1

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