reactive-ruby 0.7.3
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/.gitignore +30 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +19 -0
- data/README.md +303 -0
- data/config.ru +15 -0
- data/example/examples/Gemfile +7 -0
- data/example/examples/Gemfile.lock +45 -0
- data/example/examples/config.ru +44 -0
- data/example/examples/hello.js.rb +43 -0
- data/example/react-tutorial/Gemfile +7 -0
- data/example/react-tutorial/Gemfile.lock +49 -0
- data/example/react-tutorial/README.md +8 -0
- data/example/react-tutorial/_comments.json +14 -0
- data/example/react-tutorial/config.ru +63 -0
- data/example/react-tutorial/example.js.rb +290 -0
- data/example/react-tutorial/public/base.css +62 -0
- data/example/todos/Gemfile +11 -0
- data/example/todos/Gemfile.lock +84 -0
- data/example/todos/README.md +37 -0
- data/example/todos/Rakefile +8 -0
- data/example/todos/app/application.rb +22 -0
- data/example/todos/app/components/app.react.rb +61 -0
- data/example/todos/app/components/footer.react.rb +31 -0
- data/example/todos/app/components/todo_item.react.rb +46 -0
- data/example/todos/app/components/todo_list.react.rb +25 -0
- data/example/todos/app/models/todo.rb +19 -0
- data/example/todos/config.ru +14 -0
- data/example/todos/index.html.haml +16 -0
- data/example/todos/spec/todo_spec.rb +28 -0
- data/example/todos/vendor/base.css +410 -0
- data/example/todos/vendor/bg.png +0 -0
- data/example/todos/vendor/jquery.js +4 -0
- data/lib/rails-helpers/react_component.rb +32 -0
- data/lib/reactive-ruby.rb +23 -0
- data/lib/reactive-ruby/api.rb +177 -0
- data/lib/reactive-ruby/callbacks.rb +35 -0
- data/lib/reactive-ruby/component.rb +411 -0
- data/lib/reactive-ruby/element.rb +87 -0
- data/lib/reactive-ruby/event.rb +76 -0
- data/lib/reactive-ruby/ext/hash.rb +9 -0
- data/lib/reactive-ruby/ext/string.rb +8 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +223 -0
- data/lib/reactive-ruby/observable.rb +33 -0
- data/lib/reactive-ruby/rendering_context.rb +91 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/state.rb +90 -0
- data/lib/reactive-ruby/top_level.rb +53 -0
- data/lib/reactive-ruby/validator.rb +83 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/reactive-ruby.gemspec +25 -0
- data/spec/callbacks_spec.rb +107 -0
- data/spec/component_spec.rb +597 -0
- data/spec/element_spec.rb +60 -0
- data/spec/event_spec.rb +22 -0
- data/spec/react_spec.rb +209 -0
- data/spec/reactjs/index.html.erb +11 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/tutorial/tutorial_spec.rb +37 -0
- data/spec/validator_spec.rb +79 -0
- data/vendor/active_support/core_ext/array/extract_options.rb +29 -0
- data/vendor/active_support/core_ext/class/attribute.rb +127 -0
- data/vendor/active_support/core_ext/kernel/singleton_class.rb +13 -0
- data/vendor/active_support/core_ext/module/remove_method.rb +11 -0
- metadata +205 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
if RUBY_ENGINE == 'opal'
|
2
|
+
require "reactive-ruby/top_level"
|
3
|
+
require "reactive-ruby/component"
|
4
|
+
require "reactive-ruby/element"
|
5
|
+
require "reactive-ruby/event"
|
6
|
+
require "reactive-ruby/version"
|
7
|
+
require "reactive-ruby/api"
|
8
|
+
require "reactive-ruby/validator"
|
9
|
+
require "reactive-ruby/observable"
|
10
|
+
require "reactive-ruby/rendering_context"
|
11
|
+
require "reactive-ruby/state"
|
12
|
+
require "reactive-ruby/isomorphic_helpers"
|
13
|
+
else
|
14
|
+
require "opal"
|
15
|
+
require "reactive-ruby/version"
|
16
|
+
require "opal-activesupport"
|
17
|
+
require "rails-helpers/react_component"
|
18
|
+
require "reactive-ruby/isomorphic_helpers"
|
19
|
+
require "reactive-ruby/serializers"
|
20
|
+
|
21
|
+
Opal.append_path File.expand_path('../', __FILE__).untaint
|
22
|
+
Opal.append_path File.expand_path('../../vendor', __FILE__).untaint
|
23
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module React
|
2
|
+
|
3
|
+
class NativeLibrary
|
4
|
+
|
5
|
+
def self.renames_and_exclusions
|
6
|
+
@renames_and_exclusions ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.libraries
|
10
|
+
@libraries ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.const_missing(name)
|
14
|
+
if renames_and_exclusions.has_key? name
|
15
|
+
if native_name = renames_and_exclusions[name]
|
16
|
+
native_name
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
else
|
21
|
+
libraries.each do |library|
|
22
|
+
native_name = "#{library}.#{name}"
|
23
|
+
native_component = `eval(#{native_name})` rescue nil
|
24
|
+
React::API.import_native_component(name, native_component) and return name if native_component and `native_component != undefined`
|
25
|
+
end
|
26
|
+
name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.method_missing(n, *args, &block)
|
31
|
+
name = n
|
32
|
+
if name =~ /_as_node$/
|
33
|
+
node_only = true
|
34
|
+
name = name.gsub(/_as_node$/, "")
|
35
|
+
end
|
36
|
+
unless name = const_get(name)
|
37
|
+
return super
|
38
|
+
end
|
39
|
+
if node_only
|
40
|
+
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
|
41
|
+
else
|
42
|
+
React::RenderingContext.render(name, *args, &block)
|
43
|
+
end
|
44
|
+
rescue
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.imports(library)
|
48
|
+
libraries << library
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.rename(rename_list={})
|
52
|
+
renames_and_exclusions.merge!(rename_list.invert)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.exclude(*exclude_list)
|
56
|
+
renames_and_exclusions.merge(Hash[exclude_list.map {|k| [k, nil]}])
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class API
|
62
|
+
|
63
|
+
@@component_classes = {}
|
64
|
+
|
65
|
+
def self.import_native_component(opal_class, native_class)
|
66
|
+
@@component_classes[opal_class.to_s] = native_class
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.create_native_react_class(type)
|
70
|
+
raise "Provided class should define `render` method" if !(type.method_defined? :render)
|
71
|
+
render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
|
72
|
+
# 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"
|
73
|
+
@@component_classes[type] ||= %x{
|
74
|
+
React.createClass({
|
75
|
+
propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
|
76
|
+
getDefaultProps: function(){
|
77
|
+
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
|
78
|
+
},
|
79
|
+
componentWillMount: function() {
|
80
|
+
var instance = this._getOpalInstance.apply(this);
|
81
|
+
return #{`instance`.component_will_mount if type.method_defined? :component_will_mount};
|
82
|
+
},
|
83
|
+
componentDidMount: function() {
|
84
|
+
var instance = this._getOpalInstance.apply(this);
|
85
|
+
return #{`instance`.component_did_mount if type.method_defined? :component_did_mount};
|
86
|
+
},
|
87
|
+
componentWillReceiveProps: function(next_props) {
|
88
|
+
var instance = this._getOpalInstance.apply(this);
|
89
|
+
return #{`instance`.component_will_receive_props(`next_props`) if type.method_defined? :component_will_receive_props};
|
90
|
+
},
|
91
|
+
shouldComponentUpdate: function(next_props, next_state) {
|
92
|
+
var instance = this._getOpalInstance.apply(this);
|
93
|
+
return #{`instance`.should_component_update?(`next_props`, `next_state`) if type.method_defined? :should_component_update?};
|
94
|
+
},
|
95
|
+
componentWillUpdate: function(next_props, next_state) {
|
96
|
+
var instance = this._getOpalInstance.apply(this);
|
97
|
+
return #{`instance`.component_will_update(`next_props`, `next_state`) if type.method_defined? :component_will_update};
|
98
|
+
},
|
99
|
+
componentDidUpdate: function(prev_props, prev_state) {
|
100
|
+
var instance = this._getOpalInstance.apply(this);
|
101
|
+
return #{`instance`.component_did_update(`prev_props`, `prev_state`) if type.method_defined? :component_did_update};
|
102
|
+
},
|
103
|
+
componentWillUnmount: function() {
|
104
|
+
var instance = this._getOpalInstance.apply(this);
|
105
|
+
return #{`instance`.component_will_unmount if type.method_defined? :component_will_unmount};
|
106
|
+
},
|
107
|
+
_getOpalInstance: function() {
|
108
|
+
if (this.__opalInstance == undefined) {
|
109
|
+
var instance = #{type.new(`this`)};
|
110
|
+
} else {
|
111
|
+
var instance = this.__opalInstance;
|
112
|
+
}
|
113
|
+
this.__opalInstance = instance;
|
114
|
+
return instance;
|
115
|
+
},
|
116
|
+
render: function() {
|
117
|
+
var instance = this._getOpalInstance.apply(this);
|
118
|
+
return #{`instance`.send(render_fn).to_n};
|
119
|
+
}
|
120
|
+
})
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.create_element(type, properties = {}, &block)
|
125
|
+
params = []
|
126
|
+
|
127
|
+
# Component Spec, Normal DOM, String or Native Component
|
128
|
+
if @@component_classes[type]
|
129
|
+
params << @@component_classes[type]
|
130
|
+
elsif type.kind_of?(Class)
|
131
|
+
params << create_native_react_class(type)
|
132
|
+
elsif HTML_TAGS.include?(type)
|
133
|
+
params << type
|
134
|
+
elsif type.is_a? String
|
135
|
+
return React::Element.new(type)
|
136
|
+
else
|
137
|
+
raise "#{type} not implemented"
|
138
|
+
end
|
139
|
+
|
140
|
+
# Passed in properties
|
141
|
+
props = {}
|
142
|
+
properties.map do |key, value|
|
143
|
+
if key == "class_name" && value.is_a?(Hash)
|
144
|
+
props[lower_camelize(key)] = `React.addons.classSet(#{value.to_n})`
|
145
|
+
elsif key == "class"
|
146
|
+
props["className"] = value
|
147
|
+
elsif ["style", "dangerously_set_inner_HTML"].include? key
|
148
|
+
props[lower_camelize(key)] = value.to_n
|
149
|
+
else
|
150
|
+
props[React::ATTRIBUTES.include?(lower_camelize(key)) ? lower_camelize(key) : key] = value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
params << props.shallow_to_n
|
154
|
+
|
155
|
+
# Children Nodes
|
156
|
+
if block_given?
|
157
|
+
children = [yield].flatten.each do |ele|
|
158
|
+
params << ele.to_n
|
159
|
+
end
|
160
|
+
end
|
161
|
+
return React::Element.new(`React.createElement.apply(null, #{params})`, type, properties, block)
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.clear_component_class_cache
|
165
|
+
@@component_classes = {}
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def self.lower_camelize(snake_cased_word)
|
171
|
+
words = snake_cased_word.split("_")
|
172
|
+
result = [words.first]
|
173
|
+
result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] })
|
174
|
+
result.join("")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
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,411 @@
|
|
1
|
+
require "reactive-ruby/ext/string"
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
require 'reactive-ruby/callbacks'
|
4
|
+
require "reactive-ruby/ext/hash"
|
5
|
+
require "reactive-ruby/rendering_context"
|
6
|
+
require "reactive-ruby/observable"
|
7
|
+
require "reactive-ruby/state"
|
8
|
+
|
9
|
+
require 'native'
|
10
|
+
|
11
|
+
module React
|
12
|
+
module Component
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
base.include(API)
|
16
|
+
base.include(React::Callbacks)
|
17
|
+
base.class_eval do
|
18
|
+
class_attribute :initial_state
|
19
|
+
define_callback :before_mount
|
20
|
+
define_callback :after_mount
|
21
|
+
define_callback :before_receive_props
|
22
|
+
define_callback :before_update
|
23
|
+
define_callback :after_update
|
24
|
+
define_callback :before_unmount
|
25
|
+
|
26
|
+
def render
|
27
|
+
raise "no render defined"
|
28
|
+
end unless method_defined? :render
|
29
|
+
|
30
|
+
end
|
31
|
+
base.extend(ClassMethods)
|
32
|
+
|
33
|
+
if base.name
|
34
|
+
parent = base.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
|
35
|
+
parent.class_eval do
|
36
|
+
|
37
|
+
def method_missing(n, *args, &block)
|
38
|
+
name = n
|
39
|
+
if name =~ /_as_node$/
|
40
|
+
node_only = true
|
41
|
+
name = name.gsub(/_as_node$/, "")
|
42
|
+
end
|
43
|
+
unless name = const_get(name) and name.method_defined? :render
|
44
|
+
return super
|
45
|
+
end
|
46
|
+
if node_only
|
47
|
+
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
|
48
|
+
else
|
49
|
+
React::RenderingContext.render(name, *args, &block)
|
50
|
+
end
|
51
|
+
rescue Exception => e
|
52
|
+
message = "#{base.name}.#{n} method_missing handler exception raised: #{e}"
|
53
|
+
`console.error(#{message})`
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(native_element)
|
61
|
+
@native = native_element
|
62
|
+
end
|
63
|
+
|
64
|
+
def params
|
65
|
+
Hash.new(`#{@native}.props`)
|
66
|
+
end
|
67
|
+
|
68
|
+
def refs
|
69
|
+
Hash.new(`#{@native}.refs`)
|
70
|
+
end
|
71
|
+
|
72
|
+
def state
|
73
|
+
raise "No native ReactComponent associated" unless @native
|
74
|
+
Hash.new(`#{@native}.state`)
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_react_js_state(object, name, value)
|
78
|
+
set_state({"#{object.class.to_s+'.' unless object == self}name" => value}) rescue nil # in case we are in render
|
79
|
+
end
|
80
|
+
|
81
|
+
def emit(event_name, *args)
|
82
|
+
self.params["_on#{event_name.to_s.event_camelize}"].call(*args)
|
83
|
+
end
|
84
|
+
|
85
|
+
def component_will_mount
|
86
|
+
IsomorphicHelpers.load_context(true) if IsomorphicHelpers.on_opal_client?
|
87
|
+
@processed_params = {}
|
88
|
+
React::State.initialize_states(self, initial_state)
|
89
|
+
React::State.set_state_context_to(self) { self.run_callback(:before_mount) }
|
90
|
+
rescue Exception => e
|
91
|
+
self.class.process_exception(e, self)
|
92
|
+
end
|
93
|
+
|
94
|
+
def component_did_mount
|
95
|
+
React::State.set_state_context_to(self) do
|
96
|
+
self.run_callback(:after_mount)
|
97
|
+
React::State.update_states_to_observe
|
98
|
+
end
|
99
|
+
rescue Exception => e
|
100
|
+
self.class.process_exception(e, self)
|
101
|
+
end
|
102
|
+
|
103
|
+
def component_will_receive_props(next_props)
|
104
|
+
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
|
105
|
+
# for now we are just using it to clear processed_params
|
106
|
+
React::State.set_state_context_to(self) { self.run_callback(:before_receive_props, Hash.new(next_props)) }
|
107
|
+
@processed_params = {}
|
108
|
+
rescue Exception => e
|
109
|
+
self.class.process_exception(e, self)
|
110
|
+
end
|
111
|
+
|
112
|
+
def should_component_update?(next_props, next_state)
|
113
|
+
React::State.set_state_context_to(self) { self.respond_to?(:needs_update?) ? self.needs_update?(Hash.new(next_props), Hash.new(next_state)) : true }
|
114
|
+
rescue Exception => e
|
115
|
+
self.class.process_exception(e, self)
|
116
|
+
end
|
117
|
+
|
118
|
+
def component_will_update(next_props, next_state)
|
119
|
+
React::State.set_state_context_to(self) { self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state)) }
|
120
|
+
rescue Exception => e
|
121
|
+
self.class.process_exception(e, self)
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def component_did_update(prev_props, prev_state)
|
126
|
+
React::State.set_state_context_to(self) do
|
127
|
+
self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
|
128
|
+
React::State.update_states_to_observe
|
129
|
+
end
|
130
|
+
rescue Exception => e
|
131
|
+
self.class.process_exception(e, self)
|
132
|
+
end
|
133
|
+
|
134
|
+
def component_will_unmount
|
135
|
+
React::State.set_state_context_to(self) do
|
136
|
+
self.run_callback(:before_unmount)
|
137
|
+
React::State.remove
|
138
|
+
end
|
139
|
+
rescue Exception => e
|
140
|
+
self.class.process_exception(e, self)
|
141
|
+
end
|
142
|
+
|
143
|
+
def p(*args, &block)
|
144
|
+
if block || args.count == 0 || (args.count == 1 && args.first.is_a?(Hash))
|
145
|
+
_p_tag(*args, &block)
|
146
|
+
else
|
147
|
+
Kernel.p(*args)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def component?(name)
|
152
|
+
name_list = name.split("::")
|
153
|
+
scope_list = self.class.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }.reverse
|
154
|
+
scope_list.each do |scope|
|
155
|
+
component = name_list.inject(scope) do |scope, class_name|
|
156
|
+
scope.const_get(class_name)
|
157
|
+
end rescue nil
|
158
|
+
return component if component and component.method_defined? :render
|
159
|
+
end
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def method_missing(n, *args, &block)
|
164
|
+
return params[n] if params.key? n
|
165
|
+
name = n
|
166
|
+
if name =~ /_as_node$/
|
167
|
+
node_only = true
|
168
|
+
name = name.gsub(/_as_node$/, "")
|
169
|
+
end
|
170
|
+
unless (React::HTML_TAGS.include?(name) || name == 'present' || name == '_p_tag' || (name = component?(name, self)))
|
171
|
+
return super
|
172
|
+
end
|
173
|
+
|
174
|
+
if name == "present"
|
175
|
+
name = args.shift
|
176
|
+
end
|
177
|
+
|
178
|
+
if name == "_p_tag"
|
179
|
+
name = "p"
|
180
|
+
end
|
181
|
+
|
182
|
+
if node_only
|
183
|
+
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
|
184
|
+
else
|
185
|
+
React::RenderingContext.render(name, *args, &block)
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
def watch(value, &on_change)
|
191
|
+
React::Observable.new(value, on_change)
|
192
|
+
end
|
193
|
+
|
194
|
+
def define_state(*args, &block)
|
195
|
+
React::State.initialize_states(self, self.class.define_state(*args, &block))
|
196
|
+
end
|
197
|
+
|
198
|
+
attr_reader :waiting_on_resources
|
199
|
+
|
200
|
+
def _render_wrapper
|
201
|
+
React::State.set_state_context_to(self) do
|
202
|
+
RenderingContext.render(nil) {render || ""}.tap { |element| @waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources }
|
203
|
+
end
|
204
|
+
rescue Exception => e
|
205
|
+
self.class.process_exception(e, self)
|
206
|
+
end
|
207
|
+
|
208
|
+
module ClassMethods
|
209
|
+
|
210
|
+
def backtrace(*args)
|
211
|
+
@backtrace_on = (args.count == 0 or (args[0] != :off and args[0]))
|
212
|
+
end
|
213
|
+
|
214
|
+
def process_exception(e, component, reraise = nil)
|
215
|
+
message = ["Exception raised while rendering #{component}"]
|
216
|
+
if @backtrace_on
|
217
|
+
message << " #{e.backtrace[0]}"
|
218
|
+
message += e.backtrace[1..-1].collect { |line| line }
|
219
|
+
else
|
220
|
+
message[0] += ": #{e.message}"
|
221
|
+
end
|
222
|
+
message = message.join("\n")
|
223
|
+
`console.error(message)`
|
224
|
+
raise e if reraise
|
225
|
+
end
|
226
|
+
|
227
|
+
def validator
|
228
|
+
@validator ||= React::Validator.new
|
229
|
+
end
|
230
|
+
|
231
|
+
def prop_types
|
232
|
+
if self.validator
|
233
|
+
{
|
234
|
+
_componentValidator: %x{
|
235
|
+
function(props, propName, componentName) {
|
236
|
+
var errors = #{validator.validate(Hash.new(`props`))};
|
237
|
+
var error = new Error(#{"In component `" + self.name + "`\n" + `errors`.join("\n")});
|
238
|
+
return #{`errors`.count > 0 ? `error` : `undefined`};
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
else
|
243
|
+
{}
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def default_props
|
248
|
+
validator.default_props
|
249
|
+
end
|
250
|
+
|
251
|
+
def params(&block)
|
252
|
+
validator.build(&block)
|
253
|
+
end
|
254
|
+
|
255
|
+
def define_param_method(name, param_type)
|
256
|
+
if param_type == React::Observable
|
257
|
+
(@two_way_params ||= []) << name
|
258
|
+
define_method("#{name}") do
|
259
|
+
params[name].instance_variable_get("@value")
|
260
|
+
end
|
261
|
+
define_method("#{name}!") do |*args|
|
262
|
+
if args.count > 0
|
263
|
+
current_value = params[name].instance_variable_get("@value")
|
264
|
+
params[name].call args[0]
|
265
|
+
current_value
|
266
|
+
else
|
267
|
+
current_value = params[name].instance_variable_get("@value")
|
268
|
+
params[name].call current_value unless @dont_update_state rescue nil # rescue in case we in middle of render
|
269
|
+
params[name]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
elsif param_type == Proc
|
273
|
+
define_method("#{name}") do |*args, &block|
|
274
|
+
params[name].call *args, &block
|
275
|
+
end
|
276
|
+
else
|
277
|
+
define_method("#{name}") do
|
278
|
+
@processed_params[name] ||= if param_type.respond_to? :_react_param_conversion
|
279
|
+
param_type._react_param_conversion params[name]
|
280
|
+
elsif param_type.is_a? Array and param_type[0].respond_to? :_react_param_conversion
|
281
|
+
params[name].collect { |param| param_type[0]._react_param_conversion param }
|
282
|
+
else
|
283
|
+
params[name]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def required_param(name, options = {})
|
290
|
+
validator.requires(name, options)
|
291
|
+
define_param_method(name, options[:type])
|
292
|
+
end
|
293
|
+
|
294
|
+
alias_method :require_param, :required_param
|
295
|
+
|
296
|
+
def optional_param(name, options = {})
|
297
|
+
validator.optional(name, options)
|
298
|
+
define_param_method(name, options[:type])
|
299
|
+
end
|
300
|
+
|
301
|
+
def define_state(*states, &block)
|
302
|
+
default_initial_value = (block and block.arity == 0) ? yield : nil
|
303
|
+
states_hash = (states.last.is_a? Hash) ? states.pop : {}
|
304
|
+
states.each { |name| states_hash[name] = default_initial_value }
|
305
|
+
(self.initial_state ||= {}).merge! states_hash
|
306
|
+
states_hash.each do |name, initial_value|
|
307
|
+
define_state_methods(self, name, &block)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def export_state(*states, &block)
|
312
|
+
default_initial_value = (block and block.arity == 0) ? yield : nil
|
313
|
+
states_hash = (states.last.is_a? Hash) ? states.pop : {}
|
314
|
+
states.each { |name| states_hash[name] = default_initial_value }
|
315
|
+
React::State.initialize_states(self, states_hash)
|
316
|
+
states_hash.each do |name, initial_value|
|
317
|
+
define_state_methods(self, name, self, &block)
|
318
|
+
define_state_methods(singleton_class, name, self, &block)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def define_state_methods(this, name, from = nil, &block)
|
323
|
+
this.define_method("#{name}") do
|
324
|
+
React::State.get_state(from || self, name)
|
325
|
+
end
|
326
|
+
this.define_method("#{name}=") do |new_state|
|
327
|
+
yield name, React::State.get_state(from || self, name), new_state if block and block.arity > 0
|
328
|
+
React::State.set_state(from || self, name, new_state)
|
329
|
+
end
|
330
|
+
this.define_method("#{name}!") do |*args|
|
331
|
+
#return unless @native
|
332
|
+
if args.count > 0
|
333
|
+
yield name, React::State.get_state(from || self, name), args[0] if block and block.arity > 0
|
334
|
+
current_value = React::State.get_state(from || self, name)
|
335
|
+
React::State.set_state(from || self, name, args[0])
|
336
|
+
current_value
|
337
|
+
else
|
338
|
+
current_state = React::State.get_state(from || self, name)
|
339
|
+
yield name, React::State.get_state(from || self, name), current_state if block and block.arity > 0
|
340
|
+
React::State.set_state(from || self, name, current_state)
|
341
|
+
React::Observable.new(current_state) do |new_value|
|
342
|
+
yield name, React::State.get_state(from || self, name), new_value if block and block.arity > 0
|
343
|
+
React::State.set_state(from || self, name, new_value)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def export_component(opts = {})
|
350
|
+
export_name = (opts[:as] || name).split("::")
|
351
|
+
first_name = export_name.first
|
352
|
+
Native(`window`)[first_name] = add_item_to_tree(Native(`window`)[first_name], [React::API.create_native_react_class(self)] + export_name[1..-1].reverse).to_n
|
353
|
+
end
|
354
|
+
|
355
|
+
def add_item_to_tree(current_tree, new_item)
|
356
|
+
if Native(current_tree).class != Native::Object or new_item.length == 1
|
357
|
+
new_item.inject do |memo, sub_name| {sub_name => memo} end
|
358
|
+
else
|
359
|
+
Native(current_tree)[new_item.last] = add_item_to_tree(Native(current_tree)[new_item.last], new_item[0..-2])
|
360
|
+
current_tree
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
module API
|
367
|
+
#include Native
|
368
|
+
|
369
|
+
alias_native :dom_node, :getDOMNode
|
370
|
+
alias_native :mounted?, :isMounted
|
371
|
+
alias_native :force_update!, :forceUpdate
|
372
|
+
|
373
|
+
def set_props(prop, &block)
|
374
|
+
raise "No native ReactComponent associated" unless @native
|
375
|
+
%x{
|
376
|
+
#{@native}.setProps(#{prop.shallow_to_n}, function(){
|
377
|
+
#{block.call if block}
|
378
|
+
});
|
379
|
+
}
|
380
|
+
end
|
381
|
+
|
382
|
+
def set_props!(prop, &block)
|
383
|
+
raise "No native ReactComponent associated" unless @native
|
384
|
+
%x{
|
385
|
+
#{@native}.replaceProps(#{prop.shallow_to_n}, function(){
|
386
|
+
#{block.call if block}
|
387
|
+
});
|
388
|
+
}
|
389
|
+
end
|
390
|
+
|
391
|
+
def set_state(state, &block)
|
392
|
+
raise "No native ReactComponent associated" unless @native
|
393
|
+
%x{
|
394
|
+
#{@native}.setState(#{state.shallow_to_n}, function(){
|
395
|
+
#{block.call if block}
|
396
|
+
});
|
397
|
+
}
|
398
|
+
end
|
399
|
+
|
400
|
+
def set_state!(state, &block)
|
401
|
+
raise "No native ReactComponent associated" unless @native
|
402
|
+
%x{
|
403
|
+
#{@native}.replaceState(#{state.shallow_to_n}, function(){
|
404
|
+
#{block.call if block}
|
405
|
+
});
|
406
|
+
}
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
end
|
411
|
+
end
|