hyper-component 1.0.alpha1.1 → 1.0.alpha1.6
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.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/Gemfile +5 -0
- data/hyper-component.gemspec +6 -9
- data/lib/hyper-component.rb +10 -1
- data/lib/hyperstack/component.rb +78 -10
- data/lib/hyperstack/component/children.rb +1 -1
- data/lib/hyperstack/component/element.rb +93 -21
- data/lib/hyperstack/component/event.rb +1 -1
- data/lib/hyperstack/component/free_render.rb +21 -0
- data/lib/hyperstack/component/isomorphic_helpers.rb +13 -8
- data/lib/hyperstack/component/version.rb +1 -1
- data/lib/hyperstack/component/while_loading.rb +27 -0
- data/lib/hyperstack/ext/component/array.rb +20 -0
- data/lib/hyperstack/ext/component/element.rb +22 -8
- data/lib/hyperstack/ext/component/enumerable.rb +18 -0
- data/lib/hyperstack/ext/component/kernel.rb +7 -0
- data/lib/hyperstack/ext/component/string.rb +1 -1
- data/lib/hyperstack/internal/component.rb +7 -1
- data/lib/hyperstack/internal/component/class_methods.rb +84 -8
- data/lib/hyperstack/internal/component/haml.rb +3 -12
- data/lib/hyperstack/internal/component/instance_methods.rb +38 -5
- data/lib/hyperstack/internal/component/props_wrapper.rb +29 -9
- data/lib/hyperstack/internal/component/rails/component_mount.rb +12 -0
- data/lib/hyperstack/internal/component/rails/controller_helper.rb +20 -1
- data/lib/hyperstack/internal/component/rails/railtie.rb +3 -2
- data/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +5 -1
- data/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +4 -2
- data/lib/hyperstack/internal/component/react_wrapper.rb +79 -62
- data/lib/hyperstack/internal/component/rendering_context.rb +95 -45
- data/lib/hyperstack/internal/component/rescue_wrapper.rb +40 -0
- data/lib/hyperstack/internal/component/should_component_update.rb +1 -1
- data/lib/hyperstack/internal/component/tags.rb +12 -3
- data/lib/hyperstack/internal/component/top_level_rails_component.rb +4 -0
- data/lib/hyperstack/internal/component/while_loading_wrapper.rb +29 -0
- data/lib/react/react-source.rb +2 -2
- metadata +50 -81
- data/Gemfile.lock +0 -363
@@ -43,6 +43,9 @@ module Hyperstack
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def footers
|
46
|
+
return if @hyperstack_footers_rendered
|
47
|
+
|
48
|
+
@hyperstack_footers_rendered = true
|
46
49
|
Hyperstack::Component::IsomorphicHelpers.prerender_footers(controller)
|
47
50
|
end
|
48
51
|
end
|
@@ -50,3 +53,12 @@ module Hyperstack
|
|
50
53
|
end
|
51
54
|
end
|
52
55
|
end
|
56
|
+
|
57
|
+
|
58
|
+
module React
|
59
|
+
module Rails
|
60
|
+
module ViewHelper
|
61
|
+
alias mount_component react_component
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,5 +1,19 @@
|
|
1
1
|
require 'action_controller'
|
2
2
|
|
3
|
+
module Hyperstack
|
4
|
+
module Internal
|
5
|
+
module Component
|
6
|
+
class Redirect < StandardError
|
7
|
+
attr_reader :url
|
8
|
+
def initialize(url)
|
9
|
+
@url = url
|
10
|
+
super("redirect to #{url}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
3
17
|
module ActionController
|
4
18
|
# adds render_component helper to ActionControllers
|
5
19
|
class Base
|
@@ -7,8 +21,13 @@ module ActionController
|
|
7
21
|
@component_name = (args[0].is_a? Hash) || args.empty? ? params[:action].camelize : args.shift
|
8
22
|
@render_params = args.shift || {}
|
9
23
|
options = args[0] || {}
|
10
|
-
|
24
|
+
return if performed?
|
25
|
+
render inline: '<%= mount_component @component_name, @render_params %>',
|
11
26
|
layout: options.key?(:layout) ? options[:layout].to_s : :default
|
27
|
+
rescue Exception => e
|
28
|
+
m = /^RuntimeError: Hyperstack::Internal::Component::Redirect (.+) status: (.+)$/.match(e.message)
|
29
|
+
raise e unless m
|
30
|
+
redirect_to m[1], status: m[2]
|
12
31
|
end
|
13
32
|
end
|
14
33
|
end
|
@@ -12,8 +12,9 @@ module Hyperstack
|
|
12
12
|
end
|
13
13
|
config.after_initialize do
|
14
14
|
class ::HyperstackController < ::ApplicationController
|
15
|
-
def action_missing(
|
16
|
-
|
15
|
+
def action_missing(*)
|
16
|
+
parts = params[:action].split('__')
|
17
|
+
render_component (parts[0..-2]+[parts.last.camelize]).join('::')
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -13,7 +13,11 @@ module Hyperstack
|
|
13
13
|
|
14
14
|
class ContextualRenderer < React::ServerRendering::BundleRenderer
|
15
15
|
def initialize(options = {})
|
16
|
-
|
16
|
+
unless v8_runtime?
|
17
|
+
raise "Hyperstack prerendering only works with MiniRacer. Add 'mini_racer' to your Gemfile"
|
18
|
+
end
|
19
|
+
|
20
|
+
super({ files: ['hyperstack-prerender-loader.js'] }.merge(options))
|
17
21
|
ComponentLoader.new(v8_context).load
|
18
22
|
end
|
19
23
|
|
@@ -9,7 +9,9 @@ module Hyperstack
|
|
9
9
|
module ServerRendering
|
10
10
|
class HyperTestAssetContainer
|
11
11
|
def find_asset(logical_path)
|
12
|
-
|
12
|
+
# we skip the container if it raises an error so we
|
13
|
+
# don't care if we are running under hyperspec or not
|
14
|
+
HyperSpec::Internal::Controller.cache_read(logical_path)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -24,7 +26,7 @@ module Hyperstack
|
|
24
26
|
if React::ServerRendering::WebpackerManifestContainer.compatible?
|
25
27
|
@ass_containers << React::ServerRendering::WebpackerManifestContainer.new
|
26
28
|
end
|
27
|
-
@ass_containers << HyperTestAssetContainer.new
|
29
|
+
@ass_containers << HyperTestAssetContainer.new
|
28
30
|
end
|
29
31
|
|
30
32
|
def find_asset(logical_path)
|
@@ -18,6 +18,10 @@ module Hyperstack
|
|
18
18
|
class ReactWrapper
|
19
19
|
@@component_classes = {}
|
20
20
|
|
21
|
+
def self.stateless?(ncc)
|
22
|
+
`typeof #{ncc} === 'symbol' || (typeof #{ncc} === 'function' && !(#{ncc}.prototype && #{ncc}.prototype.isReactComponent))`
|
23
|
+
end
|
24
|
+
|
21
25
|
def self.import_native_component(opal_class, native_class)
|
22
26
|
opal_class.instance_variable_set("@native_import", true)
|
23
27
|
@@component_classes[opal_class] = native_class
|
@@ -26,12 +30,13 @@ module Hyperstack
|
|
26
30
|
def self.eval_native_react_component(name)
|
27
31
|
component = `eval(name)`
|
28
32
|
raise "#{name} is not defined" if `#{component} === undefined`
|
33
|
+
|
34
|
+
component = `component.default` if `component.__esModule`
|
29
35
|
is_component_class = `#{component}.prototype !== undefined` &&
|
30
36
|
(`!!#{component}.prototype.isReactComponent` ||
|
31
37
|
`!!#{component}.prototype.render`)
|
32
|
-
is_functional_component = `typeof #{component} === "function"`
|
33
38
|
has_render_method = `typeof #{component}.render === "function"`
|
34
|
-
unless is_component_class ||
|
39
|
+
unless is_component_class || stateless?(component) || has_render_method
|
35
40
|
raise 'does not appear to be a native react component'
|
36
41
|
end
|
37
42
|
component
|
@@ -39,9 +44,11 @@ module Hyperstack
|
|
39
44
|
|
40
45
|
def self.native_react_component?(name = nil)
|
41
46
|
return false unless name
|
47
|
+
|
42
48
|
eval_native_react_component(name)
|
43
49
|
true
|
44
|
-
|
50
|
+
# Exception to be compatible with all versions of opal
|
51
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
45
52
|
false
|
46
53
|
end
|
47
54
|
|
@@ -60,100 +67,88 @@ module Hyperstack
|
|
60
67
|
end
|
61
68
|
|
62
69
|
def self.create_native_react_class(type)
|
70
|
+
raise "createReactClass is undefined. Add the 'react-create-class' npm module, and import it as 'createReactClass'" if `typeof(createReactClass)=='undefined'`
|
63
71
|
raise "Provided class should define `render` method" if !(type.method_defined? :render)
|
64
|
-
|
72
|
+
old_school = !type.method_defined?(:_render_wrapper)
|
73
|
+
render_fn = old_school ? :render : :_render_wrapper
|
65
74
|
# 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
75
|
@@component_classes[type] ||= begin
|
68
76
|
comp = %x{
|
69
|
-
|
70
|
-
|
71
|
-
super(props);
|
77
|
+
createReactClass({
|
78
|
+
getInitialState: function() {
|
72
79
|
this.mixins = #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`};
|
73
80
|
this.statics = #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`};
|
74
|
-
this.state = {};
|
75
81
|
this.__opalInstanceInitializedState = false;
|
76
82
|
this.__opalInstanceSyncSetState = true;
|
77
83
|
this.__opalInstance = #{type.new(`this`)};
|
78
84
|
this.__opalInstanceInitializedState = true;
|
79
85
|
this.__opalInstanceSyncSetState = false;
|
80
86
|
this.__name = #{type.name};
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
} else {
|
86
|
-
return #{type.name};
|
87
|
-
}
|
88
|
-
}
|
89
|
-
static set displayName(name) {
|
90
|
-
this.__name = name;
|
91
|
-
}
|
92
|
-
static get defaultProps() {
|
87
|
+
return {}
|
88
|
+
},
|
89
|
+
displayName: #{type.name},
|
90
|
+
getDefaultProps: function() {
|
93
91
|
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
|
94
|
-
}
|
95
|
-
|
96
|
-
|
97
|
-
}
|
98
|
-
componentWillMount() {
|
92
|
+
},
|
93
|
+
propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
|
94
|
+
componentWillMount: old_school && function() {
|
99
95
|
if (#{type.method_defined? :component_will_mount}) {
|
100
96
|
this.__opalInstanceSyncSetState = true;
|
101
97
|
this.__opalInstance.$component_will_mount();
|
102
98
|
this.__opalInstanceSyncSetState = false;
|
103
99
|
}
|
104
|
-
}
|
105
|
-
componentDidMount() {
|
100
|
+
},
|
101
|
+
componentDidMount: function() {
|
106
102
|
this.__opalInstance.__hyperstack_component_is_mounted = true
|
107
103
|
if (#{type.method_defined? :component_did_mount}) {
|
108
104
|
this.__opalInstanceSyncSetState = false;
|
109
105
|
this.__opalInstance.$component_did_mount();
|
110
106
|
}
|
111
|
-
}
|
112
|
-
|
107
|
+
},
|
108
|
+
UNSAFE_componentWillReceiveProps: function(next_props) {
|
113
109
|
if (#{type.method_defined? :component_will_receive_props}) {
|
114
110
|
this.__opalInstanceSyncSetState = true;
|
115
111
|
this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props));
|
116
112
|
this.__opalInstanceSyncSetState = false;
|
117
113
|
}
|
118
|
-
}
|
119
|
-
shouldComponentUpdate(next_props, next_state) {
|
114
|
+
},
|
115
|
+
shouldComponentUpdate: function(next_props, next_state) {
|
120
116
|
if (#{type.method_defined? :should_component_update?}) {
|
121
117
|
this.__opalInstanceSyncSetState = false;
|
122
118
|
return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
|
123
119
|
} else { return true; }
|
124
|
-
}
|
125
|
-
|
120
|
+
},
|
121
|
+
UNSAFE_componentWillUpdate: function(next_props, next_state) {
|
126
122
|
if (#{type.method_defined? :component_will_update}) {
|
127
123
|
this.__opalInstanceSyncSetState = false;
|
128
124
|
this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
|
129
125
|
}
|
130
|
-
}
|
131
|
-
componentDidUpdate(prev_props, prev_state) {
|
126
|
+
},
|
127
|
+
componentDidUpdate: function(prev_props, prev_state) {
|
132
128
|
if (#{type.method_defined? :component_did_update}) {
|
133
129
|
this.__opalInstanceSyncSetState = false;
|
134
130
|
this.__opalInstance.$component_did_update(Opal.Hash.$new(prev_props), Opal.Hash.$new(prev_state));
|
135
131
|
}
|
136
|
-
}
|
137
|
-
componentWillUnmount() {
|
132
|
+
},
|
133
|
+
componentWillUnmount: function() {
|
138
134
|
if (#{type.method_defined? :component_will_unmount}) {
|
139
135
|
this.__opalInstanceSyncSetState = false;
|
140
136
|
this.__opalInstance.$component_will_unmount();
|
141
137
|
}
|
142
138
|
this.__opalInstance.__hyperstack_component_is_mounted = false;
|
143
|
-
}
|
144
|
-
|
145
|
-
render() {
|
139
|
+
},
|
140
|
+
render: function() {
|
146
141
|
this.__opalInstanceSyncSetState = false;
|
147
142
|
return this.__opalInstance.$send(render_fn).$to_n();
|
148
143
|
}
|
149
|
-
}
|
144
|
+
})
|
150
145
|
}
|
151
146
|
# check to see if there is an after_error callback. If there is add a
|
152
147
|
# componentDidCatch handler. Because legacy behavior is to allow any object
|
153
148
|
# that responds to render to act as a component we have to make sure that
|
154
149
|
# we have a callbacks_for method. This all becomes much easier once issue
|
155
150
|
# #270 is resolved.
|
156
|
-
if type.respond_to?(:
|
151
|
+
if type.respond_to?(:callbacks?) && type.callbacks?(:after_error)
|
157
152
|
add_after_error_hook_to_native comp
|
158
153
|
end
|
159
154
|
comp
|
@@ -178,7 +173,9 @@ module Hyperstack
|
|
178
173
|
end
|
179
174
|
|
180
175
|
# Convert Passed in properties
|
181
|
-
|
176
|
+
ele = nil # create nil var for the ref to use
|
177
|
+
ref = ->(ref) { ele._update_ref(ref) } unless stateless?(ncc)
|
178
|
+
properties = convert_props(type, { ref: ref }, *args)
|
182
179
|
params << properties.shallow_to_n
|
183
180
|
|
184
181
|
# Children Nodes
|
@@ -190,14 +187,17 @@ module Hyperstack
|
|
190
187
|
}
|
191
188
|
}
|
192
189
|
end
|
193
|
-
|
190
|
+
# assign to ele so that the ref callback can use it
|
191
|
+
ele = Hyperstack::Component::Element.new(
|
192
|
+
`React.createElement.apply(null, #{params})`, type, properties, block
|
193
|
+
)
|
194
194
|
end
|
195
195
|
|
196
196
|
def self.clear_component_class_cache
|
197
197
|
@@component_classes = {}
|
198
198
|
end
|
199
199
|
|
200
|
-
def self.convert_props(args)
|
200
|
+
def self.convert_props(type, *args)
|
201
201
|
# merge args together into a single properties hash
|
202
202
|
properties = {}
|
203
203
|
args.each do |arg|
|
@@ -234,34 +234,51 @@ module Hyperstack
|
|
234
234
|
# process properties according to react rules
|
235
235
|
props = {}
|
236
236
|
properties.each do |key, value|
|
237
|
-
if [
|
237
|
+
if %w[style dangerously_set_inner_HTML].include? key
|
238
238
|
props[lower_camelize(key)] = value.to_n
|
239
239
|
|
240
|
-
elsif key ==
|
240
|
+
elsif key == :className
|
241
241
|
props[key] = value.join(' ')
|
242
242
|
|
243
|
-
elsif key ==
|
244
|
-
props[
|
243
|
+
elsif key == :key
|
244
|
+
props[:key] = value.to_key
|
245
|
+
|
246
|
+
elsif key == :init
|
247
|
+
if %w[select textarea].include? type
|
248
|
+
key = :defaultValue
|
249
|
+
elsif type == :input
|
250
|
+
key = if %w[radio checkbox].include? properties[:type]
|
251
|
+
:defaultChecked
|
252
|
+
else
|
253
|
+
:defaultValue
|
254
|
+
end
|
255
|
+
end
|
256
|
+
props[key] = value
|
245
257
|
|
246
258
|
elsif key == 'ref'
|
259
|
+
next unless value
|
247
260
|
unless value.respond_to?(:call)
|
248
261
|
raise "The ref and dom params must be given a Proc.\n"\
|
249
262
|
"If you want to capture the ref in an instance variable use the `set` method.\n"\
|
250
263
|
"For example `ref: set(:TheRef)` will capture assign the ref to `@TheRef`\n"
|
251
264
|
end
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
}
|
265
|
+
unless `value.__hyperstack_component_ref_is_already_wrapped`
|
266
|
+
fn = value
|
267
|
+
value = %x{
|
268
|
+
function(dom_node){
|
269
|
+
if (dom_node !== null && dom_node.__opalInstance !== undefined && dom_node.__opalInstance !== null) {
|
270
|
+
#{ Hyperstack::Internal::State::Mapper.ignore_mutations { fn.call(`dom_node.__opalInstance`) } };
|
271
|
+
} else if(dom_node !== null && ReactDOM.findDOMNode !== undefined && dom_node.nodeType === undefined) {
|
272
|
+
#{ Hyperstack::Internal::State::Mapper.ignore_mutations { fn.call(`ReactDOM.findDOMNode(dom_node)`) } };
|
273
|
+
} else if(dom_node !== null){
|
274
|
+
#{ Hyperstack::Internal::State::Mapper.ignore_mutations { fn.call(`dom_node`) } };
|
262
275
|
}
|
263
|
-
|
264
|
-
|
276
|
+
}
|
277
|
+
}
|
278
|
+
`value.__hyperstack_component_ref_is_already_wrapped = true`
|
279
|
+
end
|
280
|
+
props[key] = value
|
281
|
+
elsif key == 'jq_ref'
|
265
282
|
unless value.respond_to?(:call)
|
266
283
|
raise "The ref and dom params must be given a Proc.\n"\
|
267
284
|
"If you want to capture the dom node in an instance variable use the `set` method.\n"\
|
@@ -2,30 +2,58 @@ module Hyperstack
|
|
2
2
|
module Internal
|
3
3
|
module Component
|
4
4
|
class RenderingContext
|
5
|
+
class NotQuiet < Exception; end
|
5
6
|
class << self
|
6
7
|
attr_accessor :waiting_on_resources
|
7
8
|
|
9
|
+
def raise_if_not_quiet?
|
10
|
+
@raise_if_not_quiet
|
11
|
+
end
|
12
|
+
|
13
|
+
def raise_if_not_quiet=(x)
|
14
|
+
@raise_if_not_quiet = x
|
15
|
+
end
|
16
|
+
|
17
|
+
def quiet_test(component)
|
18
|
+
return unless component.waiting_on_resources && raise_if_not_quiet? #&& component.class != RescueMetaWrapper <- WHY can't create a spec that this fails without this, but several fail with it.
|
19
|
+
raise NotQuiet.new("#{component} is waiting on resources")
|
20
|
+
end
|
21
|
+
|
22
|
+
def render_string(string)
|
23
|
+
@buffer ||= []
|
24
|
+
@buffer << string
|
25
|
+
end
|
26
|
+
|
8
27
|
def render(name, *args, &block)
|
9
28
|
was_outer_most = !@not_outer_most
|
10
29
|
@not_outer_most = true
|
11
30
|
remove_nodes_from_args(args)
|
12
|
-
@buffer ||= [] unless @buffer
|
31
|
+
@buffer ||= [] #unless @buffer
|
13
32
|
if block
|
14
33
|
element = build do
|
15
|
-
saved_waiting_on_resources = waiting_on_resources
|
34
|
+
saved_waiting_on_resources = nil #waiting_on_resources what was the purpose of this its used below to or in with the current elements waiting_for_resources
|
16
35
|
self.waiting_on_resources = nil
|
17
|
-
run_child_block(
|
36
|
+
run_child_block(&block)
|
18
37
|
if name
|
19
38
|
buffer = @buffer.dup
|
20
39
|
ReactWrapper.create_element(name, *args) { buffer }.tap do |element|
|
21
40
|
element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
|
22
41
|
element.waiting_on_resources ||= waiting_on_resources if buffer.last.is_a?(String)
|
23
42
|
end
|
24
|
-
elsif @buffer.last.is_a? Hyperstack::Component::Element
|
25
|
-
@buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
|
26
43
|
else
|
27
|
-
|
28
|
-
|
44
|
+
buffer = @buffer.collect do |item|
|
45
|
+
if item.is_a? Hyperstack::Component::Element
|
46
|
+
item.waiting_on_resources ||= saved_waiting_on_resources
|
47
|
+
item
|
48
|
+
else
|
49
|
+
RenderingContext.render(:span) { item.to_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if buffer.length > 1
|
53
|
+
buffer
|
54
|
+
else
|
55
|
+
buffer.first
|
56
|
+
end
|
29
57
|
end
|
30
58
|
end
|
31
59
|
elsif name.is_a? Hyperstack::Component::Element
|
@@ -51,6 +79,7 @@ module Hyperstack
|
|
51
79
|
|
52
80
|
def delete(element)
|
53
81
|
@buffer.delete(element)
|
82
|
+
@last_deleted = element
|
54
83
|
element
|
55
84
|
end
|
56
85
|
alias as_node delete
|
@@ -72,55 +101,74 @@ module Hyperstack
|
|
72
101
|
end if args[0] && args[0].is_a?(Hash)
|
73
102
|
end
|
74
103
|
|
75
|
-
# run_child_block
|
104
|
+
# run_child_block yields to the child rendering block which will put any
|
105
|
+
# elements to be rendered into the current rendering buffer.
|
106
|
+
#
|
76
107
|
# for example when rendering this div: div { "hello".span; "goodby".span }
|
77
108
|
# two child Elements will be generated.
|
78
109
|
#
|
79
|
-
# the
|
80
|
-
#
|
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
|
110
|
+
# However the block itself will return a value, which in some cases should
|
111
|
+
# also added to the buffer:
|
84
112
|
#
|
85
|
-
#
|
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
|
113
|
+
# If the final value of the block is a
|
89
114
|
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
115
|
+
# a hyper model dummy value that is being loaded, then wrap it in a span and add it to the buffer
|
116
|
+
# a string (or if the buffer is empty any value), then add it to the buffer
|
117
|
+
# an Element, then add it on the buffer unless it has been just deleted
|
118
|
+
# #
|
119
|
+
# Note that the reason we don't always allow Strings to be automatically pushed is
|
120
|
+
# to avoid confusing results in situations like this:
|
121
|
+
# DIV { collection.each { |item| SPAN { item } } }
|
122
|
+
# If we accepted any object to be rendered this would generate:
|
123
|
+
# DIV { SPAN { collection[0] } SPAN { collection[n] } collection.to_s }
|
124
|
+
# which is probably not the desired output. If it was you would just append to_s
|
125
|
+
# to the end of the expression, to force it to be added to the output buffer.
|
126
|
+
#
|
127
|
+
# However if the buffer is empty then it makes sense to automatically apply the `.to_s`
|
128
|
+
# to the value, and push it on the buffer, unless it is a falsy value or an array
|
94
129
|
|
95
|
-
def run_child_block
|
130
|
+
def run_child_block
|
96
131
|
result = yield
|
97
|
-
|
98
|
-
|
132
|
+
check_for_component_return(result)
|
133
|
+
if dummy_value?(result)
|
134
|
+
# hyper-mesh DummyValues must
|
99
135
|
# be converted to spans INSIDE the parent, otherwise the waiting_on_resources
|
100
136
|
# flag will get set in the wrong context
|
101
137
|
RenderingContext.render(:span) { result.to_s }
|
102
|
-
elsif result.is_a?(
|
103
|
-
@buffer << result
|
138
|
+
elsif result.is_a?(Hyperstack::Component::Element)
|
139
|
+
@buffer << result if @buffer.empty? unless @last_deleted == result
|
140
|
+
elsif pushable_string?(result)
|
141
|
+
@buffer << result.to_s
|
104
142
|
end
|
105
|
-
|
143
|
+
@last_deleted = nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def check_for_component_return(result)
|
147
|
+
# check for a common error of saying (for example) DIV (without parens)
|
148
|
+
# which returns the DIV component class instead of a rendered DIV
|
149
|
+
return unless result.try :hyper_component?
|
150
|
+
|
151
|
+
Hyperstack::Component::IsomorphicHelpers.log(
|
152
|
+
"a component's render method returned the component class #{result}, did you mean to say #{result}()",
|
153
|
+
:warning
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
def dummy_value?(result)
|
158
|
+
result.respond_to?(:loading?) && result.loading?
|
106
159
|
end
|
107
160
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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.'
|
161
|
+
def pushable_string?(result)
|
162
|
+
# if the buffer is not empty we will only push on strings, and ignore anything else
|
163
|
+
return result.is_a?(String) unless @buffer.empty?
|
164
|
+
|
165
|
+
# if the buffer IS empty then we can push on anything except we avoid nil, false and arrays
|
166
|
+
# as these are almost never what you want to render, and if you do there are mechanisms
|
167
|
+
# to render them explicitly
|
168
|
+
result && result.respond_to?(:to_n) && !result.is_a?(Array)
|
119
169
|
end
|
120
170
|
|
121
171
|
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
172
|
end
|
125
173
|
end
|
126
174
|
end
|
@@ -129,7 +177,7 @@ module Hyperstack
|
|
129
177
|
end
|
130
178
|
|
131
179
|
class Object
|
132
|
-
[
|
180
|
+
%i[span td th].each do |tag|
|
133
181
|
define_method(tag) do |*args, &block|
|
134
182
|
args.unshift(tag)
|
135
183
|
# legacy hyperloop allowed tags to be lower case as well so if self is a component
|
@@ -141,24 +189,26 @@ class Object
|
|
141
189
|
# in the component.
|
142
190
|
# If we fully deprecate lowercase tags, then this next line can go...
|
143
191
|
return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component?
|
192
|
+
|
144
193
|
Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s }
|
145
194
|
end
|
146
195
|
end
|
147
196
|
|
148
|
-
|
149
197
|
def para(*args, &block)
|
150
198
|
args.unshift(:p)
|
151
199
|
# see above comment
|
152
200
|
return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component?
|
201
|
+
|
153
202
|
Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s }
|
154
203
|
end
|
155
204
|
|
156
205
|
def br
|
157
206
|
# see above comment
|
158
207
|
return send(:br) if respond_to?(:hyper_component?) && hyper_component?
|
159
|
-
|
160
|
-
|
161
|
-
Hyperstack::Internal::Component::RenderingContext.render(
|
208
|
+
|
209
|
+
Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::FRAGMENT) do
|
210
|
+
Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::FRAGMENT) { to_s }
|
211
|
+
Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::BR)
|
162
212
|
end
|
163
213
|
end
|
164
214
|
|