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,87 @@
1
+ require "reactive-ruby/ext/string"
2
+
3
+ module React
4
+ class Element
5
+ include Native
6
+
7
+ alias_native :element_type, :type
8
+ alias_native :props, :props
9
+
10
+ attr_reader :type
11
+ attr_reader :properties
12
+ attr_reader :block
13
+
14
+ attr_accessor :waiting_on_resources
15
+
16
+ def initialize(native_element, type, properties, block)
17
+ @type = type
18
+ @properties = properties
19
+ @block = block
20
+ @native = native_element
21
+ end
22
+
23
+ def on(event_name)
24
+ name = event_name.to_s.event_camelize
25
+ if React::Event::BUILT_IN_EVENTS.include?("on#{name}")
26
+ self.props["on#{name}"] = %x{
27
+ function(event){
28
+ #{yield React::Event.new(`event`)}
29
+ }
30
+ }
31
+ else
32
+ self.props["_on#{name}"] = %x{
33
+ function(){
34
+ #{yield *Array(`arguments`)}
35
+ }
36
+ }
37
+ end
38
+ self
39
+ end
40
+
41
+ def method_missing(class_name, args = {}, &new_block)
42
+ class_name = class_name.split("__").collect { |s| s.gsub("_", "-") }.join("_")
43
+ new_props = properties.dup
44
+ new_props["class"] = "#{new_props['class']} #{class_name} #{args.delete("class")} #{args.delete('className')}".split(" ").uniq.join(" ")
45
+ new_props.merge! args
46
+ RenderingContext.replace(
47
+ self,
48
+ React::RenderingContext.build { React::RenderingContext.render(type, new_props, &new_block) }
49
+ )
50
+ end
51
+
52
+ def delete
53
+ RenderingContext.delete(self)
54
+ end
55
+
56
+ def children
57
+ nodes = self.props.children
58
+ class << nodes
59
+ include Enumerable
60
+
61
+ def to_n
62
+ self
63
+ end
64
+
65
+ def each(&block)
66
+ if block_given?
67
+ %x{
68
+ React.Children.forEach(#{self.to_n}, function(context){
69
+ #{block.call(React::Element.new(`context`))}
70
+ })
71
+ }
72
+ else
73
+ Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
74
+ %x{
75
+ React.Children.forEach(#{self.to_n}, function(context){
76
+ #{y << React::Element.new(`context`)}
77
+ })
78
+ }
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ nodes
85
+ end
86
+ end
87
+ 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,9 @@
1
+ class Hash
2
+ def shallow_to_n
3
+ hash = `{}`
4
+ self.map do |key, value|
5
+ `hash[#{key}] = #{value}`
6
+ end
7
+ hash
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ class String
2
+ def event_camelize
3
+ `#{self}.replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
4
+ var capitalize = true;
5
+ return capitalize ? word.substr(0,1).toUpperCase()+word.substr(1) : word;
6
+ })`
7
+ end
8
+ end
@@ -0,0 +1,223 @@
1
+ module React
2
+
3
+ module IsomorphicHelpers
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ if RUBY_ENGINE != 'opal'
10
+
11
+ def self.load_context(ctx, controller)
12
+ puts "************************** React Server Context Initialized *********************************************"
13
+ @context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller)
14
+ end
15
+
16
+ else
17
+
18
+ def self.load_context(unique_id = nil) # can be called on the client to force re-initialization for testing purposes
19
+ if !unique_id or !@context or @context.unique_id != unique_id
20
+ if on_opal_server?
21
+ message = "************************ React Prerendering Context Initialized ***********************"
22
+ else
23
+ message = "************************ React Browser Context Initialized ****************************"
24
+ end
25
+ log(message)
26
+ @context = Context.new(unique_id)
27
+ end
28
+ @context
29
+ end
30
+
31
+ end
32
+
33
+ def self.log(message, message_type = :info)
34
+ message = [message] unless message.is_a? Array
35
+ if message_type == :info
36
+ if on_opal_server?
37
+ style = 'background: #00FFFF; color: red'
38
+ else
39
+ style = 'background: #222; color: #bada55'
40
+ end
41
+ message = ["%c" + message[0], style]+message[1..-1]
42
+ `console.log.apply(console, message)`
43
+ elsif message_type == :warning
44
+ `console.warn.apply(console, message)`
45
+ else
46
+ `console.error.apply(console, message)`
47
+ end
48
+ end
49
+
50
+ if RUBY_ENGINE != 'opal'
51
+
52
+ def self.on_opal_server?
53
+ false
54
+ end
55
+
56
+ def self.on_opal_client?
57
+ false
58
+ end
59
+
60
+ else
61
+
62
+ def self.on_opal_server?
63
+ `typeof window.document === 'undefined'`
64
+ end
65
+
66
+ def self.on_opal_client?
67
+ !on_opal_server?
68
+ end
69
+
70
+ end
71
+
72
+ def log(*args)
73
+ IsomorphicHelpers.log(*args)
74
+ end
75
+
76
+ def on_opal_server?
77
+ IsomorphicHelpers.on_opal_server?
78
+ end
79
+
80
+ def on_opal_client?
81
+ IsomorphicHelpers.on_opal_client?
82
+ end
83
+
84
+ def self.prerender_footers
85
+ footer = Context.prerender_footer_blocks.collect { |block| block.call }.join("\n")
86
+ if RUBY_ENGINE != 'opal'
87
+ footer = (footer + "#{@context.send_to_opal(:prerender_footers)}") if @context
88
+ footer = footer.html_safe
89
+ end
90
+ footer
91
+ end
92
+
93
+ class Context
94
+
95
+ attr_reader :controller
96
+ attr_reader :unique_id
97
+
98
+ def self.before_first_mount_blocks
99
+ @before_first_mount_blocks ||= []
100
+ end
101
+
102
+ def self.prerender_footer_blocks
103
+ @prerender_footer_blocks ||= []
104
+ end
105
+
106
+ def initialize(unique_id, ctx = nil, controller = nil)
107
+ @unique_id = unique_id
108
+ if RUBY_ENGINE != 'opal'
109
+ @controller = controller
110
+ @ctx = ctx
111
+ ctx["ServerSideIsomorphicMethods"] = self
112
+ send_to_opal(:load_context, @unique_id)
113
+ end
114
+ self.class.before_first_mount_blocks.each { |block| block.call(self) }
115
+ end
116
+
117
+ def eval(js)
118
+ @ctx.eval(js) if @ctx
119
+ end
120
+
121
+ def send_to_opal(method, *args)
122
+ args = [1] if args.length == 0
123
+ if @ctx
124
+ unless @ctx.eval('Opal.React')
125
+ @ctx.eval(Opal::Processor.load_asset_code(::Rails.application.assets, 'components')) rescue nil
126
+ raise "No opal-react components found in the components.rb file" unless @ctx.eval('Opal.React')
127
+ end
128
+ @ctx.eval("Opal.React.IsomorphicHelpers.$#{method}(#{args.join(', ')})")
129
+ end
130
+ end
131
+
132
+ def self.register_before_first_mount_block(&block)
133
+ before_first_mount_blocks << block
134
+ end
135
+
136
+ def self.register_prerender_footer_block(&block)
137
+ prerender_footer_blocks << block
138
+ end
139
+
140
+ end
141
+
142
+ class IsomorphicProcCall
143
+
144
+ def result
145
+ @result.first if @result
146
+ end
147
+
148
+ def initialize(name, block, *args)
149
+ @name = name
150
+ block.call(self, *args)
151
+ @result ||= send_to_server(*args) if IsomorphicHelpers.on_opal_server?
152
+ end
153
+
154
+ def when_on_client(&block)
155
+ @result = [block.call] if IsomorphicHelpers.on_opal_client?
156
+ end
157
+
158
+ def send_to_server(*args)
159
+ if IsomorphicHelpers.on_opal_server?
160
+ args_as_json = args.to_json
161
+ @result = [JSON.parse(`window.ServerSideIsomorphicMethods[#{@name}](#{args_as_json})`)]
162
+ end
163
+ end
164
+
165
+ def when_on_server(&block)
166
+ @result = [block.call.to_json] unless IsomorphicHelpers.on_opal_client? or IsomorphicHelpers.on_opal_server?
167
+ end
168
+
169
+ end
170
+
171
+
172
+ module ClassMethods
173
+
174
+ def on_opal_server?
175
+ IsomorphicHelpers.on_opal_server?
176
+ end
177
+
178
+ def on_opal_client?
179
+ IsomorphicHelpers.on_opal_client?
180
+ end
181
+
182
+ def log(*args)
183
+ IsomorphicHelpers.log(*args)
184
+ end
185
+
186
+ def controller
187
+ IsomorphicHelpers.context.controller
188
+ end
189
+
190
+ def before_first_mount(&block)
191
+ React::IsomorphicHelpers::Context.register_before_first_mount_block &block
192
+ end
193
+
194
+ def prerender_footer(&block)
195
+ React::IsomorphicHelpers::Context.register_prerender_footer_block &block
196
+ end
197
+
198
+ if RUBY_ENGINE != 'opal'
199
+
200
+ def isomorphic_method(name, &block)
201
+ React::IsomorphicHelpers::Context.send(:define_method, name) do |args_as_json|
202
+ React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, *JSON.parse(args_as_json)).result
203
+ end
204
+ end
205
+
206
+ else
207
+
208
+ require 'json'
209
+
210
+ def isomorphic_method(name, &block)
211
+ self.class.send(:define_method, name) do | *args |
212
+ React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, *args).result
213
+ end
214
+ end
215
+
216
+ end
217
+
218
+
219
+ end
220
+
221
+ end
222
+
223
+ end
@@ -0,0 +1,33 @@
1
+ module React
2
+
3
+ class Observable
4
+
5
+ def initialize(value, on_change = nil, &block)
6
+ @value = value
7
+ @on_change = on_change || block
8
+ end
9
+
10
+ def method_missing(method_sym, *args, &block)
11
+ @value.send(method_sym, *args, &block).tap { |result| @on_change.call result }
12
+ end
13
+
14
+ def respond_to?(method, *args)
15
+ if [:call, :to_proc].include? method
16
+ true
17
+ else
18
+ @value.respond_to? method, *args
19
+ end
20
+ end
21
+
22
+ def call(new_value)
23
+ @on_change.call new_value
24
+ @value = new_value
25
+ end
26
+
27
+ def to_proc
28
+ lambda { |arg = @value| @on_change.call arg }
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,91 @@
1
+ module React
2
+
3
+ class RenderingContext
4
+
5
+ class << self
6
+ attr_accessor :waiting_on_resources
7
+ end
8
+
9
+ def self.render(name, *args, &block)
10
+ @buffer = [] unless @buffer
11
+ if block
12
+ element = build do
13
+ saved_waiting_on_resources = waiting_on_resources
14
+ self.waiting_on_resources = nil
15
+ result = block.call
16
+ # Todo figure out how children rendering should happen, probably should have special method that pushes children into the buffer
17
+ # i.e. render_child/render_children that takes Element/Array[Element] and does the push into the buffer
18
+ if !name and ( # !name means called from outer render so we check that it has rendered correctly
19
+ (@buffer.count > 1) or # should only render one element
20
+ (@buffer.count == 1 and @buffer.last != result) or # it should return that element
21
+ (@buffer.count == 0 and !(result.is_a? String or (result.respond_to? :acts_as_string? and result.acts_as_string?) or result.is_a? Element)) #for convience we will also convert the return value to a span if its a string
22
+ )
23
+ raise "a components render method must generate and return exactly 1 element or a string"
24
+ end
25
+
26
+ @buffer << result.to_s if result.is_a? String or (result.respond_to? :acts_as_string? and result.acts_as_string?) # For convience we push the last return value on if its a string
27
+ @buffer << result if result.is_a? Element and @buffer.count == 0
28
+ if name
29
+ buffer = @buffer.dup
30
+ React.create_element(name, *args) { buffer }.tap do |element|
31
+ element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to? :waiting_on_resources }
32
+ end
33
+ elsif @buffer.last.is_a? React::Element
34
+ @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
35
+ else
36
+ @buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
37
+ end
38
+ end
39
+ else
40
+ element = React.create_element(name, *args)
41
+ element.waiting_on_resources = waiting_on_resources
42
+ end
43
+ @buffer << element
44
+ self.waiting_on_resources = nil
45
+ element
46
+ end
47
+
48
+ def self.build(&block)
49
+ current = @buffer
50
+ @buffer = []
51
+ return_val = yield @buffer
52
+ @buffer = current
53
+ return_val
54
+ #ensure
55
+ # @buffer = current
56
+ # return_val
57
+ end
58
+
59
+ def self.as_node(element)
60
+ @buffer.delete(element)
61
+ element
62
+ end
63
+
64
+ class << self; alias_method :delete, :as_node; end
65
+
66
+ def self.replace(e1, e2)
67
+ @buffer[@buffer.index(e1)] = e2
68
+ end
69
+
70
+ end
71
+
72
+ class ::Object
73
+
74
+ alias_method :old_method_missing, :method_missing
75
+
76
+ ["span", "para", "td", "th", "while_loading"].each do |tag|
77
+ define_method(tag) do | *args, &block |
78
+ args.unshift(tag)
79
+ return self.method_missing(*args, &block) if self.is_a? React::Component
80
+ React::RenderingContext.render(*args) { self.to_s }
81
+ end
82
+ end
83
+
84
+ def br
85
+ return self.method_missing(*["br"]) if self.is_a? React::Component
86
+ React::RenderingContext.render("span") { React::RenderingContext.render(self.to_s); React::RenderingContext.render("br") }
87
+ end
88
+
89
+ end
90
+
91
+ end