reactive-ruby 0.7.28 → 0.7.29
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.
- 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
|