reactive-ruby 0.7.28 → 0.7.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +6 -0
- data/Gemfile.lock +4 -1
- data/README.md +132 -68
- data/Rakefile +5 -2
- data/example/examples/Gemfile +0 -2
- data/example/rails-tutorial/Gemfile +3 -2
- data/lib/generators/reactive_ruby/test_app/templates/application.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +105 -0
- data/lib/rails-helpers/top_level_rails_component.rb +9 -16
- data/lib/{reactive-ruby → react}/api.rb +8 -65
- data/lib/{reactive-ruby → react}/callbacks.rb +0 -0
- data/lib/react/component.rb +266 -0
- data/lib/react/component/api.rb +48 -0
- data/lib/react/component/class_methods.rb +183 -0
- data/lib/{reactive-ruby → react}/element.rb +10 -11
- data/lib/{reactive-ruby → react}/event.rb +0 -0
- data/lib/{reactive-ruby → react}/ext/hash.rb +0 -0
- data/lib/{reactive-ruby → react}/ext/string.rb +0 -0
- data/lib/react/native_library.rb +57 -0
- data/lib/{reactive-ruby → react}/observable.rb +0 -4
- data/lib/{reactive-ruby → react}/rendering_context.rb +0 -6
- data/lib/{reactive-ruby → react}/state.rb +3 -6
- data/lib/{reactive-ruby → react}/top_level.rb +2 -3
- data/lib/react/validator.rb +127 -0
- data/lib/reactive-ruby.rb +20 -20
- data/lib/reactive-ruby/version.rb +1 -1
- data/{opal-spec/reactjs → spec}/index.html.erb +1 -1
- data/{opal-spec → spec/react}/callbacks_spec.rb +8 -9
- data/{opal-spec → spec/react}/component_spec.rb +170 -120
- data/spec/react/dsl_spec.rb +16 -0
- data/{opal-spec → spec/react}/element_spec.rb +7 -20
- data/{opal-spec → spec/react}/event_spec.rb +3 -1
- data/spec/react/native_library_spec.rb +10 -0
- data/spec/react/param_declaration_spec.rb +18 -0
- data/{opal-spec → spec/react}/react_spec.rb +3 -2
- data/spec/react/react_state_spec.rb +22 -0
- data/spec/react/top_level_component_spec.rb +68 -0
- data/{opal-spec → spec/react}/tutorial/tutorial_spec.rb +11 -13
- data/{opal-spec → spec/react}/validator_spec.rb +50 -4
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +22 -4
- data/spec/spec_helper.rb +51 -0
- data/spec/support/react/spec_helpers.rb +57 -0
- data/spec/vendor/es5-shim.min.js +6 -0
- metadata +56 -24
- data/lib/reactive-ruby/component.rb +0 -502
- data/lib/reactive-ruby/validator.rb +0 -99
- data/old-readme +0 -220
- data/opal-spec/spec_helper.rb +0 -29
@@ -1,99 +0,0 @@
|
|
1
|
-
module React
|
2
|
-
class Validator
|
3
|
-
|
4
|
-
def self.build(&block)
|
5
|
-
self.new.build(&block)
|
6
|
-
end
|
7
|
-
|
8
|
-
def build(&block)
|
9
|
-
instance_eval(&block)
|
10
|
-
self
|
11
|
-
end
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
@rules = {children: {required: false}}
|
15
|
-
end
|
16
|
-
|
17
|
-
def requires(prop_name, options = {})
|
18
|
-
rule = options
|
19
|
-
options[:required] = true
|
20
|
-
@rules[prop_name] = options
|
21
|
-
end
|
22
|
-
|
23
|
-
def optional(prop_name, options = {})
|
24
|
-
rule = options
|
25
|
-
options[:required] = false
|
26
|
-
@rules[prop_name] = options
|
27
|
-
end
|
28
|
-
|
29
|
-
def all_others(prop_name)
|
30
|
-
@all_others = {}
|
31
|
-
end
|
32
|
-
|
33
|
-
def collect_all_others(params)
|
34
|
-
Hash[params.collect { |prop_name, value| [prop_name, value] if @rules[prop_name] == nil}.compact]
|
35
|
-
end
|
36
|
-
|
37
|
-
def type_check(errors, error_prefix, object, klass, nil_allowed)
|
38
|
-
return if !object and nil_allowed
|
39
|
-
is_native = !object.respond_to?(:is_a?) rescue true
|
40
|
-
if is_native or !object.is_a? klass
|
41
|
-
unless klass.respond_to? :_react_param_conversion and klass._react_param_conversion(object, :validate_only)
|
42
|
-
errors << "#{error_prefix} could not be converted to #{klass}"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def validate(props)
|
48
|
-
errors = []
|
49
|
-
|
50
|
-
if @all_others
|
51
|
-
props.each do |prop_name, value|
|
52
|
-
@all_others[prop_name] = value if @rules[prop_name] == nil
|
53
|
-
end
|
54
|
-
else
|
55
|
-
props.keys.each do |prop_name|
|
56
|
-
errors << "Provided prop `#{prop_name}` not specified in spec" if @rules[prop_name] == nil
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
props = props.select {|key| @rules.keys.include?(key) }
|
61
|
-
|
62
|
-
# requires or not
|
63
|
-
(@rules.keys - props.keys).each do |prop_name|
|
64
|
-
errors << "Required prop `#{prop_name}` was not specified" if @rules[prop_name][:required]
|
65
|
-
end
|
66
|
-
# type checking
|
67
|
-
props.each do |prop_name, value|
|
68
|
-
if klass = @rules[prop_name][:type]
|
69
|
-
is_klass_array = klass.is_a?(Array) and klass.length > 0 rescue nil
|
70
|
-
if is_klass_array
|
71
|
-
value_is_array_like = value.respond_to?(:each_with_index) rescue nil
|
72
|
-
if value_is_array_like
|
73
|
-
value.each_with_index { |ele, i| type_check(errors, "Provided prop `#{prop_name}`[#{i}]", ele, klass[0], @rules[prop_name][:allow_nil]) }
|
74
|
-
else
|
75
|
-
errors << "Provided prop `#{prop_name}` was not an Array"
|
76
|
-
end
|
77
|
-
else
|
78
|
-
type_check(errors, "Provided prop `#{prop_name}`", value, klass, @rules[prop_name][:allow_nil])
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# values
|
84
|
-
props.each do |prop_name, value|
|
85
|
-
if values = @rules[prop_name][:values]
|
86
|
-
errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value" unless values.include?(value)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
errors
|
91
|
-
end
|
92
|
-
|
93
|
-
def default_props
|
94
|
-
@rules
|
95
|
-
.select {|key, value| value.keys.include?("default") }
|
96
|
-
.inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
data/old-readme
DELETED
@@ -1,220 +0,0 @@
|
|
1
|
-
and in your Opal application,
|
2
|
-
|
3
|
-
```ruby
|
4
|
-
require "opal-react"
|
5
|
-
require "react"
|
6
|
-
|
7
|
-
React.render(React.create_element('h1'){ "Hello World!" }, `document.body`)
|
8
|
-
```
|
9
|
-
|
10
|
-
For a complete example covering most key features, as well as integration with a server (Sinatra, etc), see setup of [Examples](example/tutorial). For additional information on integrating Opal with a server see the [official docs](http://opalrb.org/docs/) of Opal.
|
11
|
-
|
12
|
-
## React Overview
|
13
|
-
|
14
|
-
### Basics
|
15
|
-
|
16
|
-
The biggest problem with react is that its almost too simple.
|
17
|
-
|
18
|
-
In react you define components. Components are simply classes that have a "render" method. The render method "draws" a chunk of
|
19
|
-
HTML.
|
20
|
-
|
21
|
-
Here is a very simple component:
|
22
|
-
|
23
|
-
```ruby
|
24
|
-
|
25
|
-
require 'opal'
|
26
|
-
require 'opal-react'
|
27
|
-
|
28
|
-
class Hello
|
29
|
-
def render
|
30
|
-
"hello world"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# to use the component we first create an instance o
|
35
|
-
|
36
|
-
Include the `React::Component` mixin in a class to turn it into a react component
|
37
|
-
|
38
|
-
```ruby
|
39
|
-
require 'opal'
|
40
|
-
require 'opal-react'
|
41
|
-
|
42
|
-
class HelloMessage
|
43
|
-
|
44
|
-
include React::Component # will create a new component named HelloMessage
|
45
|
-
|
46
|
-
MSG = {great: 'Cool!', bad: 'Cheer up!'}
|
47
|
-
|
48
|
-
optional_param :mood
|
49
|
-
required_param :name
|
50
|
-
define_state :foo, "Default greeting"
|
51
|
-
|
52
|
-
before_mount do # you can define life cycle callbacks inline
|
53
|
-
foo! "#{name}: #{MSG[mood]}" if mood # change the state of foo using foo!, read the state using foo
|
54
|
-
end
|
55
|
-
|
56
|
-
after_mount :log # you can also define life cycle callbacks by reference to a method
|
57
|
-
|
58
|
-
def log
|
59
|
-
puts "mounted!"
|
60
|
-
end
|
61
|
-
|
62
|
-
def render # render method MUST return just one component
|
63
|
-
div do # basic dsl syntax component_name(options) { ...children... }
|
64
|
-
span { "#{foo} #{name}!" } # all html5 components are defined with lower case text
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
class App
|
70
|
-
include React::Component
|
71
|
-
|
72
|
-
def render
|
73
|
-
HelloMessage name: 'John', mood: :great # new components are accessed via the class name
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# later we will talk about nicer ways to do this: For now wait till doc is loaded
|
78
|
-
# then tell React to create an "App" and render it into the document body.
|
79
|
-
|
80
|
-
`window.onload = #{lambda {React.render(React.create_element(App), `document.body`)}}`
|
81
|
-
|
82
|
-
# -> console says: mounted!
|
83
|
-
```
|
84
|
-
|
85
|
-
* Callback of life cycle could be created through helpers `before_mount`, `after_mount`, etc
|
86
|
-
* `this.props` is accessed through method `self.params`
|
87
|
-
* Use helper method `define_state` to create setter & getter of `this.state` for you
|
88
|
-
* For the detailed mapping to the original API, see [this issue](https://github.com/zetachang/react.rb/issues/2) for reference. Complete reference will come soon.
|
89
|
-
|
90
|
-
### Element Building DSL
|
91
|
-
|
92
|
-
As a replacement of JSX, include `React::Component` and you can build `React.Element` hierarchy without all the `React.create_element` noises.
|
93
|
-
|
94
|
-
```ruby
|
95
|
-
def render
|
96
|
-
div do
|
97
|
-
h1 { "Title" }
|
98
|
-
h2 { "subtitle"}
|
99
|
-
div(class_name: 'fancy', id: 'foo') { span { "some text #{interpolation}"} }
|
100
|
-
present FancyElement, fancy_props: '10'
|
101
|
-
end
|
102
|
-
end
|
103
|
-
```
|
104
|
-
|
105
|
-
### Props validation
|
106
|
-
|
107
|
-
How about props validation? Inspired by [Grape API](https://github.com/intridea/grape), props validation rule could be created easily through `params` class method as below,
|
108
|
-
|
109
|
-
```ruby
|
110
|
-
class App
|
111
|
-
include React::Component
|
112
|
-
|
113
|
-
params do
|
114
|
-
requires :username, type: String
|
115
|
-
requires :enum, values: ['foo', 'bar', 'awesome']
|
116
|
-
requires :payload, type: Todo # yeah, a plain Ruby class
|
117
|
-
optional :filters, type: Array[String]
|
118
|
-
optional :flash_message, type: String, default: 'Welcome!' # no need to feed through `getDefaultProps`
|
119
|
-
end
|
120
|
-
|
121
|
-
def render
|
122
|
-
div
|
123
|
-
end
|
124
|
-
end
|
125
|
-
```
|
126
|
-
|
127
|
-
### Mixins
|
128
|
-
|
129
|
-
Simply create a Ruby module to encapsulate the behavior. Example below is modified from the original [React.js Exmaple on Mixin](http://facebook.github.io/react/docs/reusable-components.html#mixins). [Opal Browser](https://github.com/opal/opal-browser) syntax are used here to make it cleaner.
|
130
|
-
|
131
|
-
```ruby
|
132
|
-
module SetInterval
|
133
|
-
def self.included(base)
|
134
|
-
base.class_eval do
|
135
|
-
before_mount { @interval = [] }
|
136
|
-
before_unmount do
|
137
|
-
# abort associated timer of a component right before unmount
|
138
|
-
@interval.each { |i| i.abort }
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def set_interval(seconds, &block)
|
144
|
-
@interval << $window.every(seconds, &block)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
class TickTock
|
149
|
-
include React::Component
|
150
|
-
include SetInterval
|
151
|
-
|
152
|
-
define_state(:seconds) { 0 }
|
153
|
-
|
154
|
-
before_mount do
|
155
|
-
set_interval(1) { self.seconds = self.seconds + 1 }
|
156
|
-
set_interval(1) { puts "Tick!" }
|
157
|
-
end
|
158
|
-
|
159
|
-
def render
|
160
|
-
span do
|
161
|
-
"React has been running for: #{self.seconds}"
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
React.render(React.create_element(TickTock), $document.body.to_n)
|
167
|
-
|
168
|
-
$window.after(5) do
|
169
|
-
React.unmount_component_at_node($document.body.to_n)
|
170
|
-
end
|
171
|
-
|
172
|
-
# => Tick!
|
173
|
-
# => ... for 5 times then stop ticking after 5 seconds
|
174
|
-
```
|
175
|
-
|
176
|
-
|
177
|
-
### A Simple Component
|
178
|
-
|
179
|
-
A ruby class which define method `render` is a valid component.
|
180
|
-
|
181
|
-
```ruby
|
182
|
-
class HelloMessage
|
183
|
-
def render
|
184
|
-
React.create_element("div") { "Hello World!" }
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
puts React.render_to_static_markup(React.create_element(HelloMessage))
|
189
|
-
|
190
|
-
# => '<div>Hello World!</div>'
|
191
|
-
```
|
192
|
-
|
193
|
-
### More complicated one
|
194
|
-
|
195
|
-
To hook into native ReactComponent life cycle, the native `this` will be passed to the class's initializer. And all corresponding life cycle methods (`componentDidMount`, etc) will be invoked on the instance using the snake-case method name.
|
196
|
-
|
197
|
-
```ruby
|
198
|
-
class HelloMessage
|
199
|
-
def initialize(native)
|
200
|
-
@native = Native(native)
|
201
|
-
end
|
202
|
-
|
203
|
-
def component_will_mount
|
204
|
-
puts "will mount!"
|
205
|
-
end
|
206
|
-
|
207
|
-
def render
|
208
|
-
React.create_element("div") { "Hello #{@native[:props][:name]}!" }
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
puts React.render_to_static_markup(React.create_element(HelloMessage, name: 'John'))
|
213
|
-
|
214
|
-
# => will_mount!
|
215
|
-
# => '<div>Hello John!</div>'
|
216
|
-
```
|
217
|
-
## Example
|
218
|
-
|
219
|
-
* React Tutorial: see [example/react-tutorial](example/react-tutorial), the original CommentBox example.
|
220
|
-
* TodoMVC: see [example/todos](example/todos), your beloved TodoMVC <3.
|
data/opal-spec/spec_helper.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'react'
|
2
|
-
|
3
|
-
module ReactTestHelpers
|
4
|
-
`var ReactTestUtils = React.addons.TestUtils`
|
5
|
-
|
6
|
-
def renderToDocument(type, options = {})
|
7
|
-
element = React.create_element(type, options)
|
8
|
-
return renderElementToDocument(element)
|
9
|
-
end
|
10
|
-
|
11
|
-
def renderElementToDocument(element)
|
12
|
-
instance = Native(`ReactTestUtils.renderIntoDocument(#{element.to_n})`)
|
13
|
-
instance.class.include(React::Component::API)
|
14
|
-
return instance
|
15
|
-
end
|
16
|
-
|
17
|
-
def simulateEvent(event, element, params = {})
|
18
|
-
simulator = Native(`ReactTestUtils.Simulate`)
|
19
|
-
simulator[event.to_s].call(`#{element.to_n}.getDOMNode()`, params)
|
20
|
-
end
|
21
|
-
|
22
|
-
def isElementOfType(element, type)
|
23
|
-
`React.addons.TestUtils.isElementOfType(#{element.to_n}, #{type.cached_component_class})`
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
RSpec.configure do |config|
|
28
|
-
config.include ReactTestHelpers
|
29
|
-
end
|