react.rb 0.0.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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +48 -0
  5. data/LICENSE +19 -0
  6. data/README.md +166 -0
  7. data/config.ru +15 -0
  8. data/example/react-tutorial/Gemfile +6 -0
  9. data/example/react-tutorial/Gemfile.lock +45 -0
  10. data/example/react-tutorial/README.md +8 -0
  11. data/example/react-tutorial/_comments.json +6 -0
  12. data/example/react-tutorial/config.ru +55 -0
  13. data/example/react-tutorial/example.rb +104 -0
  14. data/example/react-tutorial/public/base.css +62 -0
  15. data/example/todos/Gemfile +11 -0
  16. data/example/todos/Gemfile.lock +84 -0
  17. data/example/todos/README.md +37 -0
  18. data/example/todos/Rakefile +8 -0
  19. data/example/todos/app/application.rb +22 -0
  20. data/example/todos/app/components/app.react.rb +61 -0
  21. data/example/todos/app/components/footer.react.rb +31 -0
  22. data/example/todos/app/components/todo_item.react.rb +46 -0
  23. data/example/todos/app/components/todo_list.react.rb +25 -0
  24. data/example/todos/app/models/todo.rb +19 -0
  25. data/example/todos/config.ru +14 -0
  26. data/example/todos/index.html.haml +16 -0
  27. data/example/todos/spec/todo_spec.rb +28 -0
  28. data/example/todos/vendor/base.css +410 -0
  29. data/example/todos/vendor/bg.png +0 -0
  30. data/example/todos/vendor/jquery.js +4 -0
  31. data/lib/react.rb +16 -0
  32. data/lib/react/api.rb +95 -0
  33. data/lib/react/callbacks.rb +35 -0
  34. data/lib/react/component.rb +197 -0
  35. data/lib/react/element.rb +63 -0
  36. data/lib/react/event.rb +76 -0
  37. data/lib/react/ext/hash.rb +9 -0
  38. data/lib/react/ext/string.rb +8 -0
  39. data/lib/react/top_level.rb +50 -0
  40. data/lib/react/validator.rb +65 -0
  41. data/lib/react/version.rb +3 -0
  42. data/react.rb.gemspec +24 -0
  43. data/spec/callbacks_spec.rb +107 -0
  44. data/spec/component_spec.rb +556 -0
  45. data/spec/element_spec.rb +60 -0
  46. data/spec/event_spec.rb +22 -0
  47. data/spec/react_spec.rb +168 -0
  48. data/spec/reactjs/index.html.erb +10 -0
  49. data/spec/spec_helper.rb +29 -0
  50. data/spec/validator_spec.rb +79 -0
  51. data/vendor/active_support/core_ext/array/extract_options.rb +29 -0
  52. data/vendor/active_support/core_ext/class/attribute.rb +127 -0
  53. data/vendor/active_support/core_ext/kernel/singleton_class.rb +13 -0
  54. data/vendor/active_support/core_ext/module/remove_method.rb +11 -0
  55. metadata +189 -0
