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