react-opal 0.14.1
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 +7 -0
- data/lib/react-opal.rb +15 -0
- data/lib/react/opal/callbacks.rb +35 -0
- data/lib/react/opal/component.rb +221 -0
- data/lib/react/opal/component/api.rb +29 -0
- data/lib/react/opal/component_factory.rb +78 -0
- data/lib/react/opal/event.rb +76 -0
- data/lib/react/opal/ext/hash.rb +9 -0
- data/lib/react/opal/native_element.rb +52 -0
- data/lib/react/opal/props_children.rb +50 -0
- data/lib/react/opal/testing.rb +14 -0
- data/lib/react/opal/top_level.rb +101 -0
- data/lib/react/opal/validator.rb +69 -0
- data/lib/react/opal/version.rb +3 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bbee40ce70c05f1c8be8904a0516736a898e0965
|
4
|
+
data.tar.gz: 3cb8fa21df7d35d38041b72ba48354e4f0bdf183
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9a8506067fcb72df4a5c1b1160229a209bec151a37c3b36e932a1ea27ad27d02d08aafc88d7b8f5ece44e2a8e34cecdc4629500930ed20e2179c58e392206719
|
7
|
+
data.tar.gz: c88db5dc1b1a919992ed9d5aafae113b5ca62c75b00e36687aff4ae72172e2137e4765129263f2a8daf46894cee33292700e5a51afbe9bac1ba1c7a6fea41c3b
|
data/lib/react-opal.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
if RUBY_ENGINE == 'opal'
|
2
|
+
require 'react/opal/top_level'
|
3
|
+
require 'react/opal/props_children'
|
4
|
+
require 'react/opal/component'
|
5
|
+
require 'react/opal/native_element'
|
6
|
+
require 'react/opal/event'
|
7
|
+
require 'react/opal/component_factory'
|
8
|
+
require 'react/opal/validator'
|
9
|
+
else
|
10
|
+
require 'opal'
|
11
|
+
require 'react/opal/version'
|
12
|
+
require 'opal-activesupport'
|
13
|
+
|
14
|
+
Opal.append_path File.expand_path('../', __FILE__).untaint
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module React
|
4
|
+
module Callbacks
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def run_callback(name, *args)
|
10
|
+
attribute_name = "_#{name}_callbacks"
|
11
|
+
callbacks = self.class.send(attribute_name)
|
12
|
+
callbacks.each do |callback|
|
13
|
+
if callback.is_a?(Proc)
|
14
|
+
instance_exec(*args, &callback)
|
15
|
+
else
|
16
|
+
send(callback, *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def define_callback(callback_name)
|
23
|
+
attribute_name = "_#{callback_name}_callbacks"
|
24
|
+
class_attribute(attribute_name)
|
25
|
+
self.send("#{attribute_name}=", [])
|
26
|
+
define_singleton_method(callback_name) do |*args, &block|
|
27
|
+
callbacks = self.send(attribute_name)
|
28
|
+
callbacks.concat(args)
|
29
|
+
callbacks.push(block) if block_given?
|
30
|
+
self.send("#{attribute_name}=", callbacks)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'react/opal/callbacks'
|
3
|
+
require 'react/opal/ext/hash'
|
4
|
+
require 'react/opal/component/api'
|
5
|
+
|
6
|
+
module React
|
7
|
+
module Component
|
8
|
+
def self.included(base)
|
9
|
+
base.include(API)
|
10
|
+
base.include(React::Callbacks)
|
11
|
+
base.class_eval do
|
12
|
+
class_attribute :init_state, :validator, :context_types, :child_context_types, :child_context_get
|
13
|
+
define_callback :before_mount
|
14
|
+
define_callback :after_mount
|
15
|
+
define_callback :before_receive_props
|
16
|
+
define_callback :before_update
|
17
|
+
define_callback :after_update
|
18
|
+
define_callback :before_unmount
|
19
|
+
end
|
20
|
+
base.extend(ClassMethods)
|
21
|
+
end
|
22
|
+
|
23
|
+
def params
|
24
|
+
Hash.new(`#{self}.props`).inject({}) do |memo, (k, v)|
|
25
|
+
memo[k.underscore] = v
|
26
|
+
memo
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def refs
|
31
|
+
Hash.new(`#{self}.refs`)
|
32
|
+
end
|
33
|
+
|
34
|
+
def context
|
35
|
+
Hash.new(`#{self}.context`)
|
36
|
+
end
|
37
|
+
|
38
|
+
def emit(event_name, *args)
|
39
|
+
self.params["on_#{event_name.to_s}"].call(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def component_will_mount
|
43
|
+
self.run_callback(:before_mount)
|
44
|
+
end
|
45
|
+
|
46
|
+
def component_did_mount
|
47
|
+
self.run_callback(:after_mount)
|
48
|
+
end
|
49
|
+
|
50
|
+
def component_will_receive_props(next_props)
|
51
|
+
self.run_callback(:before_receive_props, Hash.new(next_props))
|
52
|
+
end
|
53
|
+
|
54
|
+
def should_component_update?(next_props, next_state)
|
55
|
+
self.respond_to?(:needs_update?) ? self.needs_update?(Hash.new(next_props), Hash.new(next_state)) : true
|
56
|
+
end
|
57
|
+
|
58
|
+
def component_will_update(next_props, next_state)
|
59
|
+
self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state))
|
60
|
+
end
|
61
|
+
|
62
|
+
def component_did_update(prev_props, prev_state)
|
63
|
+
self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
|
64
|
+
end
|
65
|
+
|
66
|
+
def component_will_unmount
|
67
|
+
self.run_callback(:before_unmount)
|
68
|
+
end
|
69
|
+
|
70
|
+
def p(*args, &block)
|
71
|
+
if block || args.count == 0 || (args.count == 1 && args.first.is_a?(Hash))
|
72
|
+
_p_tag(*args, &block)
|
73
|
+
else
|
74
|
+
Kernel.p(*args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def method_missing(name, *args, &block)
|
79
|
+
unless (React::HTML_TAGS.include?(name) || name == 'present' || name == '_p_tag')
|
80
|
+
return super
|
81
|
+
end
|
82
|
+
|
83
|
+
if name == "present"
|
84
|
+
name = args.shift
|
85
|
+
end
|
86
|
+
|
87
|
+
if name == "_p_tag"
|
88
|
+
name = "p"
|
89
|
+
end
|
90
|
+
|
91
|
+
@buffer = [] unless @buffer
|
92
|
+
if block
|
93
|
+
current = @buffer
|
94
|
+
@buffer = []
|
95
|
+
result = block.call
|
96
|
+
element = React.create_element(name, *args) { @buffer.count == 0 ? result : @buffer }
|
97
|
+
@buffer = current
|
98
|
+
else
|
99
|
+
element = React.create_element(name, *args)
|
100
|
+
end
|
101
|
+
|
102
|
+
@buffer << element
|
103
|
+
element
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_n
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
module ClassMethods
|
111
|
+
def prop_types
|
112
|
+
if self.validator
|
113
|
+
{
|
114
|
+
_componentValidator: %x{
|
115
|
+
function(props, propName, componentName) {
|
116
|
+
var errors = #{validator.validate(Hash.new(`props`))};
|
117
|
+
var error = new Error(#{"In component `" + self.name + "`\n" + `errors`.join("\n")});
|
118
|
+
return #{`errors`.count > 0 ? `error` : `undefined`};
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
else
|
123
|
+
{}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def initial_state
|
128
|
+
self.init_state || {}
|
129
|
+
end
|
130
|
+
|
131
|
+
def default_props
|
132
|
+
self.validator ? self.validator.default_props : {}
|
133
|
+
end
|
134
|
+
|
135
|
+
def params(&block)
|
136
|
+
if self.validator
|
137
|
+
self.validator.evaluate_more_rules(&block)
|
138
|
+
else
|
139
|
+
self.validator = React::Validator.build(&block)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def define_state_prop(prop, &block)
|
144
|
+
define_state prop
|
145
|
+
update_value = lambda do |new_value|
|
146
|
+
new_value = instance_exec(new_value, &block) if block
|
147
|
+
self.send("#{prop}=", new_value)
|
148
|
+
end
|
149
|
+
before_mount do
|
150
|
+
# need to execute in context of each object
|
151
|
+
instance_exec params[prop], &update_value
|
152
|
+
end
|
153
|
+
before_receive_props do |new_props|
|
154
|
+
# need to execute in context of each object
|
155
|
+
instance_exec new_props[prop], &update_value
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_prop_type(klass)
|
160
|
+
if klass == Proc
|
161
|
+
`React.PropTypes.func`
|
162
|
+
elsif klass.is_a?(Proc)
|
163
|
+
`React.PropTypes.object`
|
164
|
+
elsif klass == Boolean
|
165
|
+
`React.PropTypes.bool`
|
166
|
+
elsif klass.ancestors.include?(Numeric)
|
167
|
+
`React.PropTypes.number`
|
168
|
+
elsif klass == String
|
169
|
+
`React.PropTypes.string`
|
170
|
+
elsif klass == Array
|
171
|
+
`React.PropTypes.array`
|
172
|
+
else
|
173
|
+
`React.PropTypes.object`
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def consume_context(item, klass)
|
178
|
+
self.context_types ||= {}
|
179
|
+
self.context_types[item] = get_prop_type(klass)
|
180
|
+
end
|
181
|
+
|
182
|
+
def provide_context(item, klass, &block)
|
183
|
+
self.child_context_types ||= {}
|
184
|
+
self.child_context_types[item] = get_prop_type(klass)
|
185
|
+
self.child_context_get ||= {}
|
186
|
+
self.child_context_get[item] = block
|
187
|
+
unless method_defined?(:get_child_context)
|
188
|
+
define_method(:get_child_context) do
|
189
|
+
Hash[self.child_context_get.map do |item, blk|
|
190
|
+
[item, instance_eval(&blk)]
|
191
|
+
end].to_n
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def define_state(*states)
|
197
|
+
raise "Block could be only given when define exactly one state" if block_given? && states.count > 1
|
198
|
+
|
199
|
+
self.init_state = {} unless self.init_state
|
200
|
+
|
201
|
+
if block_given?
|
202
|
+
self.init_state[states[0]] = yield
|
203
|
+
end
|
204
|
+
states.each do |name|
|
205
|
+
# getter
|
206
|
+
define_method("#{name}") do
|
207
|
+
self.state[name]
|
208
|
+
end
|
209
|
+
# setter
|
210
|
+
define_method("#{name}=") do |new_state|
|
211
|
+
hash = {}
|
212
|
+
hash[name] = new_state
|
213
|
+
self.set_state(hash)
|
214
|
+
|
215
|
+
new_state
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module React
|
2
|
+
module Component
|
3
|
+
module API
|
4
|
+
def self.included(base)
|
5
|
+
base.include(::React::PropsChildren)
|
6
|
+
end
|
7
|
+
|
8
|
+
def state
|
9
|
+
Hash.new(`#{self}.state`)
|
10
|
+
end
|
11
|
+
|
12
|
+
def force_update!
|
13
|
+
`#{self}.forceUpdate()`
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_state(state, &block)
|
17
|
+
%x{
|
18
|
+
#{self}.setState(#{state.shallow_to_n}, function(){
|
19
|
+
#{block.call if block}
|
20
|
+
});
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def dom_node
|
25
|
+
raise "`dom_node` is deprecated in favor of `React.find_dom_node`"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module React
|
2
|
+
class ComponentFactory
|
3
|
+
@@component_classes = {}
|
4
|
+
|
5
|
+
def self.clear_component_class_cache
|
6
|
+
@@component_classes = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.native_component_class(klass)
|
10
|
+
@@component_classes[klass.to_s] ||= begin
|
11
|
+
klass.class_eval do
|
12
|
+
include(React::Component::API)
|
13
|
+
# In Opal 0.8, native_alias fails if the method isn't there but we don't want to force all of these to be implemented
|
14
|
+
optional_native_alias = lambda do |js, ruby|
|
15
|
+
not_there = `!(#{self}.$$proto['$' + #{ruby}])`
|
16
|
+
native_alias js, ruby unless not_there
|
17
|
+
end
|
18
|
+
optional_native_alias[:componentWillMount, :component_will_mount]
|
19
|
+
optional_native_alias[:componentDidMount, :component_did_mount]
|
20
|
+
optional_native_alias[:componentWillReceiveProps, :component_will_receive_props]
|
21
|
+
optional_native_alias[:shouldComponentUpdate, :should_component_update?]
|
22
|
+
optional_native_alias[:componentWillUpdate, :component_will_update]
|
23
|
+
optional_native_alias[:componentDidUpdate, :component_did_update]
|
24
|
+
optional_native_alias[:componentWillUnmount, :component_will_unmount]
|
25
|
+
optional_native_alias[:getChildContext, :get_child_context]
|
26
|
+
native_alias :render, :render
|
27
|
+
end
|
28
|
+
%x{
|
29
|
+
if (!Object.assign) {
|
30
|
+
Object.defineProperty(Object, 'assign', {
|
31
|
+
enumerable: false,
|
32
|
+
configurable: true,
|
33
|
+
writable: true,
|
34
|
+
value: function(target, firstSource) {
|
35
|
+
'use strict';
|
36
|
+
if (target === undefined || target === null) {
|
37
|
+
throw new TypeError('Cannot convert first argument to object');
|
38
|
+
}
|
39
|
+
|
40
|
+
var to = Object(target);
|
41
|
+
for (var i = 1; i < arguments.length; i++) {
|
42
|
+
var nextSource = arguments[i];
|
43
|
+
if (nextSource === undefined || nextSource === null) {
|
44
|
+
continue;
|
45
|
+
}
|
46
|
+
|
47
|
+
var keysArray = Object.keys(Object(nextSource));
|
48
|
+
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
|
49
|
+
var nextKey = keysArray[nextIndex];
|
50
|
+
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
|
51
|
+
if (desc !== undefined && desc.enumerable) {
|
52
|
+
to[nextKey] = nextSource[nextKey];
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
return to;
|
57
|
+
}
|
58
|
+
});
|
59
|
+
}
|
60
|
+
function ctor(props){
|
61
|
+
this.constructor = ctor;
|
62
|
+
this.state = #{klass.respond_to?(:initial_state) ? klass.initial_state.to_n : `{}`};
|
63
|
+
React.Component.apply(this, arguments);
|
64
|
+
#{klass}.$$alloc.prototype.$initialize.call(this, Opal.Hash.$new(props));
|
65
|
+
};
|
66
|
+
ctor.prototype = klass.$$proto;
|
67
|
+
Object.assign(ctor.prototype, React.Component.prototype);
|
68
|
+
ctor.propTypes = #{klass.respond_to?(:prop_types) ? klass.prop_types.to_n : `{}`};
|
69
|
+
ctor.contextTypes = #{klass.respond_to?(:context_types) ? klass.context_types.to_n : `{}`};
|
70
|
+
ctor.childContextTypes = #{klass.respond_to?(:child_context_types) ? klass.child_context_types.to_n : `{}`};
|
71
|
+
ctor.defaultProps = #{klass.respond_to?(:default_props) ? klass.default_props.to_n : `{}`};
|
72
|
+
ctor.displayName = #{klass.to_s};
|
73
|
+
}
|
74
|
+
`ctor`
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module React
|
2
|
+
class Event
|
3
|
+
include Native
|
4
|
+
alias_native :bubbles, :bubbles
|
5
|
+
alias_native :cancelable, :cancelable
|
6
|
+
alias_native :current_target, :currentTarget
|
7
|
+
alias_native :default_prevented, :defaultPrevented
|
8
|
+
alias_native :event_phase, :eventPhase
|
9
|
+
alias_native :is_trusted?, :isTrusted
|
10
|
+
alias_native :native_event, :nativeEvent
|
11
|
+
alias_native :target, :target
|
12
|
+
alias_native :timestamp, :timeStamp
|
13
|
+
alias_native :event_type, :type
|
14
|
+
alias_native :prevent_default, :preventDefault
|
15
|
+
alias_native :stop_propagation, :stopPropagation
|
16
|
+
# Clipboard
|
17
|
+
alias_native :clipboard_data, :clipboardData
|
18
|
+
# Keyboard
|
19
|
+
alias_native :alt_key, :altKey
|
20
|
+
alias_native :char_code, :charCode
|
21
|
+
alias_native :ctrl_key, :ctrlKey
|
22
|
+
alias_native :get_modifier_state, :getModifierState
|
23
|
+
alias_native :key, :key
|
24
|
+
alias_native :key_code, :keyCode
|
25
|
+
alias_native :locale, :locale
|
26
|
+
alias_native :location, :location
|
27
|
+
alias_native :meta_key, :metaKey
|
28
|
+
alias_native :repeat, :repeat
|
29
|
+
alias_native :shift_key, :shiftKey
|
30
|
+
alias_native :which, :which
|
31
|
+
# Focus
|
32
|
+
alias_native :related_target, :relatedTarget
|
33
|
+
# Mouse
|
34
|
+
alias_native :alt_key, :altKey
|
35
|
+
alias_native :button, :button
|
36
|
+
alias_native :buttons, :buttons
|
37
|
+
alias_native :client_x, :clientX
|
38
|
+
alias_native :client_y, :clientY
|
39
|
+
alias_native :ctrl_key, :ctrlKey
|
40
|
+
alias_native :get_modifier_state, :getModifierState
|
41
|
+
alias_native :meta_key, :metaKey
|
42
|
+
alias_native :page_x, :pageX
|
43
|
+
alias_native :page_y, :pageY
|
44
|
+
alias_native :related_target, :relatedTarget
|
45
|
+
alias_native :screen_x, :screen_x
|
46
|
+
alias_native :screen_y, :screen_y
|
47
|
+
alias_native :shift_key, :shift_key
|
48
|
+
# Touch
|
49
|
+
alias_native :alt_key, :altKey
|
50
|
+
alias_native :changed_touches, :changedTouches
|
51
|
+
alias_native :ctrl_key, :ctrlKey
|
52
|
+
alias_native :get_modifier_state, :getModifierState
|
53
|
+
alias_native :meta_key, :metaKey
|
54
|
+
alias_native :shift_key, :shiftKey
|
55
|
+
alias_native :target_touches, :targetTouches
|
56
|
+
alias_native :touches, :touches
|
57
|
+
# UI
|
58
|
+
alias_native :detail, :detail
|
59
|
+
alias_native :view, :view
|
60
|
+
# Wheel
|
61
|
+
alias_native :delta_mode, :deltaMode
|
62
|
+
alias_native :delta_x, :deltaX
|
63
|
+
alias_native :delta_y, :deltaY
|
64
|
+
alias_native :delta_z, :deltaZ
|
65
|
+
|
66
|
+
BUILT_IN_EVENTS = %w{onCopy onCut onPaste onKeyDown onKeyPress onKeyUp
|
67
|
+
onFocus onBlur onChange onInput onSubmit onClick onDoubleClick onDrag
|
68
|
+
onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop
|
69
|
+
onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver
|
70
|
+
onMouseUp onTouchCancel onTouchEnd onTouchMove onTouchStart onScroll}
|
71
|
+
|
72
|
+
def initialize(native_element)
|
73
|
+
@native = native_element
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module React
|
2
|
+
class NativeElement
|
3
|
+
include PropsChildren
|
4
|
+
|
5
|
+
# As of React 0.14, elements are now just plain object literals, so we can't inherit anymore
|
6
|
+
# We can just set each of the properties on our object though
|
7
|
+
# See var ReactElement = function (type, key, ref, self, source, owner, props) in the React source
|
8
|
+
def initialize(native)
|
9
|
+
%x{
|
10
|
+
self.$$typeof = #{native}.$$typeof;
|
11
|
+
self.type = #{native}.type;
|
12
|
+
self.key = #{native}.key;
|
13
|
+
self.ref = #{native}.ref;
|
14
|
+
self.props = #{native}.props;
|
15
|
+
self._owner = #{native}._owner;
|
16
|
+
self._store = #{native}._store;
|
17
|
+
self._self = #{native}._self;
|
18
|
+
self._source = #{native}._source;
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def element_type
|
23
|
+
self.JS[:type]
|
24
|
+
end
|
25
|
+
|
26
|
+
def on(event_name)
|
27
|
+
name = event_name.to_s.camelize
|
28
|
+
|
29
|
+
prop_key = "on#{name}"
|
30
|
+
|
31
|
+
if React::Event::BUILT_IN_EVENTS.include?(prop_key)
|
32
|
+
callback = %x{
|
33
|
+
function(event){
|
34
|
+
#{yield React::Event.new(`event`)}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
else
|
38
|
+
callback = %x{
|
39
|
+
function(){
|
40
|
+
#{yield *Array(`arguments`)}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
new_prop = `{}`
|
46
|
+
`new_prop[prop_key] = #{callback}`
|
47
|
+
|
48
|
+
cloned = `React.cloneElement(#{self}, #{new_prop})`
|
49
|
+
React::NativeElement.new cloned
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module React
|
2
|
+
module PropsChildren
|
3
|
+
def props
|
4
|
+
Hash.new(`#{self}.props`)
|
5
|
+
end
|
6
|
+
|
7
|
+
def children
|
8
|
+
nodes = `#{self}.props.children`
|
9
|
+
|
10
|
+
if `React.Children.count(nodes)` == 0
|
11
|
+
`[]`
|
12
|
+
elsif `React.Children.count(nodes)` == 1
|
13
|
+
if `(typeof nodes === 'string') || (typeof nodes === 'number')`
|
14
|
+
[nodes]
|
15
|
+
else
|
16
|
+
`[React.Children.only(nodes)]`
|
17
|
+
end
|
18
|
+
else
|
19
|
+
# Not sure the overhead of doing this..
|
20
|
+
class << nodes
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
def to_n
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def each(&block)
|
28
|
+
if block_given?
|
29
|
+
%x{
|
30
|
+
React.Children.forEach(#{self.to_n}, function(context){
|
31
|
+
#{block.call(`context`)}
|
32
|
+
})
|
33
|
+
}
|
34
|
+
else
|
35
|
+
Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
|
36
|
+
%x{
|
37
|
+
React.Children.forEach(#{self.to_n}, function(context){
|
38
|
+
#{y << `context`}
|
39
|
+
})
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
nodes
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module React
|
2
|
+
module Testing
|
3
|
+
`var ReactTestUtils = React.addons.TestUtils`
|
4
|
+
|
5
|
+
def simulate_event(event_name, dom_element, event_data = {})
|
6
|
+
simulator = Native(`ReactTestUtils.Simulate`)
|
7
|
+
simulator[event_name].call(dom_element, event_data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def render_to_document(element)
|
11
|
+
`ReactTestUtils.renderIntoDocument(#{element})`
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'native'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
module React
|
5
|
+
HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
|
6
|
+
button canvas caption cite code col colgroup data datalist dd del details dfn
|
7
|
+
dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
|
8
|
+
h6 head header hr html i iframe img input ins kbd keygen label legend li link
|
9
|
+
main map mark menu menuitem meta meter nav noscript object ol optgroup option
|
10
|
+
output p param picture pre progress q rp rt ruby s samp script section select
|
11
|
+
small source span strong style sub summary sup table tbody td textarea tfoot th
|
12
|
+
thead time title tr track u ul var video wbr)
|
13
|
+
|
14
|
+
def self.create_element(type, properties = {})
|
15
|
+
params = []
|
16
|
+
|
17
|
+
# Component Spec or Normal DOM
|
18
|
+
native = `(typeof type === 'function')` || HTML_TAGS.include?(type)
|
19
|
+
params << if native
|
20
|
+
type
|
21
|
+
elsif type.kind_of?(Class)
|
22
|
+
raise "Provided class should define `render` method" if !(type.method_defined? :render)
|
23
|
+
React::ComponentFactory.native_component_class(type)
|
24
|
+
else
|
25
|
+
raise "#{type} not implemented"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Passed in properties
|
29
|
+
props = camel_case_hash_keys(properties) do |key, value|
|
30
|
+
if key == "class_name" && value.is_a?(Hash)
|
31
|
+
value.inject([]) { |ary, (k, v)| v ? ary.push(k) : ary }.join(" ")
|
32
|
+
elsif key == 'value_link'
|
33
|
+
process_value_link value
|
34
|
+
else
|
35
|
+
value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
params << props.shallow_to_n
|
40
|
+
|
41
|
+
# Children Nodes
|
42
|
+
if block_given?
|
43
|
+
[yield].flatten.each do |ele|
|
44
|
+
params << ele
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
element = `React.createElement.apply(null, #{params})`
|
49
|
+
React::NativeElement.new(element)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.lower_camelize(str)
|
53
|
+
camelized = str.camelize
|
54
|
+
camelized[0].downcase + camelized[1..-1]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.camel_case_hash_keys(input)
|
58
|
+
as_array = input.map do |key, value|
|
59
|
+
new_value = block_given? ? yield(key, value) : value
|
60
|
+
[lower_camelize(key), new_value]
|
61
|
+
end
|
62
|
+
Hash[as_array]
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.process_value_link(arguments)
|
66
|
+
arguments = arguments.call if arguments.is_a? Proc
|
67
|
+
camel_case_hash_keys(arguments).to_n
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.render(element, container)
|
71
|
+
component = Native(`ReactDOM.render(#{element}, container, function(){#{yield if block_given?}})`)
|
72
|
+
component.class.include(React::Component::API)
|
73
|
+
component
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.is_valid_element(element)
|
77
|
+
`React.isValidElement(#{element})`
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.render_to_string(element)
|
81
|
+
`ReactDOMServer.renderToString(#{element})`
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.render_to_static_markup(element)
|
85
|
+
`ReactDOMServer.renderToStaticMarkup(#{element})`
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.unmount_component_at_node(node)
|
89
|
+
`ReactDOM.unmountComponentAtNode(node)`
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.expose_native_class(*args)
|
93
|
+
args.each do |klass|
|
94
|
+
`window[#{klass.to_s}] = #{React::ComponentFactory.native_component_class(klass)}`
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.find_dom_node(component)
|
99
|
+
`ReactDOM.findDOMNode(#{component})`
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module React
|
2
|
+
class Validator
|
3
|
+
def self.build(&block)
|
4
|
+
validator = self.new
|
5
|
+
validator.instance_eval(&block)
|
6
|
+
validator
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@rules = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate_more_rules(&block)
|
14
|
+
self.instance_eval(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def requires(prop_name, options = {})
|
18
|
+
rule = options
|
19
|
+
options[:required] = true
|
20
|
+
@rules[prop_name] = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def optional(prop_name, options = {})
|
24
|
+
rule = options
|
25
|
+
options[:required] = false
|
26
|
+
@rules[prop_name] = options
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate(props)
|
30
|
+
errors = []
|
31
|
+
props.keys.each do |prop_name|
|
32
|
+
errors << "Provided prop `#{prop_name}` not specified in spec" if @rules[prop_name] == nil
|
33
|
+
end
|
34
|
+
|
35
|
+
props = props.select {|key| @rules.keys.include?(key) }
|
36
|
+
|
37
|
+
# requires or not
|
38
|
+
(@rules.keys - props.keys).each do |prop_name|
|
39
|
+
errors << "Required prop `#{prop_name}` was not specified" if @rules[prop_name][:required]
|
40
|
+
end
|
41
|
+
|
42
|
+
# type
|
43
|
+
props.each do |prop_name, value|
|
44
|
+
if klass = @rules[prop_name][:type]
|
45
|
+
if klass.is_a?(Array)
|
46
|
+
errors << "Provided prop `#{prop_name}` was not an Array of the specified type `#{klass[0]}`" unless value.all?{ |ele| ele.is_a?(klass[0]) }
|
47
|
+
else
|
48
|
+
errors << "Provided prop `#{prop_name}` was not the specified type `#{klass}`" unless value.is_a?(klass)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# values
|
54
|
+
props.each do |prop_name, value|
|
55
|
+
if values = @rules[prop_name][:values]
|
56
|
+
errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value" unless values.include?(value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
errors
|
61
|
+
end
|
62
|
+
|
63
|
+
def default_props
|
64
|
+
@rules
|
65
|
+
.select {|key, value| value.keys.include?("default") }
|
66
|
+
.inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: react-opal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.14.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brady Wied
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: opal
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: opal-activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.2.0
|
41
|
+
description: Write reactive UI component with Ruby's elegancy and compiled to run
|
42
|
+
in Javascript.
|
43
|
+
email: brady@bswtechconsulting.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/react-opal.rb
|
49
|
+
- lib/react/opal/callbacks.rb
|
50
|
+
- lib/react/opal/component.rb
|
51
|
+
- lib/react/opal/component/api.rb
|
52
|
+
- lib/react/opal/component_factory.rb
|
53
|
+
- lib/react/opal/event.rb
|
54
|
+
- lib/react/opal/ext/hash.rb
|
55
|
+
- lib/react/opal/native_element.rb
|
56
|
+
- lib/react/opal/props_children.rb
|
57
|
+
- lib/react/opal/testing.rb
|
58
|
+
- lib/react/opal/top_level.rb
|
59
|
+
- lib/react/opal/validator.rb
|
60
|
+
- lib/react/opal/version.rb
|
61
|
+
homepage: https://github.com/wied03/react-opal
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.6.1
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Opal Ruby wrapper of React.js library.
|
85
|
+
test_files: []
|