@@ -0,0 +1,95 @@
1
+ module React
2
+ class API
3
+ @@component_classes = {}
4
+
5
+ def self.create_element(type, properties = {}, &block)
6
+ params = []
7
+
8
+ # Component Spec or Nomral DOM
9
+ if type.kind_of?(Class)
10
+ raise "Provided class should define `render` method" if !(type.method_defined? :render)
11
+ @@component_classes[type.to_s] ||= %x{
12
+ React.createClass({
13
+ propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
14
+ getDefaultProps: function(){
15
+ return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
16
+ },
17
+ getInitialState: function(){
18
+ return #{type.respond_to?(:initial_state) ? type.initial_state.to_n : `{}`};
19
+ },
20
+ componentWillMount: function() {
21
+ instance = #{type.new(`this`)};
22
+ return #{`instance`.component_will_mount if type.method_defined? :component_will_mount};
23
+ },
24
+ componentDidMount: function() {
25
+ instance = #{type.new(`this`)};
26
+ return #{`instance`.component_did_mount if type.method_defined? :component_did_mount};
27
+ },
28
+ componentWillReceiveProps: function(next_props) {
29
+ instance = #{type.new(`this`)};
30
+ return #{`instance`.component_will_receive_props(`next_props`) if type.method_defined? :component_will_receive_props};
31
+ },
32
+ shouldComponentUpdate: function(next_props, next_state) {
33
+ instance = #{type.new(`this`)};
34
+ return #{`instance`.should_component_update?(`next_props`, `next_state`) if type.method_defined? :should_component_update?};
35
+ },
36
+ componentWillUpdate: function(next_props, next_state) {
37
+ instance = #{type.new(`this`)};
38
+ return #{`instance`.component_will_update(`next_props`, `next_state`) if type.method_defined? :component_will_update};
39
+ },
40
+ componentDidUpdate: function(prev_props, prev_state) {
41
+ instance = #{type.new(`this`)};
42
+ return #{`instance`.component_did_update(`prev_props`, `prev_state`) if type.method_defined? :component_did_update};
43
+ },
44
+ componentWillUnmount: function() {
45
+ instance = #{type.new(`this`)};
46
+ return #{`instance`.component_will_unmount if type.method_defined? :component_will_unmount};
47
+ },
48
+ render: function() {
49
+ instance = #{type.new(`this`)};
50
+ return #{`instance`.render.to_n};
51
+ }
52
+ })
53
+ }
54
+
55
+ params << @@component_classes[type.to_s]
56
+ else
57
+ raise "#{type} not implemented" unless HTML_TAGS.include?(type)
58
+ params << type
59
+ end
60
+
61
+ # Passed in properties
62
+ props = {}
63
+ properties.map do |key, value|
64
+ if key == "class_name" && value.is_a?(Hash)
65
+ props[lower_camelize(key)] = `React.addons.classSet(#{value.to_n})`
66
+ else
67
+ props[React::ATTRIBUTES.include?(lower_camelize(key)) ? lower_camelize(key) : key] = value
68
+ end
69
+ end
70
+ params << props.shallow_to_n
71
+
72
+ # Children Nodes
73
+ if block_given?
74
+ children = [yield].flatten.each do |ele|
75
+ params << ele.to_n
76
+ end
77
+ end
78
+
79
+ return React::Element.new(`React.createElement.apply(null, #{params})`)
80
+ end
81
+
82
+ def self.clear_component_class_cache
83
+ @@component_classes = {}
84
+ end
85
+
86
+ private
87
+
88
+ def self.lower_camelize(snake_cased_word)
89
+ words = snake_cased_word.split("_")
90
+ result = [words.first]
91
+ result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] })
92
+ result.join("")
93
+ end
94
+ end
95
+ 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,197 @@
1
+ require "./ext/string"
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'react/callbacks'
4
+ require "react/ext/hash"
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
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 initialize(native_element)
24
+ @native = native_element
25
+ end
26
+
27
+ def params
28
+ Hash.new(`#{@native}.props`)
29
+ end
30
+
31
+ def refs
32
+ Hash.new(`#{@native}.refs`)
33
+ end
34
+
35
+ def state
36
+ raise "No native ReactComponent associated" unless @native
37
+ Hash.new(`#{@native}.state`)
38
+ end
39
+
40
+ def emit(event_name, *args)
41
+ self.params["_on#{event_name.to_s.event_camelize}"].call(*args)
42
+ end
43
+
44
+ def component_will_mount
45
+ self.run_callback(:before_mount)
46
+ end
47
+
48
+ def component_did_mount
49
+ self.run_callback(:after_mount)
50
+ end
51
+
52
+ def component_will_receive_props(next_props)
53
+ self.run_callback(:before_receive_props, Hash.new(next_props))
54
+ end
55
+
56
+ def should_component_update?(next_props, next_state)
57
+ self.respond_to?(:needs_update?) ? self.needs_update?(Hash.new(next_props), Hash.new(next_state)) : true
58
+ end
59
+
60
+ def component_will_update(next_props, next_state)
61
+ self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state))
62
+ end
63
+
64
+ def component_did_update(prev_props, prev_state)
65
+ self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
66
+ end
67
+
68
+ def component_will_unmount
69
+ self.run_callback(:before_unmount)
70
+ end
71
+
72
+ def method_missing(name, *args, &block)
73
+ unless (React::HTML_TAGS.include?(name) || name == 'present')
74
+ return super
75
+ end
76
+
77
+ if name == "present"
78
+ name = args.shift
79
+ end
80
+
81
+ @buffer = [] unless @buffer
82
+ if block
83
+ current = @buffer
84
+ @buffer = []
85
+ result = block.call
86
+ element = React.create_element(name, *args) { @buffer.count == 0 ? result : @buffer }
87
+ @buffer = current
88
+ else
89
+ element = React.create_element(name, *args)
90
+ end
91
+
92
+ @buffer << element
93
+ element
94
+ end
95
+
96
+
97
+ module ClassMethods
98
+ def prop_types
99
+ if self.validator
100
+ {
101
+ _componentValidator: %x{
102
+ function(props, propName, componentName) {
103
+ var errors = #{validator.validate(Hash.new(`props`))};
104
+ var error = new Error(#{"In component `" + self.name + "`\n" + `errors`.join("\n")});
105
+ return #{`errors`.count > 0 ? `error` : `undefined`};
106
+ }
107
+ }
108
+ }
109
+ else
110
+ {}
111
+ end
112
+ end
113
+
114
+ def initial_state
115
+ self.init_state || {}
116
+ end
117
+
118
+ def default_props
119
+ self.validator ? self.validator.default_props : {}
120
+ end
121
+
122
+ def params(&block)
123
+ self.validator = React::Validator.build(&block)
124
+ end
125
+
126
+ def define_state(*states)
127
+ raise "Block could be only given when define exactly one state" if block_given? && states.count > 1
128
+
129
+ self.init_state = {} unless self.init_state
130
+
131
+ if block_given?
132
+ self.init_state[states[0]] = yield
133
+ end
134
+ states.each do |name|
135
+ # getter
136
+ define_method("#{name}") do
137
+ return unless @native
138
+ self.state[name]
139
+ end
140
+ # setter
141
+ define_method("#{name}=") do |new_state|
142
+ return unless @native
143
+ hash = {}
144
+ hash[name] = new_state
145
+ self.set_state(hash)
146
+
147
+ new_state
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ module API
154
+ include Native
155
+
156
+ alias_native :dom_node, :getDOMNode
157
+ alias_native :mounted?, :isMounted
158
+ alias_native :force_update!, :forceUpdate
159
+
160
+ def set_props(prop, &block)
161
+ raise "No native ReactComponent associated" unless @native
162
+ %x{
163
+ #{@native}.setProps(#{prop.shallow_to_n}, function(){
164
+ #{block.call if block}
165
+ });
166
+ }
167
+ end
168
+
169
+ def set_props!(prop, &block)
170
+ raise "No native ReactComponent associated" unless @native
171
+ %x{
172
+ #{@native}.replaceProps(#{prop.shallow_to_n}, function(){
173
+ #{block.call if block}
174
+ });
175
+ }
176
+ end
177
+
178
+ def set_state(state, &block)
179
+ raise "No native ReactComponent associated" unless @native
180
+ %x{
181
+ #{@native}.setState(#{state.shallow_to_n}, function(){
182
+ #{block.call if block}
183
+ });
184
+ }
185
+ end
186
+
187
+ def set_state!(state, &block)
188
+ raise "No native ReactComponent associated" unless @native
189
+ %x{
190
+ #{@native}.replaceState(#{state.shallow_to_n}, function(){
191
+ #{block.call if block}
192
+ });
193
+ }
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,63 @@
1
+ require "./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
+ def initialize(native_element)
11
+ @native = native_element
12
+ end
13
+
14
+ def on(event_name)
15
+ name = event_name.to_s.event_camelize
16
+ if React::Event::BUILT_IN_EVENTS.include?("on#{name}")
17
+ self.props["on#{name}"] = %x{
18
+ function(event){
19
+ #{yield React::Event.new(`event`)}
20
+ }
21
+ }
22
+ else
23
+ self.props["_on#{name}"] = %x{
24
+ function(){
25
+ #{yield *Array(`arguments`)}
26
+ }
27
+ }
28
+ end
29
+ self
30
+ end
31
+
32
+ def children
33
+ nodes = self.props.children
34
+ class << nodes
35
+ include Enumerable
36
+
37
+ def to_n
38
+ self
39
+ end
40
+
41
+ def each(&block)
42
+ if block_given?
43
+ %x{
44
+ React.Children.forEach(#{self.to_n}, function(context){
45
+ #{block.call(React::Element.new(`context`))}
46
+ })
47
+ }
48
+ else
49
+ Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
50
+ %x{
51
+ React.Children.forEach(#{self.to_n}, function(context){
52
+ #{y << React::Element.new(`context`)}
53
+ })
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ nodes
61
+ end
62
+ end
63
+ 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