react.rb 0.0.1 → 0.0.2
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/README.md +60 -6
- data/lib/react/api.rb +17 -8
- data/lib/react/component.rb +13 -1
- data/lib/react/version.rb +2 -2
- data/react.rb.gemspec +1 -0
- data/spec/component_spec.rb +41 -0
- data/spec/react_spec.rb +41 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dbadd7b8df4dbcfb57fd8b20da802307d913740
|
4
|
+
data.tar.gz: 649ec20f57caec86097ad8abdfd2075abf3d8997
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ce9d3607ecca7d4891b0ba96dca4ebe5bcd950ff8092a3bd569cbd0da536a4c0f2bbc96faa6e17361875f07b0620bfd3aa13958edc96d7c51225af17d5ed053
|
7
|
+
data.tar.gz: a351a443594be61d7759cf02242a42424e2048a520aa2cac19208d533b358b50d33b9f09d37a1e3dbf32218f95d8d797b2ec19915dd9215d81b3a4be12820fec
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# React.rb
|
2
2
|
|
3
|
-
**React.rb is
|
3
|
+
**React.rb is an [Opal Ruby](http://opalrb.org) wrapper of [React.js library](http://facebook.github.io/react/)**.
|
4
4
|
|
5
5
|
It let you write reactive UI component with Ruby's elegancy and compiled to run in Javascript. :heart:
|
6
6
|
|
@@ -16,6 +16,7 @@ and in your Opal application,
|
|
16
16
|
```ruby
|
17
17
|
require "opal"
|
18
18
|
require "react"
|
19
|
+
|
19
20
|
React.render(React.create_element('h1'){ "Hello World!" }, `document.body`)
|
20
21
|
```
|
21
22
|
|
@@ -34,12 +35,14 @@ class HelloMessage
|
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
React.
|
38
|
+
puts React.render_to_static_markup(React.create_element(HelloMessage))
|
39
|
+
|
40
|
+
# => '<div>Hello World!</div>'
|
38
41
|
```
|
39
42
|
|
40
43
|
### More complicated one
|
41
44
|
|
42
|
-
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
|
45
|
+
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.
|
43
46
|
|
44
47
|
```ruby
|
45
48
|
class HelloMessage
|
@@ -64,7 +67,7 @@ puts React.render_to_static_markup(React.create_element(HelloMessage, name: 'Joh
|
|
64
67
|
|
65
68
|
### React::Component
|
66
69
|
|
67
|
-
Hey, we are using Ruby, simply include `React::Component` to save your typing and have some handy
|
70
|
+
Hey, we are using Ruby, simply include `React::Component` to save your typing and have some handy methods defined.
|
68
71
|
|
69
72
|
```ruby
|
70
73
|
class HelloMessage
|
@@ -99,8 +102,11 @@ class App
|
|
99
102
|
end
|
100
103
|
|
101
104
|
puts React.render_to_static_markup(React.create_element(App))
|
105
|
+
|
102
106
|
# => '<div><span>Default greeting: Cool! John!</span></div>'
|
107
|
+
|
103
108
|
React.render(React.create_element(App), `document.body`)
|
109
|
+
|
104
110
|
# mounted!
|
105
111
|
```
|
106
112
|
|
@@ -111,7 +117,7 @@ React.render(React.create_element(App), `document.body`)
|
|
111
117
|
|
112
118
|
### Props validation
|
113
119
|
|
114
|
-
How about props validation? Inspired
|
120
|
+
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,
|
115
121
|
|
116
122
|
```ruby
|
117
123
|
class App
|
@@ -131,6 +137,55 @@ class App
|
|
131
137
|
end
|
132
138
|
```
|
133
139
|
|
140
|
+
### Mixins
|
141
|
+
|
142
|
+
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.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
module SetInterval
|
146
|
+
def self.included(base)
|
147
|
+
base.class_eval do
|
148
|
+
before_mount { @interval = [] }
|
149
|
+
before_unmount do
|
150
|
+
# abort associated timer of a component right before unmount
|
151
|
+
@interval.each { |i| i.abort }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def set_interval(seconds, &block)
|
157
|
+
@interval << $window.every(seconds, &block)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class TickTock
|
162
|
+
include React::Component
|
163
|
+
include SetInterval
|
164
|
+
|
165
|
+
define_state(:seconds) { 0 }
|
166
|
+
|
167
|
+
before_mount do
|
168
|
+
set_interval(1) { self.seconds = self.seconds + 1 }
|
169
|
+
set_interval(1) { puts "Tick!" }
|
170
|
+
end
|
171
|
+
|
172
|
+
def render
|
173
|
+
span do
|
174
|
+
"React has been running for: #{self.seconds}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
React.render(React.create_element(TickTock), $document.body.to_n)
|
180
|
+
|
181
|
+
$window.after(5) do
|
182
|
+
React.unmount_component_at_node($document.body.to_n)
|
183
|
+
end
|
184
|
+
|
185
|
+
# => Tick!
|
186
|
+
# => ... for 5 times then stop ticking after 5 seconds
|
187
|
+
```
|
188
|
+
|
134
189
|
## Example
|
135
190
|
|
136
191
|
* React Tutorial: see [example/react-tutorial](example/react-tutorial), the original CommentBox example.
|
@@ -138,7 +193,6 @@ end
|
|
138
193
|
|
139
194
|
## TODOS
|
140
195
|
|
141
|
-
* Mixins examples
|
142
196
|
* Documentation
|
143
197
|
* API wrapping coverage of the original js library (pretty close though)
|
144
198
|
* React Native?
|
data/lib/react/api.rb
CHANGED
@@ -18,35 +18,44 @@ module React
|
|
18
18
|
return #{type.respond_to?(:initial_state) ? type.initial_state.to_n : `{}`};
|
19
19
|
},
|
20
20
|
componentWillMount: function() {
|
21
|
-
instance =
|
21
|
+
var instance = this._getOpalInstance.apply(this);
|
22
22
|
return #{`instance`.component_will_mount if type.method_defined? :component_will_mount};
|
23
23
|
},
|
24
24
|
componentDidMount: function() {
|
25
|
-
instance =
|
25
|
+
var instance = this._getOpalInstance.apply(this);
|
26
26
|
return #{`instance`.component_did_mount if type.method_defined? :component_did_mount};
|
27
27
|
},
|
28
28
|
componentWillReceiveProps: function(next_props) {
|
29
|
-
instance =
|
29
|
+
var instance = this._getOpalInstance.apply(this);
|
30
30
|
return #{`instance`.component_will_receive_props(`next_props`) if type.method_defined? :component_will_receive_props};
|
31
31
|
},
|
32
32
|
shouldComponentUpdate: function(next_props, next_state) {
|
33
|
-
instance =
|
33
|
+
var instance = this._getOpalInstance.apply(this);
|
34
34
|
return #{`instance`.should_component_update?(`next_props`, `next_state`) if type.method_defined? :should_component_update?};
|
35
35
|
},
|
36
36
|
componentWillUpdate: function(next_props, next_state) {
|
37
|
-
instance =
|
37
|
+
var instance = this._getOpalInstance.apply(this);
|
38
38
|
return #{`instance`.component_will_update(`next_props`, `next_state`) if type.method_defined? :component_will_update};
|
39
39
|
},
|
40
40
|
componentDidUpdate: function(prev_props, prev_state) {
|
41
|
-
instance =
|
41
|
+
var instance = this._getOpalInstance.apply(this);
|
42
42
|
return #{`instance`.component_did_update(`prev_props`, `prev_state`) if type.method_defined? :component_did_update};
|
43
43
|
},
|
44
44
|
componentWillUnmount: function() {
|
45
|
-
instance =
|
45
|
+
var instance = this._getOpalInstance.apply(this);
|
46
46
|
return #{`instance`.component_will_unmount if type.method_defined? :component_will_unmount};
|
47
47
|
},
|
48
|
+
_getOpalInstance: function() {
|
49
|
+
if (this.__opalInstance == undefined) {
|
50
|
+
var instance = #{type.new(`this`)};
|
51
|
+
} else {
|
52
|
+
var instance = this.__opalInstance;
|
53
|
+
}
|
54
|
+
this.__opalInstance = instance;
|
55
|
+
return instance;
|
56
|
+
},
|
48
57
|
render: function() {
|
49
|
-
instance =
|
58
|
+
var instance = this._getOpalInstance.apply(this);
|
50
59
|
return #{`instance`.render.to_n};
|
51
60
|
}
|
52
61
|
})
|
data/lib/react/component.rb
CHANGED
@@ -69,8 +69,16 @@ module React
|
|
69
69
|
self.run_callback(:before_unmount)
|
70
70
|
end
|
71
71
|
|
72
|
+
def p(*args, &block)
|
73
|
+
if block || args.count == 0 || (args.count == 1 && args.first.is_a?(Hash))
|
74
|
+
_p_tag(*args, &block)
|
75
|
+
else
|
76
|
+
Kernel.p(*args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
72
80
|
def method_missing(name, *args, &block)
|
73
|
-
unless (React::HTML_TAGS.include?(name) || name == 'present')
|
81
|
+
unless (React::HTML_TAGS.include?(name) || name == 'present' || name == '_p_tag')
|
74
82
|
return super
|
75
83
|
end
|
76
84
|
|
@@ -78,6 +86,10 @@ module React
|
|
78
86
|
name = args.shift
|
79
87
|
end
|
80
88
|
|
89
|
+
if name == "_p_tag"
|
90
|
+
name = "p"
|
91
|
+
end
|
92
|
+
|
81
93
|
@buffer = [] unless @buffer
|
82
94
|
if block
|
83
95
|
current = @buffer
|
data/lib/react/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module React
|
2
|
-
VERSION = "0.0.
|
3
|
-
end
|
2
|
+
VERSION = "0.0.2"
|
3
|
+
end
|
data/react.rb.gemspec
CHANGED
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.email = 'zeta11235813@gmail.com'
|
9
9
|
s.homepage = 'https://github.com/zetachang/react.rb'
|
10
10
|
s.summary = 'Opal Ruby wrapper of React.js library.'
|
11
|
+
s.license = 'MIT'
|
11
12
|
s.description = "Write reactive UI component with Ruby's elegancy and compiled to run in Javascript."
|
12
13
|
|
13
14
|
s.files = `git ls-files`.split("\n")
|
data/spec/component_spec.rb
CHANGED
@@ -536,6 +536,47 @@ describe React::Component do
|
|
536
536
|
element = React.create_element(Foo)
|
537
537
|
expect(React.render_to_static_markup(element)).to eq("<div></div>")
|
538
538
|
end
|
539
|
+
|
540
|
+
it "should redefine `p` to make method missing work" do
|
541
|
+
stub_const 'Foo', Class.new
|
542
|
+
Foo.class_eval do
|
543
|
+
include React::Component
|
544
|
+
|
545
|
+
def render
|
546
|
+
p(class_name: "foo") do
|
547
|
+
p
|
548
|
+
div { "lorem ipsum" }
|
549
|
+
p(id: "10")
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
element = React.create_element(Foo)
|
555
|
+
expect(React.render_to_static_markup(element)).to eq("<p class=\"foo\"><p></p><div>lorem ipsum</div><p id=\"10\"></p></p>")
|
556
|
+
end
|
557
|
+
|
558
|
+
it "should only override `p` in render context" do
|
559
|
+
stub_const 'Foo', Class.new
|
560
|
+
Foo.class_eval do
|
561
|
+
include React::Component
|
562
|
+
|
563
|
+
before_mount do
|
564
|
+
p "first"
|
565
|
+
end
|
566
|
+
|
567
|
+
after_mount do
|
568
|
+
p "second"
|
569
|
+
end
|
570
|
+
|
571
|
+
def render
|
572
|
+
div
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
expect(Kernel).to receive(:p).with("first")
|
577
|
+
expect(Kernel).to receive(:p).with("second")
|
578
|
+
renderToDocument(Foo)
|
579
|
+
end
|
539
580
|
end
|
540
581
|
|
541
582
|
describe "isMounted()" do
|
data/spec/react_spec.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe React do
|
4
|
+
after(:each) do
|
5
|
+
React::API.clear_component_class_cache
|
6
|
+
end
|
7
|
+
|
4
8
|
describe "is_valid_element" do
|
5
9
|
it "should return true if passed a valid element" do
|
6
10
|
element = React::Element.new(`React.createElement('div')`)
|
@@ -79,6 +83,43 @@ describe React do
|
|
79
83
|
it "should raise error if provided class doesn't defined `render`" do
|
80
84
|
expect { React.create_element(Array) }.to raise_error
|
81
85
|
end
|
86
|
+
|
87
|
+
it "should use the same instance for the same ReactComponent" do
|
88
|
+
Foo.class_eval do
|
89
|
+
attr_accessor :a
|
90
|
+
def initialize(n)
|
91
|
+
self.a = 10
|
92
|
+
end
|
93
|
+
|
94
|
+
def component_will_mount
|
95
|
+
self.a = 20
|
96
|
+
end
|
97
|
+
|
98
|
+
def render
|
99
|
+
React.create_element("div") { self.a.to_s }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
expect(React.render_to_static_markup(React.create_element(Foo))).to eq("<div>20</div>")
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should match the instance cycle to ReactComponent life cycle" do
|
107
|
+
`var count = 0;`
|
108
|
+
|
109
|
+
Foo.class_eval do
|
110
|
+
def initialize
|
111
|
+
`count = count + 1;`
|
112
|
+
end
|
113
|
+
def render
|
114
|
+
React.create_element("div")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
renderToDocument(Foo)
|
119
|
+
renderToDocument(Foo)
|
120
|
+
|
121
|
+
expect(`count`).to eq(2)
|
122
|
+
end
|
82
123
|
end
|
83
124
|
|
84
125
|
describe "create element with properties" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: react.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Chang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opal
|
@@ -155,7 +155,8 @@ files:
|
|
155
155
|
- vendor/active_support/core_ext/kernel/singleton_class.rb
|
156
156
|
- vendor/active_support/core_ext/module/remove_method.rb
|
157
157
|
homepage: https://github.com/zetachang/react.rb
|
158
|
-
licenses:
|
158
|
+
licenses:
|
159
|
+
- MIT
|
159
160
|
metadata: {}
|
160
161
|
post_install_message:
|
161
162
|
rdoc_options: []
|