hyper-component 1.0.alpha1.1 → 1.0.alpha1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|