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,62 @@
1
+ body {
2
+ background: #fff;
3
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;;
4
+ font-size: 15px;
5
+ line-height: 1.7;
6
+ margin: 0;
7
+ padding: 30px;
8
+ }
9
+
10
+ a {
11
+ color: #4183c4;
12
+ text-decoration: none;
13
+ }
14
+
15
+ a:hover {
16
+ text-decoration: underline;
17
+ }
18
+
19
+ code {
20
+ background-color: #f8f8f8;
21
+ border: 1px solid #ddd;
22
+ border-radius: 3px;
23
+ font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
24
+ font-size: 12px;
25
+ margin: 0 2px;
26
+ padding: 0px 5px;
27
+ }
28
+
29
+ h1, h2, h3, h4 {
30
+ font-weight: bold;
31
+ margin: 0 0 15px;
32
+ padding: 0;
33
+ }
34
+
35
+ h1 {
36
+ border-bottom: 1px solid #ddd;
37
+ font-size: 2.5em;
38
+ font-weight: bold;
39
+ margin: 0 0 15px;
40
+ padding: 0;
41
+ }
42
+
43
+ h2 {
44
+ border-bottom: 1px solid #eee;
45
+ font-size: 2em;
46
+ }
47
+
48
+ h3 {
49
+ font-size: 1.5em;
50
+ }
51
+
52
+ h4 {
53
+ font-size: 1.2em;
54
+ }
55
+
56
+ p, ul {
57
+ margin: 15px 0;
58
+ }
59
+
60
+ ul {
61
+ padding-left: 30px;
62
+ }
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'opal', :github => 'opal/opal', :ref => '85220f32136c74ac93f1cb721462324a3423cf44'
5
+ gem 'opal-jquery', :github => 'opal/opal-jquery'
6
+ gem 'vienna', :github => 'opal/vienna', :ref => '593335cbd7fb99ce471fa720e9b9c849d99b8dda'
7
+ gem 'opal-haml', :github => 'opal/opal-haml'
8
+ gem 'opal-rspec', '0.3.0.beta2'
9
+ gem 'react.rb', :path => "../.."
10
+ gem 'thin'
11
+ gem 'react-source'
@@ -0,0 +1,84 @@
1
+ GIT
2
+ remote: git://github.com/opal/opal-haml.git
3
+ revision: 0bdd3eb53ec03d380e14440a94f779ed7c3741e1
4
+ specs:
5
+ opal-haml (0.2.0)
6
+ haml
7
+ opal (>= 0.5.0, < 1.0.0)
8
+
9
+ GIT
10
+ remote: git://github.com/opal/opal-jquery.git
11
+ revision: 1814202085f168176231b877b2b7a967b75b0726
12
+ specs:
13
+ opal-jquery (0.1.2)
14
+ opal (>= 0.5.0, < 1.0.0)
15
+
16
+ GIT
17
+ remote: git://github.com/opal/opal.git
18
+ revision: 85220f32136c74ac93f1cb721462324a3423cf44
19
+ ref: 85220f32136c74ac93f1cb721462324a3423cf44
20
+ specs:
21
+ opal (0.6.0)
22
+ source_map
23
+ sprockets
24
+
25
+ GIT
26
+ remote: git://github.com/opal/vienna.git
27
+ revision: 593335cbd7fb99ce471fa720e9b9c849d99b8dda
28
+ ref: 593335cbd7fb99ce471fa720e9b9c849d99b8dda
29
+ specs:
30
+ vienna (0.0.2)
31
+ opal (>= 0.5.0, < 1.0.0)
32
+ opal-activesupport
33
+ opal-jquery
34
+
35
+ PATH
36
+ remote: ../..
37
+ specs:
38
+ react.rb (0.0.1)
39
+ opal (~> 0.6.0)
40
+ opal-activesupport
41
+
42
+ GEM
43
+ remote: https://rubygems.org/
44
+ specs:
45
+ daemons (1.1.9)
46
+ eventmachine (1.0.3)
47
+ haml (4.0.5)
48
+ tilt
49
+ hike (1.2.3)
50
+ json (1.8.2)
51
+ multi_json (1.10.1)
52
+ opal-activesupport (0.1.0)
53
+ opal (>= 0.5.0, < 1.0.0)
54
+ opal-rspec (0.3.0.beta2)
55
+ opal (>= 0.6.0, < 1.0.0)
56
+ rack (1.5.2)
57
+ rake (10.1.1)
58
+ react-source (0.12.2)
59
+ source_map (3.0.1)
60
+ json
61
+ sprockets (2.12.3)
62
+ hike (~> 1.2)
63
+ multi_json (~> 1.0)
64
+ rack (~> 1.0)
65
+ tilt (~> 1.1, != 1.3.0)
66
+ thin (1.6.2)
67
+ daemons (>= 1.0.9)
68
+ eventmachine (>= 1.0.0)
69
+ rack (>= 1.0.0)
70
+ tilt (1.4.1)
71
+
72
+ PLATFORMS
73
+ ruby
74
+
75
+ DEPENDENCIES
76
+ opal!
77
+ opal-haml!
78
+ opal-jquery!
79
+ opal-rspec (= 0.3.0.beta2)
80
+ rake
81
+ react-source
82
+ react.rb!
83
+ thin
84
+ vienna!
@@ -0,0 +1,37 @@
1
+ # react.rb-todos
2
+
3
+ Modify from original version of [Opal-Todos](https://github.com/opal/opal-todos)
4
+
5
+ ## Running
6
+
7
+ Get dependencies:
8
+
9
+ ```
10
+ $ bundle install
11
+ ```
12
+
13
+ Run the sprockets based server for auto-compiling:
14
+
15
+ ```
16
+ $ bundle exec rackup
17
+ ```
18
+
19
+ Open `http://localhost:9292` in the browser.
20
+
21
+ ## Code Overview
22
+
23
+ Opal comes with sprockets support built in, so using rack we can have an
24
+ easy to boot build system to handle all opal dependencies. If you look
25
+ in `index.html.erb`, you will see a call to `javascript_include_tag`
26
+ which acts just like the rails tag helper. This will include our
27
+ `application.rb` file, and all of its dependencies. Each file will be included
28
+ in a seperate `<script>..</script>` tag to make navigating the code in a
29
+ web browser easier.
30
+
31
+ ### Router
32
+
33
+ `TodoAppView` use router provided by [Vienna](https://github.com/opal/vienna)
34
+
35
+ ### Model
36
+
37
+ Use model layer provided by [Vienna](https://github.com/opal/vienna) which provide basic support of local storage and update notification.
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require 'opal/rspec/rake_task'
5
+ Opal::RSpec::RakeTask.new(:default) do |s|
6
+ s.append_path 'app'
7
+ s.append_path 'vendor'
8
+ end
@@ -0,0 +1,22 @@
1
+ require 'opal'
2
+ require 'jquery'
3
+ require 'opal-jquery'
4
+ require 'opal-haml'
5
+ require 'vienna'
6
+ require "react"
7
+
8
+ require 'models/todo'
9
+
10
+ require "components/app.react"
11
+
12
+ Document.ready? do
13
+ element = React.create_element(TodoAppView, filter: "all")
14
+ component = React.render(element, Element.find('#todoapp').get(0))
15
+
16
+ Vienna::Router.new.tap do |router|
17
+ router.route('/:filter') do |params|
18
+ component.set_props(filter: params[:filter].empty? ? "all" : params[:filter])
19
+ end
20
+ end.update
21
+
22
+ end
@@ -0,0 +1,61 @@
1
+ require "components/footer.react"
2
+ require "components/todo_item.react"
3
+ require "components/todo_list.react"
4
+
5
+ class TodoAppView
6
+ include React::Component
7
+
8
+ KEY_ENTER = 13
9
+
10
+ params do
11
+ requires :filter, values: ["all", "active", "completed"]
12
+ end
13
+
14
+ define_state(:todos) { [] }
15
+
16
+ before_mount do
17
+ Todo.on(:create) { Todo.adapter.sync_models(Todo); reload_current_filter }
18
+ Todo.on(:update) { Todo.adapter.sync_models(Todo); reload_current_filter }
19
+ Todo.on(:destroy) { Todo.adapter.sync_models(Todo); reload_current_filter }
20
+ end
21
+
22
+ before_receive_props do |next_props|
23
+ apply_filter next_props[:filter]
24
+ end
25
+
26
+ def reload_current_filter
27
+ apply_filter(params[:filter])
28
+ end
29
+
30
+ def apply_filter(filter)
31
+ Todo.adapter.find_all(Todo) do |models|
32
+ case filter
33
+ when "all"
34
+ self.todos = models
35
+ when "active"
36
+ self.todos = models.reject(&:completed)
37
+ when "completed"
38
+ self.todos = models.select(&:completed)
39
+ end
40
+ end
41
+ end
42
+
43
+ def handle_keydown(event)
44
+ if event.key_code == KEY_ENTER
45
+ value = event.target.value.strip
46
+ Todo.create title: value, completed: false
47
+ event.target.value = ""
48
+ end
49
+ end
50
+
51
+ def render
52
+ div do
53
+ header(id: "header") do
54
+ h1 { "Todos" }
55
+ input(id: "new-todo", placeholder: "What needs to be done?").on(:key_down) { |e| handle_keydown(e) }
56
+ end
57
+ present TodoList, todos: self.todos
58
+ present Footer, selected_filter: params[:filter]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,31 @@
1
+ class Footer
2
+ include React::Component
3
+
4
+ def clear_completed
5
+ Todo.completed.each { |t| t.destroy }
6
+ end
7
+
8
+ def render
9
+ footer(id: "footer") do
10
+ span(id: "todo-count") do
11
+ strong { Todo.active.size }
12
+ span { Todo.active.size == 1 ? ' item left' : ' items left' }
13
+ end
14
+
15
+ ul(id: "filters") do
16
+ filters = [{href: "#/", filter: "all"},
17
+ {href: "#/active", filter: "active"},
18
+ {href: "#/completed", filter: "completed"}]
19
+ filters.map do |item|
20
+ li { a(href: item[:href], class_name: {selected: params[:selected_filter] == item[:filter]}) { item[:filter].capitalize } }
21
+ end
22
+ end
23
+
24
+ completed = Todo.completed.size
25
+
26
+ if completed > 0
27
+ button(id: "clear-completed") { "Clear completed (#{completed})" }.on(:click) { clear_completed }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ class TodoItem
2
+ include React::Component
3
+ KEY_ENTER = 13
4
+
5
+ define_state(:editing) { false }
6
+ define_state(:edit_text)
7
+
8
+ before_mount :set_up
9
+
10
+ def set_up
11
+ self.edit_text = params[:todo].title
12
+ end
13
+
14
+ def finish_editing
15
+ self.editing = false
16
+ new_value = self.edit_text.strip
17
+ if new_value.empty?
18
+ params[:todo].destroy
19
+ else
20
+ params[:todo].update(title: new_value)
21
+ end
22
+ end
23
+
24
+ def render
25
+ li(class_name: {editing: self.editing}) do
26
+ div(class_name: 'view') do
27
+ input(class_name: "toggle", type: "checkbox", checked: params[:todo].completed).on(:click) do
28
+ todo = params[:todo]
29
+ todo.update(:completed => !todo.completed)
30
+ end
31
+ label { self.edit_text }.on(:double_click) do
32
+ # set on state will trigger re-render, so we manipulate the DOM after render done
33
+ self.set_state(editing: true) do
34
+ self.refs[:input].dom_node.focus
35
+ end
36
+ self.edit_text = params[:todo].title
37
+ end
38
+ button(class_name: "destroy").on(:click) { params[:todo].destroy }
39
+ end
40
+ input(class_name: "edit", value: self.edit_text, ref: :input)
41
+ .on(:blur) { finish_editing }
42
+ .on(:change) {|e| self.edit_text = e.target.value }
43
+ .on(:key_down) { |e| finish_editing if (e.key_code == KEY_ENTER) }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ class TodoList
2
+ include React::Component
3
+
4
+ def toggle_all
5
+ distinct_status = Todo.all.map {|t| t.completed }.uniq
6
+
7
+ if distinct_status.count == 1
8
+ Todo.all.each {|t| t.update(:completed => !distinct_status[0]) }
9
+ else # toggle all as completed
10
+ Todo.all.each {|t| t.update(:completed => true) }
11
+ end
12
+ end
13
+
14
+ def render
15
+ section(id: "main") do
16
+ input(id: "toggle-all", type: "checkbox").on(:click) { toggle_all }
17
+ label(htmlFor: "toggle-all") { "Mark all as complete" }
18
+ ul(id: "todo-list") do
19
+ params[:todos].map do |todo|
20
+ present TodoItem, todo: todo , key: todo.id
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ require 'vienna/adapters/local'
2
+
3
+ class Todo < Vienna::Model
4
+ adapter Vienna::LocalAdapter
5
+
6
+ attributes :title, :completed
7
+
8
+ alias completed? completed
9
+
10
+ # All active (not completed) todos
11
+ def self.active
12
+ all.reject(&:completed)
13
+ end
14
+
15
+ # All completed todos
16
+ def self.completed
17
+ all.select(&:completed)
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require "react/source"
5
+
6
+ run Opal::Server.new { |s|
7
+ s.append_path 'app'
8
+ s.append_path 'vendor'
9
+ s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
10
+
11
+ s.debug = true
12
+ s.main = 'application'
13
+ s.index_path = 'index.html.haml'
14
+ }
@@ -0,0 +1,16 @@
1
+ !!!
2
+ %html(lang="en")
3
+ %head
4
+ %meta(charset="utf-8")
5
+ %meta(http-equiv="X-UA-Compatible" content="IE=edge,chrome=1")
6
+ %link(rel="stylesheet" href="/vendor/base.css")
7
+ = javascript_include_tag 'react-with-addons.min.js'
8
+ = javascript_include_tag 'application'
9
+
10
+ %body
11
+ %section#todoapp
12
+ #info
13
+ %p Double-click to edit a todo
14
+ %p
15
+ Part of
16
+ %a(href="http://todomvc.com") TodoMVC