react.rb 0.0.1

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.
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