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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d41b80ffb70a0cfd51d99327653c12283fc4507c
4
+ data.tar.gz: 7cf663a17b5c5ff75ada548badd48b47394a96b1
5
+ SHA512:
6
+ metadata.gz: 0d1ecf283bed8249da9f488f5ceb37fdd398cc06ac7d83643be535b13df18a4e8bb69d000fc25e22b44189738e7170f7e2af9b2c6cc1827fd8410e6095cc9590
7
+ data.tar.gz: a785b76558b4c9832aa8ba53db2fbf28a07a0993b5bf83e32a4042157293a9ba1c658296d0733c5855cc6d108ca1215cb9ec005392a8485893cd41fbf1a7e984
@@ -0,0 +1,30 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+
5
+ # Runtime data
6
+ pids
7
+ *.pid
8
+ *.seed
9
+
10
+ # Directory for instrumented libs generated by jscoverage/JSCover
11
+ lib-cov
12
+
13
+ # Coverage directory used by tools like istanbul
14
+ coverage
15
+
16
+ # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17
+ .grunt
18
+
19
+ # node-waf configuration
20
+ .lock-wscript
21
+
22
+ # Compiled binary addons (http://nodejs.org/api/addons.html)
23
+ build/Release
24
+
25
+ # Dependency directory
26
+ # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
27
+ node_modules
28
+
29
+ # Ignore bundler config.
30
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ react.rb (0.0.1)
5
+ opal (~> 0.6.0)
6
+ opal-activesupport
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ hike (1.2.3)
12
+ json (1.8.2)
13
+ multi_json (1.10.1)
14
+ opal (0.6.3)
15
+ source_map
16
+ sprockets
17
+ opal-activesupport (0.1.0)
18
+ opal (>= 0.5.0, < 1.0.0)
19
+ opal-jquery (0.2.0)
20
+ opal (>= 0.5.0, < 1.0.0)
21
+ opal-rspec (0.3.0.beta3)
22
+ opal (>= 0.6.0, < 1.0.0)
23
+ rack (1.6.0)
24
+ rack-protection (1.5.3)
25
+ rack
26
+ react-source (0.12.2)
27
+ sinatra (1.4.5)
28
+ rack (~> 1.4)
29
+ rack-protection (~> 1.4)
30
+ tilt (~> 1.3, >= 1.3.4)
31
+ source_map (3.0.1)
32
+ json
33
+ sprockets (2.12.3)
34
+ hike (~> 1.2)
35
+ multi_json (~> 1.0)
36
+ rack (~> 1.0)
37
+ tilt (~> 1.1, != 1.3.0)
38
+ tilt (1.4.1)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ opal-jquery
45
+ opal-rspec (~> 0.3.0.beta3)
46
+ react-source (~> 0.12)
47
+ react.rb!
48
+ sinatra
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015 Yi-Cheng Chang (http://github.com/zetachang)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,166 @@
1
+ # React.rb
2
+
3
+ **React.rb is a [Opal Ruby](http://opalrb.org) wrapper of [React.js library](http://facebook.github.io/react/)**.
4
+
5
+ It let you write reactive UI component with Ruby's elegancy and compiled to run in Javascript. :heart:
6
+
7
+ ## Installation
8
+
9
+ ```ruby
10
+ # Gemfile
11
+ gem "react.rb"
12
+ ```
13
+
14
+ and in your Opal application,
15
+
16
+ ```ruby
17
+ require "opal"
18
+ require "react"
19
+ React.render(React.create_element('h1'){ "Hello World!" }, `document.body`)
20
+ ```
21
+
22
+ For integration with server (Sinatra, etc), see setup of [TodoMVC](example/todos) or the [official docs](http://opalrb.org/docs/) of Opal.
23
+
24
+ ## Usage
25
+
26
+ ### A Simple Component
27
+
28
+ A ruby class which define method `render` is a valid component.
29
+
30
+ ```ruby
31
+ class HelloMessage
32
+ def render
33
+ React.create_element("div") { "Hello World!" }
34
+ end
35
+ end
36
+
37
+ React.render_static_markup(React.create_element(HelloMessage)) # => '<div>Hello World!</div>'
38
+ ```
39
+
40
+ ### More complicated one
41
+
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 corresponding snake-case method name.
43
+
44
+ ```ruby
45
+ class HelloMessage
46
+ def initialize(native)
47
+ @native = Native(native)
48
+ end
49
+
50
+ def component_will_mount
51
+ puts "will mount!"
52
+ end
53
+
54
+ def render
55
+ React.create_element("div") { "Hello #{@native[:props][:name]}!" }
56
+ end
57
+ end
58
+
59
+ puts React.render_to_static_markup(React.create_element(HelloMessage, name: 'John'))
60
+
61
+ # => will_mount!
62
+ # => '<div>Hello John!</div>'
63
+ ```
64
+
65
+ ### React::Component
66
+
67
+ Hey, we are using Ruby, simply include `React::Component` to save your typing and have some handy method defined.
68
+
69
+ ```ruby
70
+ class HelloMessage
71
+ include React::Component
72
+ MSG = {great: 'Cool!', bad: 'Cheer up!'}
73
+
74
+ define_state(:foo) { "Default greeting" }
75
+
76
+ before_mount do
77
+ self.foo = "#{self.foo}: #{MSG[params[:mood]]}"
78
+ end
79
+
80
+ after_mount :log
81
+
82
+ def log
83
+ puts "mounted!"
84
+ end
85
+
86
+ def render
87
+ div do
88
+ span { self.foo + " #{params[:name]}!" }
89
+ end
90
+ end
91
+ end
92
+
93
+ class App
94
+ include React::Component
95
+
96
+ def render
97
+ present HelloMessage, name: 'John', mood: 'great'
98
+ end
99
+ end
100
+
101
+ puts React.render_to_static_markup(React.create_element(App))
102
+ # => '<div><span>Default greeting: Cool! John!</span></div>'
103
+ React.render(React.create_element(App), `document.body`)
104
+ # mounted!
105
+ ```
106
+
107
+ * Callback of life cycle could be created through helpers `before_mount`, `after_mount`, etc
108
+ * `this.props` is accessed through method `self.params`
109
+ * Use helper method `define_state` to create setter & getter of `this.state` for you
110
+ * 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.
111
+
112
+ ### Props validation
113
+
114
+ How about props validation? Inspired from [Grape API](https://github.com/intridea/grape), props validation rule could be created easily through `params` class method as below,
115
+
116
+ ```ruby
117
+ class App
118
+ include React::Component
119
+
120
+ params do
121
+ requires :username, type: String
122
+ requires :enum, values: ['foo', 'bar', 'awesome']
123
+ requires :payload, type: Todo # yeah, a plain Ruby class
124
+ optional :filters, type: Array[String]
125
+ optional :flash_message, type: String, default: 'Welcome!' # no need to feed through `getDefaultProps`
126
+ end
127
+
128
+ def render
129
+ div
130
+ end
131
+ end
132
+ ```
133
+
134
+ ## Example
135
+
136
+ * React Tutorial: see [example/react-tutorial](example/react-tutorial), the original CommentBox example.
137
+ * TodoMVC: see [example/todos](example/todos), your beloved TodoMVC <3.
138
+
139
+ ## TODOS
140
+
141
+ * Mixins examples
142
+ * Documentation
143
+ * API wrapping coverage of the original js library (pretty close though)
144
+ * React Native?
145
+
146
+ ## Developing
147
+
148
+ To run the test case of the project yourself.
149
+
150
+ 1. `git clone` the project
151
+ 2. `bundle install`
152
+ 3. `bundle exec rackup`
153
+ 4. Open `http://localhost:9292` to run the spec
154
+
155
+ ## Contributions
156
+
157
+ This project is still in early stage, so discussion, bug report and PR are really welcome :wink:.
158
+
159
+ ## Contact
160
+
161
+ [David Chang](http://github.com/zetachang)
162
+ [@zetachang](https://twitter.com/zetachang)
163
+
164
+ ## License
165
+
166
+ In short, React.rb is available under the MIT license. See the LICENSE file for more info.
@@ -0,0 +1,15 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require "opal-rspec"
5
+ require "react/source"
6
+
7
+ Opal.append_path File.expand_path('../spec', __FILE__)
8
+
9
+ run Opal::Server.new { |s|
10
+ s.main = 'opal/rspec/sprockets_runner'
11
+ s.append_path 'spec'
12
+ s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
13
+ s.debug = true
14
+ s.index_path = 'spec/reactjs/index.html.erb'
15
+ }
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'react.rb', :path => '../..'
4
+ gem 'sinatra'
5
+ gem 'opal-jquery'
6
+ gem 'react-source'
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ react.rb (0.0.1)
5
+ opal (~> 0.6.0)
6
+ opal-activesupport
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ hike (1.2.3)
12
+ json (1.8.2)
13
+ multi_json (1.10.1)
14
+ opal (0.6.3)
15
+ source_map
16
+ sprockets
17
+ opal-activesupport (0.1.0)
18
+ opal (>= 0.5.0, < 1.0.0)
19
+ opal-jquery (0.2.0)
20
+ opal (>= 0.5.0, < 1.0.0)
21
+ rack (1.6.0)
22
+ rack-protection (1.5.3)
23
+ rack
24
+ react-source (0.12.2)
25
+ sinatra (1.4.5)
26
+ rack (~> 1.4)
27
+ rack-protection (~> 1.4)
28
+ tilt (~> 1.3, >= 1.3.4)
29
+ source_map (3.0.1)
30
+ json
31
+ sprockets (2.12.3)
32
+ hike (~> 1.2)
33
+ multi_json (~> 1.0)
34
+ rack (~> 1.0)
35
+ tilt (~> 1.1, != 1.3.0)
36
+ tilt (1.4.1)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ opal-jquery
43
+ react-source
44
+ react.rb!
45
+ sinatra
@@ -0,0 +1,8 @@
1
+ # React Tutorial
2
+
3
+ This is a rewrite of original comment box example using React.rb from the [React tutorial](http://facebook.github.io/react/docs/tutorial.html).
4
+
5
+ ## To use
6
+
7
+ 1. Make sure you use Bundler, then `bundle exec rackup`
8
+ 2. And visit <http://localhost:9292/>. Try opening multiple tabs!
@@ -0,0 +1,6 @@
1
+ [
2
+ {
3
+ "author": "Pete Hunt",
4
+ "text": "Hey there!"
5
+ }
6
+ ]
@@ -0,0 +1,55 @@
1
+ # config.ru
2
+ require 'bundler'
3
+ Bundler.require
4
+
5
+ require "react/source"
6
+
7
+ Opal::Processor.source_map_enabled = true
8
+
9
+ opal = Opal::Server.new {|s|
10
+ s.append_path './'
11
+ s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
12
+ s.main = 'example'
13
+ s.debug = true
14
+ }
15
+
16
+ map opal.source_maps.prefix do
17
+ run opal.source_maps
18
+ end
19
+
20
+ map '/assets' do
21
+ run opal.sprockets
22
+ end
23
+
24
+ get '/comments.json' do
25
+ comments = JSON.parse(open("./_comments.json").read)
26
+ JSON.generate(comments)
27
+ end
28
+
29
+ post "/comments.json" do
30
+ comments = JSON.parse(open("./_comments.json").read)
31
+ comments << JSON.parse(request.body.read)
32
+ File.write('./_comments.json', JSON.pretty_generate(comments, :indent => ' '))
33
+ JSON.generate(comments)
34
+ end
35
+
36
+ get '/' do
37
+ <<-HTML
38
+ <!doctype html>
39
+ <html>
40
+ <head>
41
+ <title>Hello React</title>
42
+ <link rel="stylesheet" href="base.css" />
43
+ <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
44
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script>
45
+ <script src="/assets/react-with-addons.min.js"></script>
46
+ <script src="/assets/example.js"></script>
47
+ </head>
48
+ <body>
49
+ <div id="content"></div>
50
+ </body>
51
+ </html>
52
+ HTML
53
+ end
54
+
55
+ run Sinatra::Application
@@ -0,0 +1,104 @@
1
+ require 'opal'
2
+ require 'opal-jquery'
3
+ require "json"
4
+ require 'react'
5
+
6
+ class Window
7
+ def self.set_interval(delay, &block)
8
+ `window.setInterval(function(){#{block.call}}, #{delay.to_i})`
9
+ end
10
+ end
11
+
12
+ class Comment
13
+ include React::Component
14
+
15
+ def render
16
+ converter = Native(`new Showdown.converter()`)
17
+ raw_markup = converter.makeHtml(params[:children].to_s)
18
+ div class_name: "comment" do
19
+ h2(class_name: "commentAuthor") { params[:author] }
20
+ span(dangerously_set_inner_HTML: {__html: raw_markup}.to_n)
21
+ end
22
+ end
23
+ end
24
+
25
+ class CommentList
26
+ include React::Component
27
+
28
+ def render
29
+ div class_name: "commentList" do
30
+ params[:data].each_with_index.map do |comment, idx|
31
+ present(Comment, author: comment["author"], key: idx) { comment["text"] }
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ class CommentForm
38
+ include React::Component
39
+
40
+ def render
41
+ f = form(class_name: "commentForm") do
42
+ input type: "text", placeholder: "Your name", ref: "author"
43
+ input type: "text", placeholder: "Say something...", ref: "text"
44
+ input type: "submit", value: "Post"
45
+ end
46
+
47
+ f.on(:submit) do |event|
48
+ event.prevent_default
49
+ author = self.refs[:author].dom_node.value.strip
50
+ text = self.refs[:text].dom_node.value.strip
51
+ return if !text || !author
52
+ self.emit(:comment_submit, {author: author, text: text})
53
+ self.refs[:author].dom_node.value = ""
54
+ self.refs[:text].dom_node.value = ""
55
+ end
56
+ end
57
+ end
58
+
59
+ class CommentBox
60
+ include React::Component
61
+ after_mount :load_comments_from_server, :start_polling
62
+ define_state(:data) { [] }
63
+
64
+ def load_comments_from_server
65
+ HTTP.get(params[:url]) do |response|
66
+ if response.ok?
67
+ self.data = JSON.parse(response.body)
68
+ else
69
+ puts "failed with status #{response.status_code}"
70
+ end
71
+ end
72
+ end
73
+
74
+ def start_polling
75
+ Window.set_interval(params[:poll_interval]) { load_comments_from_server }
76
+ end
77
+
78
+ def handle_comment_submit(comment)
79
+ comments = self.data
80
+ comments.push(comment)
81
+ self.data = comments
82
+
83
+ HTTP.post(params[:url], payload: comment) do |response|
84
+ if response.ok?
85
+ self.data = JSON.parse(response.body)
86
+ else
87
+ puts "failed with status #{response.status_code}"
88
+ end
89
+ end
90
+ end
91
+
92
+ def render
93
+ div class_name: "commentBox" do
94
+ h1 { "Comments" }
95
+ present CommentList, data: self.data
96
+ present(CommentForm).on(:comment_submit) {|c| handle_comment_submit(c) }
97
+ end
98
+ end
99
+ end
100
+
101
+
102
+ Document.ready? do
103
+ React.render React.create_element(CommentBox, url: "comments.json", poll_interval: 2000), Element.find('#content').get(0)
104
+ end