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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -3
- data/Gemfile +4 -3
- data/Gemfile.lock +51 -36
- data/{misc/how-component-name-lookup-works.md → how-component-name-lookup-works.md} +1 -1
- data/hyper-component.gemspec +9 -8
- data/lib/hyper-component.rb +31 -43
- data/lib/hyperstack/component.rb +145 -0
- data/lib/hyperstack/component/auto-import.rb +44 -0
- data/lib/hyperstack/component/children.rb +40 -0
- data/lib/hyperstack/component/element.rb +129 -0
- data/lib/hyperstack/component/event.rb +78 -0
- data/lib/hyperstack/component/haml.rb +18 -0
- data/lib/hyperstack/component/isomorphic_helpers.rb +235 -0
- data/lib/hyperstack/component/jquery.rb +2 -0
- data/lib/hyperstack/component/native_library.rb +92 -0
- data/lib/hyperstack/component/react_api.rb +142 -0
- data/lib/hyperstack/component/server.rb +21 -0
- data/lib/hyperstack/component/version.rb +5 -0
- data/lib/hyperstack/ext/component/boolean.rb +14 -0
- data/lib/{react/ext/opal-jquery → hyperstack/ext/component}/element.rb +17 -12
- data/lib/{react/ext → hyperstack/ext/component}/hash.rb +0 -0
- data/lib/{react/to_key.rb → hyperstack/ext/component/number.rb} +0 -12
- data/lib/hyperstack/ext/component/object.rb +32 -0
- data/lib/{reactive-ruby → hyperstack/ext/component}/serializers.rb +0 -0
- data/lib/{react/ext → hyperstack/ext/component}/string.rb +0 -0
- data/lib/hyperstack/internal/component.rb +16 -0
- data/lib/hyperstack/internal/component/class_methods.rb +212 -0
- data/lib/hyperstack/internal/component/haml.rb +56 -0
- data/lib/hyperstack/internal/component/instance_methods.rb +92 -0
- data/lib/hyperstack/internal/component/props_wrapper.rb +125 -0
- data/lib/hyperstack/internal/component/rails.rb +11 -0
- data/lib/hyperstack/internal/component/rails/component_loader.rb +49 -0
- data/lib/hyperstack/internal/component/rails/component_mount.rb +52 -0
- data/lib/{reactive-ruby → hyperstack/internal/component}/rails/controller_helper.rb +0 -0
- data/lib/hyperstack/internal/component/rails/railtie.rb +24 -0
- data/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +52 -0
- data/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +52 -0
- data/lib/hyperstack/internal/component/react_wrapper.rb +308 -0
- data/lib/hyperstack/internal/component/rendering_context.rb +165 -0
- data/lib/hyperstack/internal/component/should_component_update.rb +101 -0
- data/lib/hyperstack/internal/component/tags.rb +109 -0
- data/lib/hyperstack/internal/component/top_level_rails_component.rb +83 -0
- data/lib/hyperstack/internal/component/validator.rb +149 -0
- data/lib/react/react-source.rb +2 -2
- data/unmounting-objects.md +78 -0
- metadata +73 -85
- data/DOCS.md +0 -1515
- data/LICENSE +0 -19
- data/README.md +0 -49
- data/lib/hyper-component/jquery.rb +0 -2
- data/lib/rails-helpers/top_level_rails_component.rb +0 -79
- data/lib/react/api.rb +0 -272
- data/lib/react/callbacks.rb +0 -42
- data/lib/react/children.rb +0 -38
- data/lib/react/component.rb +0 -189
- data/lib/react/component/api.rb +0 -70
- data/lib/react/component/base.rb +0 -13
- data/lib/react/component/class_methods.rb +0 -175
- data/lib/react/component/dsl_instance_methods.rb +0 -23
- data/lib/react/component/params.rb +0 -6
- data/lib/react/component/props_wrapper.rb +0 -90
- data/lib/react/component/should_component_update.rb +0 -99
- data/lib/react/component/tags.rb +0 -116
- data/lib/react/config.rb +0 -5
- data/lib/react/element.rb +0 -167
- data/lib/react/event.rb +0 -76
- data/lib/react/native_library.rb +0 -87
- data/lib/react/object.rb +0 -15
- data/lib/react/ref_callback.rb +0 -31
- data/lib/react/rendering_context.rb +0 -149
- data/lib/react/server.rb +0 -19
- data/lib/react/state_wrapper.rb +0 -23
- data/lib/react/test.rb +0 -16
- data/lib/react/test/dsl.rb +0 -17
- data/lib/react/test/matchers/render_html_matcher.rb +0 -56
- data/lib/react/test/rspec.rb +0 -15
- data/lib/react/test/session.rb +0 -37
- data/lib/react/test/utils.rb +0 -71
- data/lib/react/top_level.rb +0 -110
- data/lib/react/top_level_render.rb +0 -30
- data/lib/react/validator.rb +0 -132
- data/lib/reactive-ruby/component_loader.rb +0 -43
- data/lib/reactive-ruby/isomorphic_helpers.rb +0 -233
- data/lib/reactive-ruby/rails.rb +0 -8
- data/lib/reactive-ruby/rails/component_mount.rb +0 -48
- data/lib/reactive-ruby/rails/railtie.rb +0 -20
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +0 -46
- data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +0 -46
- data/lib/reactive-ruby/version.rb +0 -5
- data/lib/reactrb/auto-import.rb +0 -27
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +0 -3
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +0 -5
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +0 -2
- data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +0 -6
- data/misc/generators/reactive_ruby/test_app/templates/script/rails +0 -5
- data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +0 -13
- data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +0 -11
- data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +0 -14
- data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +0 -121
- data/misc/hyperloop-logo-small-pink.png +0 -0
- data/misc/logo1.png +0 -0
- data/misc/logo2.png +0 -0
- data/misc/logo3.png +0 -0
- 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
|