reactrb 0.7.42

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +6 -0
  3. data/.gitignore +33 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +19 -0
  7. data/README.md +117 -0
  8. data/Rakefile +28 -0
  9. data/config.ru +16 -0
  10. data/example/examples/Gemfile +7 -0
  11. data/example/examples/app/basics.js.rb +42 -0
  12. data/example/examples/app/items.rb +11 -0
  13. data/example/examples/app/jquery.js +5 -0
  14. data/example/examples/app/nodes.rb +61 -0
  15. data/example/examples/app/react-router.js +6 -0
  16. data/example/examples/app/react_api_demo.rb +29 -0
  17. data/example/examples/app/rerendering.rb +72 -0
  18. data/example/examples/app/reuse.rb +59 -0
  19. data/example/examples/app/show.rb +52 -0
  20. data/example/examples/config.ru +38 -0
  21. data/example/rails-tutorial/.gitignore +17 -0
  22. data/example/rails-tutorial/Gemfile +51 -0
  23. data/example/rails-tutorial/README.rdoc +28 -0
  24. data/example/rails-tutorial/Rakefile +6 -0
  25. data/example/rails-tutorial/app/assets/images/.keep +0 -0
  26. data/example/rails-tutorial/app/assets/javascripts/application.rb +15 -0
  27. data/example/rails-tutorial/app/assets/stylesheets/application.css +15 -0
  28. data/example/rails-tutorial/app/controllers/application_controller.rb +6 -0
  29. data/example/rails-tutorial/app/controllers/concerns/.keep +0 -0
  30. data/example/rails-tutorial/app/controllers/home_controller.rb +6 -0
  31. data/example/rails-tutorial/app/helpers/application_helper.rb +2 -0
  32. data/example/rails-tutorial/app/mailers/.keep +0 -0
  33. data/example/rails-tutorial/app/models/.keep +0 -0
  34. data/example/rails-tutorial/app/models/concerns/.keep +0 -0
  35. data/example/rails-tutorial/app/views/components.rb +3 -0
  36. data/example/rails-tutorial/app/views/components/home/show.rb +47 -0
  37. data/example/rails-tutorial/app/views/layouts/application.html.erb +14 -0
  38. data/example/rails-tutorial/bin/bundle +3 -0
  39. data/example/rails-tutorial/bin/rails +8 -0
  40. data/example/rails-tutorial/bin/rake +8 -0
  41. data/example/rails-tutorial/bin/setup +29 -0
  42. data/example/rails-tutorial/bin/spring +15 -0
  43. data/example/rails-tutorial/config.ru +4 -0
  44. data/example/rails-tutorial/config/application.rb +26 -0
  45. data/example/rails-tutorial/config/boot.rb +3 -0
  46. data/example/rails-tutorial/config/database.yml +25 -0
  47. data/example/rails-tutorial/config/environment.rb +5 -0
  48. data/example/rails-tutorial/config/environments/development.rb +41 -0
  49. data/example/rails-tutorial/config/environments/production.rb +79 -0
  50. data/example/rails-tutorial/config/environments/test.rb +42 -0
  51. data/example/rails-tutorial/config/initializers/assets.rb +11 -0
  52. data/example/rails-tutorial/config/initializers/backtrace_silencers.rb +7 -0
  53. data/example/rails-tutorial/config/initializers/cookies_serializer.rb +3 -0
  54. data/example/rails-tutorial/config/initializers/filter_parameter_logging.rb +4 -0
  55. data/example/rails-tutorial/config/initializers/inflections.rb +16 -0
  56. data/example/rails-tutorial/config/initializers/mime_types.rb +4 -0
  57. data/example/rails-tutorial/config/initializers/session_store.rb +3 -0
  58. data/example/rails-tutorial/config/initializers/wrap_parameters.rb +14 -0
  59. data/example/rails-tutorial/config/locales/en.yml +23 -0
  60. data/example/rails-tutorial/config/routes.rb +59 -0
  61. data/example/rails-tutorial/config/secrets.yml +22 -0
  62. data/example/rails-tutorial/db/seeds.rb +7 -0
  63. data/example/rails-tutorial/lib/assets/.keep +0 -0
  64. data/example/rails-tutorial/lib/tasks/.keep +0 -0
  65. data/example/rails-tutorial/log/.keep +0 -0
  66. data/example/rails-tutorial/public/404.html +67 -0
  67. data/example/rails-tutorial/public/422.html +67 -0
  68. data/example/rails-tutorial/public/500.html +66 -0
  69. data/example/rails-tutorial/public/favicon.ico +0 -0
  70. data/example/rails-tutorial/public/robots.txt +5 -0
  71. data/example/rails-tutorial/test/controllers/.keep +0 -0
  72. data/example/rails-tutorial/test/fixtures/.keep +0 -0
  73. data/example/rails-tutorial/test/helpers/.keep +0 -0
  74. data/example/rails-tutorial/test/integration/.keep +0 -0
  75. data/example/rails-tutorial/test/mailers/.keep +0 -0
  76. data/example/rails-tutorial/test/models/.keep +0 -0
  77. data/example/rails-tutorial/test/test_helper.rb +10 -0
  78. data/example/rails-tutorial/vendor/assets/javascripts/.keep +0 -0
  79. data/example/rails-tutorial/vendor/assets/stylesheets/.keep +0 -0
  80. data/example/sinatra-tutorial/.DS_Store +0 -0
  81. data/example/sinatra-tutorial/Gemfile +5 -0
  82. data/example/sinatra-tutorial/README.md +8 -0
  83. data/example/sinatra-tutorial/_comments.json +42 -0
  84. data/example/sinatra-tutorial/app/example.rb +290 -0
  85. data/example/sinatra-tutorial/app/jquery.js +5 -0
  86. data/example/sinatra-tutorial/config.ru +58 -0
  87. data/example/sinatra-tutorial/public/base.css +62 -0
  88. data/example/todos/Gemfile +11 -0
  89. data/example/todos/README.md +37 -0
  90. data/example/todos/Rakefile +8 -0
  91. data/example/todos/app/application.rb +22 -0
  92. data/example/todos/app/components/app.react.rb +61 -0
  93. data/example/todos/app/components/footer.react.rb +31 -0
  94. data/example/todos/app/components/todo_item.react.rb +46 -0
  95. data/example/todos/app/components/todo_list.react.rb +25 -0
  96. data/example/todos/app/models/todo.rb +19 -0
  97. data/example/todos/config.ru +14 -0
  98. data/example/todos/index.html.haml +16 -0
  99. data/example/todos/spec/todo_spec.rb +28 -0
  100. data/example/todos/vendor/base.css +410 -0
  101. data/example/todos/vendor/bg.png +0 -0
  102. data/example/todos/vendor/jquery.js +4 -0
  103. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
  104. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  105. data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  106. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  107. data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  108. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  109. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  110. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +105 -0
  111. data/lib/rails-helpers/top_level_rails_component.rb +54 -0
  112. data/lib/react/api.rb +127 -0
  113. data/lib/react/callbacks.rb +42 -0
  114. data/lib/react/component.rb +269 -0
  115. data/lib/react/component/api.rb +50 -0
  116. data/lib/react/component/base.rb +9 -0
  117. data/lib/react/component/class_methods.rb +190 -0
  118. data/lib/react/component/props_wrapper.rb +82 -0
  119. data/lib/react/element.rb +77 -0
  120. data/lib/react/event.rb +76 -0
  121. data/lib/react/ext/hash.rb +9 -0
  122. data/lib/react/ext/string.rb +8 -0
  123. data/lib/react/native_library.rb +53 -0
  124. data/lib/react/observable.rb +29 -0
  125. data/lib/react/rendering_context.rb +109 -0
  126. data/lib/react/state.rb +140 -0
  127. data/lib/react/top_level.rb +97 -0
  128. data/lib/react/validator.rb +136 -0
  129. data/lib/reactive-ruby/component_loader.rb +45 -0
  130. data/lib/reactive-ruby/isomorphic_helpers.rb +196 -0
  131. data/lib/reactive-ruby/rails.rb +7 -0
  132. data/lib/reactive-ruby/rails/component_mount.rb +44 -0
  133. data/lib/reactive-ruby/rails/controller_helper.rb +13 -0
  134. data/lib/reactive-ruby/rails/railtie.rb +14 -0
  135. data/lib/reactive-ruby/serializers.rb +15 -0
  136. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
  137. data/lib/reactive-ruby/version.rb +3 -0
  138. data/lib/reactrb.rb +50 -0
  139. data/lib/sources/react-latest.js +21167 -0
  140. data/lib/sources/react-v13.js +21642 -0
  141. data/lib/sources/react-v14.js +20818 -0
  142. data/lib/sources/react-v15.js +21167 -0
  143. data/logo1.png +0 -0
  144. data/logo2.png +0 -0
  145. data/logo3.png +0 -0
  146. data/path_release_steps.md +9 -0
  147. data/reactrb.gemspec +43 -0
  148. data/spec/controller_helper_spec.rb +22 -0
  149. data/spec/index.html.erb +12 -0
  150. data/spec/react/callbacks_spec.rb +106 -0
  151. data/spec/react/component/base_spec.rb +36 -0
  152. data/spec/react/component_spec.rb +721 -0
  153. data/spec/react/dsl_spec.rb +161 -0
  154. data/spec/react/element_spec.rb +47 -0
  155. data/spec/react/event_spec.rb +24 -0
  156. data/spec/react/native_library_spec.rb +10 -0
  157. data/spec/react/observable_spec.rb +7 -0
  158. data/spec/react/param_declaration_spec.rb +286 -0
  159. data/spec/react/react_spec.rb +211 -0
  160. data/spec/react/state_spec.rb +26 -0
  161. data/spec/react/top_level_component_spec.rb +68 -0
  162. data/spec/react/tutorial/tutorial_spec.rb +35 -0
  163. data/spec/react/validator_spec.rb +128 -0
  164. data/spec/reactive-ruby/component_loader_spec.rb +68 -0
  165. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
  166. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +9 -0
  167. data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
  168. data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
  169. data/spec/spec_helper.rb +109 -0
  170. data/spec/support/react/spec_helpers.rb +57 -0
  171. data/spec/vendor/es5-shim.min.js +6 -0
  172. data/spec/vendor/jquery-2.2.4.min.js +4 -0
  173. metadata +441 -0
