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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +53 -0
  5. data/LICENSE +19 -0
  6. data/README.md +303 -0
  7. data/config.ru +15 -0
  8. data/example/examples/Gemfile +7 -0
  9. data/example/examples/Gemfile.lock +45 -0
  10. data/example/examples/config.ru +44 -0
  11. data/example/examples/hello.js.rb +43 -0
  12. data/example/react-tutorial/Gemfile +7 -0
  13. data/example/react-tutorial/Gemfile.lock +49 -0
  14. data/example/react-tutorial/README.md +8 -0
  15. data/example/react-tutorial/_comments.json +14 -0
  16. data/example/react-tutorial/config.ru +63 -0
  17. data/example/react-tutorial/example.js.rb +290 -0
  18. data/example/react-tutorial/public/base.css +62 -0
  19. data/example/todos/Gemfile +11 -0
  20. data/example/todos/Gemfile.lock +84 -0
  21. data/example/todos/README.md +37 -0
  22. data/example/todos/Rakefile +8 -0
  23. data/example/todos/app/application.rb +22 -0
  24. data/example/todos/app/components/app.react.rb +61 -0
  25. data/example/todos/app/components/footer.react.rb +31 -0
  26. data/example/todos/app/components/todo_item.react.rb +46 -0
  27. data/example/todos/app/components/todo_list.react.rb +25 -0
  28. data/example/todos/app/models/todo.rb +19 -0
  29. data/example/todos/config.ru +14 -0
  30. data/example/todos/index.html.haml +16 -0
  31. data/example/todos/spec/todo_spec.rb +28 -0
  32. data/example/todos/vendor/base.css +410 -0
  33. data/example/todos/vendor/bg.png +0 -0
  34. data/example/todos/vendor/jquery.js +4 -0
  35. data/lib/rails-helpers/react_component.rb +32 -0
  36. data/lib/reactive-ruby.rb +23 -0
  37. data/lib/reactive-ruby/api.rb +177 -0
  38. data/lib/reactive-ruby/callbacks.rb +35 -0
  39. data/lib/reactive-ruby/component.rb +411 -0
  40. data/lib/reactive-ruby/element.rb +87 -0
  41. data/lib/reactive-ruby/event.rb +76 -0
  42. data/lib/reactive-ruby/ext/hash.rb +9 -0
  43. data/lib/reactive-ruby/ext/string.rb +8 -0
  44. data/lib/reactive-ruby/isomorphic_helpers.rb +223 -0
  45. data/lib/reactive-ruby/observable.rb +33 -0
  46. data/lib/reactive-ruby/rendering_context.rb +91 -0
  47. data/lib/reactive-ruby/serializers.rb +15 -0
  48. data/lib/reactive-ruby/state.rb +90 -0
  49. data/lib/reactive-ruby/top_level.rb +53 -0
  50. data/lib/reactive-ruby/validator.rb +83 -0
  51. data/lib/reactive-ruby/version.rb +3 -0
  52. data/logo1.png +0 -0
  53. data/logo2.png +0 -0
  54. data/logo3.png +0 -0
  55. data/reactive-ruby.gemspec +25 -0
  56. data/spec/callbacks_spec.rb +107 -0
  57. data/spec/component_spec.rb +597 -0
  58. data/spec/element_spec.rb +60 -0
  59. data/spec/event_spec.rb +22 -0
  60. data/spec/react_spec.rb +209 -0
  61. data/spec/reactjs/index.html.erb +11 -0
  62. data/spec/spec_helper.rb +29 -0
  63. data/spec/tutorial/tutorial_spec.rb +37 -0
  64. data/spec/validator_spec.rb +79 -0
  65. data/vendor/active_support/core_ext/array/extract_options.rb +29 -0
  66. data/vendor/active_support/core_ext/class/attribute.rb +127 -0
  67. data/vendor/active_support/core_ext/kernel/singleton_class.rb +13 -0
  68. data/vendor/active_support/core_ext/module/remove_method.rb +11 -0
  69. 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