react.rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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