@@ -0,0 +1,7 @@
1
+ # This file should contain all the record creation needed to seed the database with its default values.
2
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3
+ #
4
+ # Examples:
5
+ #
6
+ # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7
+ # Mayor.create(name: 'Emanuel', city: cities.first)
File without changes
File without changes
File without changes
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/404.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The page you were looking for doesn't exist.</h1>
62
+ <p>You may have mistyped the address or the page may have moved.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-agent: *
5
+ # Disallow: /
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,10 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7
+ fixtures :all
8
+
9
+ # Add more helper methods to be used by all tests here...
10
+ end
Binary file
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'reactive-ruby', :path => '../..'
4
+ gem 'sinatra'
5
+ gem 'opal-jquery'
@@ -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,42 @@
1
+ [
2
+ {
3
+ "author": "Pete Hunt",
4
+ "text": "Hey there!"
5
+ },
6
+ {
7
+ "author": "Mitch",
8
+ "text": "i ***am*** saying something!"
9
+ },
10
+ {
11
+ "author": "Jan",
12
+ "text": "well what are you saying *dear*"
13
+ },
14
+ {
15
+ "author": "Mitch",
16
+ "text": "I am saying something"
17
+ },
18
+ {
19
+ "author": "Mitch",
20
+ "text": "I did say something"
21
+ },
22
+ {
23
+ "author": "Mitch",
24
+ "text": "said make a brandy Alexander"
25
+ },
26
+ {
27
+ "author": "Jan",
28
+ "text": "No **I Said** make the alexander! - You make it"
29
+ },
30
+ {
31
+ "author": "Cool Fred",
32
+ "text": "Just do it!"
33
+ },
34
+ {
35
+ "author": "foo",
36
+ "text": "to you"
37
+ },
38
+ {
39
+ "author": "Mitch",
40
+ "text": "fred"
41
+ }
42
+ ]
@@ -0,0 +1,290 @@
1
+ require 'opal'
2
+ require 'browser/interval' # gives us wrappers on javascript methods such as setTimer and setInterval
3
+ require 'jquery'
4
+ require 'opal-jquery' # gives us a nice wrapper on jQuery which we will use mainly for HTTP calls
5
+ require "json" # json conversions
6
+ require 'reactive-ruby' # and the whole reason we are gathered here today!
7
+
8
+ Document.ready? do # Document.ready? is a opal-jquery method. The block will run when doc is loaded
9
+
10
+ # render an instance of the CommentBox component at the '#content' element.
11
+ # url and poll_interval are the initial params for this comment box
12
+ React.render(
13
+ React.create_element(
14
+ CommentBox, url: "comments.json", poll_interval: 2),
15
+ Element['#content']
16
+ )
17
+ end
18
+
19
+ class CommentBox
20
+
21
+ # A react component is simply a class that has a "render" method.
22
+
23
+ # But including React::Component mixin provides a nice dsl, and many other features
24
+
25
+ include React::Component
26
+
27
+ # Components can have parameters that are passed in when the component is first "mounted"
28
+ # and then updated as the application state changes. In this case url, and poll_interval will
29
+ # never change since this is the top level component.
30
+
31
+ required_param :url
32
+ required_param :poll_interval
33
+
34
+ # Components also may have internal state variables, which are like instance variables,
35
+ # with one added feature: Changing state causes a rerender to occur.
36
+
37
+ # The "comments" state is being initialized by parsing the javascript object at window.initial_comments
38
+ # This is not a react feature, but was just set up in the HTML header (see config.ru for how this was done).
39
+
40
+ define_state comments: JSON.from_object(`window.initial_comments`)
41
+
42
+ # The following call backs are made during the component lifecycle:
43
+
44
+ # before_mount before component is first rendered
45
+ # after_mount after component is first rendered, after DOM is loaded. ONLY CALLED ON CLIENT
46
+ # before_receive_props when component is being about to be rerendered by an outside state change. CANCELLABLE
47
+ # before_update just before a rerender, and not cancellable.
48
+ # after_update after DOM has been updated.
49
+ # before_unmount before component instance will be removed. Use this to kill low level handlers etc.
50
+
51
+ # just to show off how these callbacks work we have separated setting up a repeating fetch into three pieces.
52
+
53
+ # before mounting we will initialize a polling loop, but we don't want to start it yet.
54
+
55
+ before_mount do
56
+ @fetcher = every(poll_interval) do # we use the opal browser utility to call the server every poll_interval seconds
57
+ HTTP.get(url) do |response| # notice that params poll_interval, and url are accessed as instance methods
58
+ if response.ok?
59
+ comments! JSON.parse(response.body) # comments!(value) updates the state and notifies react of the state change
60
+ else
61
+ puts "failed with status #{response.status_code}"
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ # once we have things up and displayed lets start polling for updates
68
+
69
+ after_mount do
70
+ puts "start me up!"
71
+ @fetcher.start
72
+ end
73
+
74
+ # finally our component should be a good citizen and stop the polling when its unmounted
75
+
76
+ before_unmount do
77
+ @fetcher.stop
78
+ end
79
+
80
+ # components can have their own methods like any other class
81
+ # in this case we receive a new comment and send it the server
82
+
83
+ def send_comment_to_server(comment)
84
+ HTTP.post(url, payload: comment) do |response|
85
+ puts "failed with status #{response.status_code}" unless response.ok?
86
+ end
87
+ comment
88
+ end
89
+
90
+ # every component must implement a render method. The method must generate a single
91
+ # react virtual DOM element. React compares the output of each render and determines
92
+ # the minimum actual DOM update needed.
93
+
94
+ # A very common mistake is to try generate two or more elements (or none at all.) Either case will
95
+ # throw an error. Just remember that there is already a DOM node waiting for the output of the render
96
+ # hence the need for exactly one element per render.
97
+
98
+ def render
99
+
100
+ # the dsl syntax is simply a method call, with params hash, followed by a block
101
+ # the built in dsl methods correspond to the standard HTML5 tags such as div, h1, table, tr, td, span etc.
102
+ #return div.comment { h1 {"hello"} }
103
+ div class: "commentBox" do # just like <div class="commentBox">
104
+
105
+ h1 { "Comments" } # yep just like <h1>Comments</h1>
106
+
107
+ # Custom components use their class name, as the tag. Notice that the comments state is passed to
108
+ # to the CommentList component. This is the normal React paradigm: Data flows towards the leaf nodes.
109
+
110
+ CommentList comments: comments
111
+
112
+ # Sometimes its necessary for data to move upwards, and react provides several ways to do this.
113
+
114
+ # In this case we need to know when a new comment is submitted. So we pass a callback proc.
115
+
116
+ # The callback takes the new comment and sends it to the server and then pushes it onto the comments list.
117
+ # Again the comments! method is used to signal that the state is changing. The use of the "bang" pseudo
118
+ # operator is important as the value of comments has NOT changed (its still tha same array), but its
119
+ # internal state has.
120
+
121
+ CommentForm submit_comment: lambda { |comment| comments! << send_comment_to_server(comment)}
122
+
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ # Our second component!
129
+
130
+ class CommentList
131
+
132
+ include React::Component
133
+
134
+ # As we saw above a CommentList component takes a comments parameter
135
+ # Here we introduce optional parameter type checking. The syntax [Hash] means "Array of Hashes"
136
+ # In our case each comment is a hash with an author and text key.
137
+
138
+ # Failure to match the type puts a warning on the console not an error,
139
+ # and only in development mode not production.
140
+
141
+ required_param :comments, type: Array
142
+
143
+ # This is a good place to think more about the component lifecycle. The first time
144
+ # CommentList is mounted, comments will be the initial array of author, text hashes.
145
+ # As new comments are added the component will receive new params. However the component
146
+ # does NOT reinitialize its state. If changes in state are needed as result of incoming param changes
147
+ # the before_receive_props call back can be used.
148
+
149
+ def render
150
+
151
+ # Lets render some comments - all we need to do is iterate over the comments array using the usual
152
+ # ruby "each" method.
153
+
154
+ # This is a good place to clarify how the DSL works. Notice that we use comments.each NOT comments.collect
155
+ # When a tag method (such as div, or Comment) is called its "output" is internally pushed into a render buffer.
156
+ # This simplifies the DSL by separating the control flow from the output, but can sometimes be a bit confusing.
157
+
158
+ div.commentList.and_another_class.and_another do # you can also include the class haml style (tx to @dancinglightning!)
159
+ comments.each do |comment|
160
+ # By now we are getting used to the react paradigm: Stuff comes in, is processed, and then
161
+ # passed to next lower level. In this case we pass along each author-text pair to the Comment component.
162
+ Comment author: comment[:author], text: comment[:text], hash: comment
163
+ end
164
+ end
165
+ end
166
+
167
+ end
168
+
169
+ # Notice that the above CommentList component had no state. Each time its parameters change, it simply re-renders.
170
+ # CommentForm does have internal state as we will see...
171
+
172
+ class CommentForm
173
+
174
+ include React::Component
175
+
176
+ # While declaring the type of a param is optional its handy not only for debug, but also to let React create
177
+ # appropriate helpers based on the type. In this case we are passing in a Proc, and so React will treat the
178
+ # "submit_comment" param specially. Instead of submit_comment returning its value (as the previous params have done)
179
+ # it will call the associated Proc, thus allow CommentForm to communicate state changes back to the parent.
180
+
181
+ required_param :submit_comment, type: Proc
182
+
183
+ # We are going to have 2 state variable. One for each field in the comment. As the user types,
184
+ # these state variables will be updating causing a rerender of the CommentForm (but no other components.)
185
+
186
+ define_state :author, :text
187
+
188
+ def render
189
+ div do
190
+ div do
191
+
192
+ "Author: ".span # Note the shorthand for span { "Author" }. You can do this with br, span, th, td, and para (for p) tags
193
+
194
+ # Now we are going to generate an input tag. Notice how the author state variable is provided. Referencing
195
+ # author is what will cause us to re-render and update the input as the value of author changes.
196
+ # React will optimize the updates so parts that are not changing will not be effected.
197
+
198
+ input.author_name(type: :text, value: author, placeholder: "Your name", style: {width: "30%"}).
199
+ # and we attach an on_change handler to the input. As the input changes we simply update author.
200
+ on(:change) { |e| author! e.target.value }
201
+
202
+ end
203
+
204
+ div do
205
+ # lets have some fun with the text. Same deal as the author except we will use a text area...
206
+ div(style: {float: :left, width: "50%"}) do
207
+ textarea(value: text, placeholder: "Say something...", style: {width: "90%"}, rows: 30).
208
+ on(:change) { |e| text! e.target.value }
209
+ end
210
+ # and lets use Showdown to allow for markdown, and display the mark down to the left of input
211
+ # we will define Showdown later, and it will be our first reusable component, as we will use it twice.
212
+ div(style: {float: :left, width: "50%"}) do
213
+ Showdown markup: text
214
+ end
215
+ end
216
+
217
+ # Finally lets give the use a button to submit changes. Why not? We have come this far!
218
+ # Notice how the submit_comment proc param allows us to be ignorant of how the update is made.
219
+
220
+ # Notice that (author! "") updates author, but returns the current value.
221
+ # This is usually the desired behavior in React as we are typically interested in state changes,
222
+ # and before/after values, not simply doing a chained update of multiple variables.
223
+
224
+ button { "Post" }.on(:click) { submit_comment :author => (author! ""), :text => (text! "") }
225
+
226
+ end
227
+ end
228
+ end
229
+
230
+ # Wow only two more components left! This one is a breeze. We just take the author, and text and display
231
+ # them. We already know how to use our Showdown component to display the markdown so we can just reuse that.
232
+
233
+ class Comment
234
+
235
+ include React::Component
236
+
237
+ required_param :author
238
+ required_param :text
239
+ required_param :hash, type: Hash
240
+
241
+ def render
242
+ div.comment do
243
+ h2.comment_author { author } # NOTE: single underscores in haml style class names are converted to dashes
244
+ # so comment_author becomes comment-author, but comment__author would be comment_author
245
+ # this is handy for boot strap names like col-md-push-9 which can be written as col_md_push_9
246
+ Showdown markup: text
247
+ end
248
+ end
249
+
250
+ end
251
+
252
+ # Last but not least here is our ShowDown Component
253
+
254
+ class Showdown
255
+
256
+ include React::Component
257
+
258
+ required_param :markup
259
+
260
+ def render
261
+
262
+ # we will use some Opal lowlevel stuff to interface to the javascript Showdown class
263
+ # we only need to build the converter once, and then reuse it so we will use a plain old
264
+ # instance variable to keep track of it.
265
+
266
+ @converter ||= Native(`new Showdown.converter()`)
267
+
268
+ # then we will take our markup param, and convert it to html
269
+
270
+ raw_markup = @converter.makeHtml(markup) if markup
271
+
272
+ # React.js takes a very dim view of passing raw html so its purposefully made
273
+ # difficult so you won't do it by accident. After all think of how dangerous what we
274
+ # are doing right here is!
275
+
276
+ # The span tag can be replaced by any tag that could sensibly take a child html element.
277
+ # You could also use div, td, etc.
278
+
279
+ span(dangerously_set_inner_HTML: {__html: raw_markup})
280
+
281
+ end
282
+
283
+ end
284
+
285
+
286
+
287
+
288
+
289
+
290
+