hyper-react 0.99.6 → 1.0.0.lap21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +30 -37
  4. data/.rubocop.yml +1159 -0
  5. data/.travis.yml +32 -0
  6. data/Appraisals +31 -0
  7. data/CHANGELOG.md +143 -0
  8. data/DOCS.md +1515 -0
  9. data/Gemfile +2 -5
  10. data/LICENSE +19 -0
  11. data/README.md +5 -33
  12. data/Rakefile +25 -6
  13. data/UPGRADING.md +24 -0
  14. data/component-name-lookup.md +145 -0
  15. data/dciy.toml +3 -0
  16. data/dciy_prepare.sh +8 -0
  17. data/dciy_run.sh +10 -0
  18. data/hyper-react.gemspec +24 -18
  19. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
  20. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
  21. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  22. data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  23. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  24. data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  25. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  26. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  27. data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  28. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +117 -0
  29. data/lib/hyper-react.rb +66 -4
  30. data/lib/rails-helpers/top_level_rails_component.rb +75 -0
  31. data/lib/react/api.rb +203 -0
  32. data/lib/react/callbacks.rb +41 -0
  33. data/lib/react/children.rb +30 -0
  34. data/lib/react/component.rb +177 -0
  35. data/lib/react/component/api.rb +69 -0
  36. data/lib/react/component/base.rb +13 -0
  37. data/lib/react/component/class_methods.rb +181 -0
  38. data/lib/react/component/dsl_instance_methods.rb +23 -0
  39. data/lib/react/component/params.rb +6 -0
  40. data/lib/react/component/props_wrapper.rb +78 -0
  41. data/lib/react/component/should_component_update.rb +99 -0
  42. data/lib/react/component/tags.rb +108 -0
  43. data/lib/react/config.rb +5 -0
  44. data/lib/react/config/client.rb.erb +19 -0
  45. data/lib/react/config/server.rb +23 -0
  46. data/lib/react/element.rb +150 -0
  47. data/lib/react/event.rb +76 -0
  48. data/lib/react/ext/hash.rb +9 -0
  49. data/lib/react/ext/opal-jquery/element.rb +26 -0
  50. data/lib/react/ext/string.rb +8 -0
  51. data/lib/react/hash.rb +13 -0
  52. data/lib/react/native_library.rb +87 -0
  53. data/lib/react/object.rb +15 -0
  54. data/lib/react/react-source-browser.rb +3 -0
  55. data/lib/react/react-source-server.rb +3 -0
  56. data/lib/react/react-source.rb +16 -0
  57. data/lib/react/ref_callback.rb +31 -0
  58. data/lib/react/rendering_context.rb +146 -0
  59. data/lib/react/server.rb +19 -0
  60. data/lib/react/state_wrapper.rb +23 -0
  61. data/lib/react/test.rb +16 -0
  62. data/lib/react/test/dsl.rb +17 -0
  63. data/lib/react/test/matchers/render_html_matcher.rb +56 -0
  64. data/lib/react/test/rspec.rb +15 -0
  65. data/lib/react/test/session.rb +37 -0
  66. data/lib/react/test/utils.rb +71 -0
  67. data/lib/react/top_level.rb +110 -0
  68. data/lib/react/top_level_render.rb +28 -0
  69. data/lib/react/validator.rb +136 -0
  70. data/lib/reactive-ruby/component_loader.rb +43 -0
  71. data/lib/reactive-ruby/isomorphic_helpers.rb +235 -0
  72. data/lib/reactive-ruby/rails.rb +8 -0
  73. data/lib/reactive-ruby/rails/component_mount.rb +48 -0
  74. data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
  75. data/lib/reactive-ruby/rails/railtie.rb +20 -0
  76. data/lib/reactive-ruby/serializers.rb +15 -0
  77. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +41 -0
  78. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
  79. data/lib/reactive-ruby/version.rb +3 -0
  80. data/lib/reactrb/auto-import.rb +27 -0
  81. data/logo1.png +0 -0
  82. data/logo2.png +0 -0
  83. data/logo3.png +0 -0
  84. data/path_release_steps.md +9 -0
  85. data/spec/controller_helper_spec.rb +35 -0
  86. data/spec/index.html.erb +11 -0
  87. data/spec/react/callbacks_spec.rb +142 -0
  88. data/spec/react/children_spec.rb +132 -0
  89. data/spec/react/component/base_spec.rb +36 -0
  90. data/spec/react/component_spec.rb +1073 -0
  91. data/spec/react/dsl_spec.rb +323 -0
  92. data/spec/react/element_spec.rb +132 -0
  93. data/spec/react/event_spec.rb +39 -0
  94. data/spec/react/native_library_spec.rb +387 -0
  95. data/spec/react/observable_spec.rb +31 -0
  96. data/spec/react/opal_jquery_extensions_spec.rb +68 -0
  97. data/spec/react/param_declaration_spec.rb +253 -0
  98. data/spec/react/react_spec.rb +278 -0
  99. data/spec/react/refs_callback_spec.rb +65 -0
  100. data/spec/react/server_spec.rb +25 -0
  101. data/spec/react/state_spec.rb +52 -0
  102. data/spec/react/test/dsl_spec.rb +43 -0
  103. data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
  104. data/spec/react/test/rspec_spec.rb +62 -0
  105. data/spec/react/test/session_spec.rb +88 -0
  106. data/spec/react/test/utils_spec.rb +28 -0
  107. data/spec/react/top_level_component_spec.rb +103 -0
  108. data/spec/react/tutorial/tutorial_spec.rb +42 -0
  109. data/spec/react/validator_spec.rb +134 -0
  110. data/spec/reactive-ruby/component_loader_spec.rb +74 -0
  111. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +157 -0
  112. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +17 -0
  113. data/spec/reactive-ruby/rails/component_mount_spec.rb +64 -0
  114. data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +39 -0
  115. data/spec/spec_helper.rb +55 -0
  116. data/spec/test_app/README.md +24 -0
  117. data/spec/test_app/Rakefile +6 -0
  118. data/spec/test_app/app/assets/config/manifest.js +3 -0
  119. data/spec/test_app/app/assets/images/.keep +0 -0
  120. data/spec/test_app/app/assets/javascripts/application.rb +7 -0
  121. data/spec/test_app/app/assets/javascripts/cable.js +13 -0
  122. data/spec/test_app/app/assets/javascripts/channels/.keep +0 -0
  123. data/spec/test_app/app/assets/javascripts/server_rendering.js +5 -0
  124. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  125. data/spec/test_app/app/channels/application_cable/channel.rb +4 -0
  126. data/spec/test_app/app/channels/application_cable/connection.rb +4 -0
  127. data/spec/test_app/app/controllers/application_controller.rb +3 -0
  128. data/spec/test_app/app/controllers/concerns/.keep +0 -0
  129. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  130. data/spec/test_app/app/jobs/application_job.rb +2 -0
  131. data/spec/test_app/app/mailers/application_mailer.rb +4 -0
  132. data/spec/test_app/app/models/application_record.rb +3 -0
  133. data/spec/test_app/app/models/concerns/.keep +0 -0
  134. data/spec/test_app/app/views/components.rb +11 -0
  135. data/spec/test_app/app/views/components/hello_world.rb +11 -0
  136. data/spec/test_app/app/views/components/todo.rb +14 -0
  137. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  138. data/spec/test_app/app/views/layouts/explicit_layout.html.erb +0 -0
  139. data/spec/test_app/app/views/layouts/mailer.html.erb +13 -0
  140. data/spec/test_app/app/views/layouts/mailer.text.erb +1 -0
  141. data/spec/test_app/app/views/layouts/test_layout.html.erb +0 -0
  142. data/spec/test_app/bin/bundle +3 -0
  143. data/spec/test_app/bin/rails +4 -0
  144. data/spec/test_app/bin/rake +4 -0
  145. data/spec/test_app/bin/setup +38 -0
  146. data/spec/test_app/bin/update +29 -0
  147. data/spec/test_app/bin/yarn +11 -0
  148. data/spec/test_app/config.ru +5 -0
  149. data/spec/test_app/config/application.rb +45 -0
  150. data/spec/test_app/config/boot.rb +6 -0
  151. data/spec/test_app/config/cable.yml +10 -0
  152. data/spec/test_app/config/database.yml +25 -0
  153. data/spec/test_app/config/environment.rb +5 -0
  154. data/spec/test_app/config/environments/development.rb +54 -0
  155. data/spec/test_app/config/environments/production.rb +91 -0
  156. data/spec/test_app/config/environments/test.rb +42 -0
  157. data/spec/test_app/config/initializers/application_controller_renderer.rb +8 -0
  158. data/spec/test_app/config/initializers/assets.rb +14 -0
  159. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  160. data/spec/test_app/config/initializers/cookies_serializer.rb +5 -0
  161. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  162. data/spec/test_app/config/initializers/inflections.rb +16 -0
  163. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  164. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  165. data/spec/test_app/config/locales/en.yml +33 -0
  166. data/spec/test_app/config/puma.rb +56 -0
  167. data/spec/test_app/config/routes.rb +3 -0
  168. data/spec/test_app/config/secrets.yml +32 -0
  169. data/spec/test_app/config/spring.rb +6 -0
  170. data/spec/test_app/db/development.sqlite3 +0 -0
  171. data/spec/test_app/db/schema.rb +15 -0
  172. data/spec/test_app/db/seeds.rb +7 -0
  173. data/spec/test_app/db/test.sqlite3 +0 -0
  174. data/spec/test_app/lib/assets/.keep +0 -0
  175. data/spec/test_app/log/.keep +0 -0
  176. data/spec/test_app/package.json +5 -0
  177. data/spec/test_app/public/404.html +67 -0
  178. data/spec/test_app/public/422.html +67 -0
  179. data/spec/test_app/public/500.html +66 -0
  180. data/spec/test_app/public/apple-touch-icon-precomposed.png +0 -0
  181. data/spec/test_app/public/apple-touch-icon.png +0 -0
  182. data/spec/test_app/public/favicon.ico +0 -0
  183. data/spec/vendor/es5-shim.min.js +7 -0
  184. data/spec/vendor/jquery-2.2.4.min.js +4 -0
  185. metadata +401 -61
  186. data/CODE_OF_CONDUCT.md +0 -49
  187. data/lib/react/version.rb +0 -3
@@ -0,0 +1,32 @@
1
+ dist: trusty
2
+ language: ruby
3
+ rvm:
4
+ - ruby
5
+ env:
6
+ - DRIVER=travis HYPER_DEV_GEM_SOURCE="https://gems.ruby-hyperloop.org" TZ=Europe/Berlin
7
+ - DRIVER=beheaded HYPER_DEV_GEM_SOURCE="https://gems.ruby-hyperloop.org" TZ=Europe/Berlin
8
+ before_install:
9
+ - if [[ "$DRIVER" == "travis" ]]; then wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb; fi
10
+ - if [[ "$DRIVER" == "travis" ]]; then sudo dpkg -i google-chrome*.deb; fi
11
+ - if [[ "$DRIVER" == "beheaded" ]]; then wget https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz; fi
12
+ - if [[ "$DRIVER" == "beheaded" ]]; then tar zxf geckodriver-v0.19.1-linux64.tar.gz; fi
13
+ - if [[ "$DRIVER" == "beheaded" ]]; then sudo mv geckodriver /usr/local/bin/; fi
14
+ - gem install bundler
15
+ before_script:
16
+ - cd spec/test_app
17
+ - bundle update
18
+ - bundle exec rails db:setup
19
+ - cd ../../
20
+ - if [[ "$DRIVER" == "travis" ]]; then chromedriver-update; fi
21
+ - if [[ "$DRIVER" == "travis" ]]; then ls -lR ~/.chromedriver-helper/; fi
22
+ - if [[ "$DRIVER" == "travis" ]]; then chromedriver --version; fi
23
+ - if [[ "$DRIVER" == "travis" ]]; then google-chrome --version; fi
24
+ - if [[ "$DRIVER" == "travis" ]]; then which chromedriver; fi
25
+ - if [[ "$DRIVER" == "travis" ]]; then which google-chrome; fi
26
+ - if [[ "$DRIVER" == "beheaded" ]]; then firefox --version; fi
27
+ - if [[ "$DRIVER" == "beheaded" ]]; then geckodriver --version; fi
28
+ - if [[ "$DRIVER" == "beheaded" ]]; then which firefox; fi
29
+ - if [[ "$DRIVER" == "beheaded" ]]; then which geckodriver; fi
30
+ script: bundle exec rspec
31
+ gemfile:
32
+ - gemfiles/opal_0_11_react-rails_2_4.gemfile
@@ -0,0 +1,31 @@
1
+ opal_versions = ['0.8', '0.9', '0.10']
2
+ react_versions_map = {
3
+ '13' => '~> 1.3.3',
4
+ '14' => '~> 1.6.2',
5
+ '15' => '~> 1.10.0'
6
+ }
7
+ opal_rails_versions_map = {
8
+ '0.8' => '~> 0.8.1',
9
+ '0.9' => '~> 0.9.0',
10
+ '0.10' => '~> 0.9.0',
11
+ }
12
+
13
+ opal_versions.each do |opal_v|
14
+ react_versions_map.each do |react_v, react_rails_v|
15
+ appraise "opal-#{opal_v}-react-#{react_v}" do
16
+ ruby ">= 1.9.3"
17
+ gem 'opal', "~> #{opal_v}.0"
18
+ gem 'opal-rails', opal_rails_versions_map[opal_v]
19
+ gem 'react-rails', react_rails_v, require: false
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ appraise "opal-master-react-15" do
26
+ ruby '>= 2.0.0'
27
+ gem 'opal', git: 'https://github.com/opal/opal.git'
28
+ gem "opal-sprockets", git: 'https://github.com/opal/opal-sprockets.git'
29
+ gem 'opal-rails', '~> 0.9.0'
30
+ gem 'react-rails', '~> 2.4.0', require: false
31
+ end
@@ -0,0 +1,143 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file starting with v0.8.6.
4
+ This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
+
6
+ Changes are grouped as follows:
7
+ - **Added** for new features.
8
+ - **Changed** for changes in existing functionality.
9
+ - **Deprecated** for once-stable features to be removed in upcoming releases.
10
+ - **Removed** for deprecated features removed in this release.
11
+ - **Fixed** for any bug fixes.
12
+ - **Security** to invite users to upgrade in case of vulnerabilities.
13
+
14
+ <!--
15
+ Whitespace conventions:
16
+ - 4 spaces before ## titles
17
+ - 2 spaces before ### titles
18
+ - 1 spaces before normal text
19
+ -->
20
+
21
+ ## [0.12.0] - Unreleased
22
+
23
+ ### Added
24
+
25
+ - `React::Server` is provided as a module wrapping the original `ReactDOMServer` API, require `react/server` to use it. (#186)
26
+ - `React::Config` is introduced, `environment` is the only config option provided for now. See [#204](https://github.com/ruby-hyperloop/hyper-react/issues/204) for usage details.
27
+
28
+ ### Changed
29
+
30
+ - State syntax is now consistent with Hyperloop::Store, old syntax is deprecated. (#209, #97)
31
+
32
+ ### Deprecated
33
+
34
+ - Current ref callback behavior is deprecated. Require `"react/ref_callback"` to get the updated behavior. (#188)
35
+ - `React.render_to_string` & `React.render_to_static_markup` is deprecated, use `React::Server.render_to_string` & `React::Server.render_to_static_markup` instead. (#186)
36
+ - `react/react-source` is deprecated, use `react/react-source-browser` or `react/react-source-server` instead. For most usecase, `react/react-source-browser` is sufficient. If you are using the built-in server side rendering feature, the actual `ReactDOMServer` is already provided by the `react-rails` gem. Therefore, unless you are building a custom server side rendering mechanism, it's not suggested to use `react/react-source-server` in browser code. (#186)
37
+
38
+ ### Removed
39
+
40
+ - `react-latest` & `react-v1x` is removed. Use `react/react-source-browser` or `react/react-source-server` instead.
41
+ - Support for Ruby < 2.0 is removed. (#201)
42
+
43
+ ### Fixed
44
+
45
+ - [NativeLibrary] Passing native JS object as props will raise exception. (#195)
46
+ - Returns better error message if result of rendering block is not suitable (#207)
47
+ - Batch all state changes and execute *after* rendering cycle (#206, #178) (Code is now moved to Hyper::Store)
48
+ You can revert to the old behavior by defining the `React::State::ALWAYS_UPDATE_STATE_AFTER_RENDER = false`
49
+ - Memory Leak in render context fixed (#192)
50
+
51
+
52
+ ## [0.11.0] - 2016-12-13
53
+
54
+ ### Changed
55
+
56
+ - The whole opal-activesuppport is not loaded by default now. This gave us about 18% size reduction on the built file. If your code rely on any of the module which is not required by hyper-react, you need to require it yourself. (#135)
57
+
58
+ ### Deprecated
59
+
60
+ - Current `React.render` behavior is deprecated. Require `"react/top_level_render"` to get the updated behavior. (#187)
61
+ - `React.is_valid_element` is deprecated in favor of `React.is_valid_element?`.
62
+ - `expect(component).to render('<div />')` is now deprecated in favor of `expect(component).to render_static_html('<div />')`, which is much clearer.
63
+
64
+ ### Fixed
65
+
66
+ - `ReferenceError: window is not defined` error in prerender context with react-rails v1.10.0. (#196)
67
+ - State might not be updated using `React::Observable` from a param. (#175)
68
+ - Arity checking failed for `_react_param_conversion` & `React::Element#initialize` (#167)
69
+
70
+
71
+ ## [0.10.0] - 2016-10-30
72
+
73
+ ### Changed
74
+
75
+ - This gem is now renamed to `hyper-react`, see [UPGRADING](UPGRADING.md) for details.
76
+
77
+ ### Fixed
78
+
79
+ - ReactJS functional stateless component could not be imported from `NativeLibrary`. Note that functional component is only supported in React v14+. (#162)
80
+ - Prerender log got accumulated between reqeusts. (#176)
81
+
82
+ ## [0.9.0] - 2016-10-19
83
+
84
+ ### Added
85
+
86
+ - `react/react-source` is the suggested way to include ReactJS sources now. Simply require `react/react-source` immediately before the `require "reactrb"` in your Opal code will make it work.
87
+
88
+ ### Deprecated
89
+
90
+ - `react-latest` & `react-v1x` is deprecated. Use `react/react-source` instead.
91
+
92
+ ### Removed
93
+
94
+ - `opal-browser` is removed from runtime dependency. (#133) You will have to add `gem 'opal-browser'` to your gemfile (recommended) or remove all references to opal-browser from your manifest files.
95
+
96
+ ### Fixed
97
+
98
+ - `$window#on` in `opal-jquery` is broken. (#166)
99
+ - `Element#render` trigger unnecessary re-mounts when called multiple times. (#170)
100
+ - Gets rid of react warnings about updating state during render (#155)
101
+ - Multiple HAML classes (i.e. div.foo.bar) was not working (regression introduced in 0.8.8)
102
+ - Don't send nil (null) to form components as the value string (#157)
103
+ - Process `params` (props) correctly when using `Element#on` or `Element#render` (#158)
104
+ - Deprecate shallow param compare (#156)
105
+
106
+
107
+ ## [0.8.8] - 2016-07-13
108
+
109
+ ### Added
110
+
111
+ - More helpful error messages on render failures (#152)
112
+ - `Element#on('<my_event_name>')` subscribes to `my_event_name` (#153)
113
+
114
+ ### Changed
115
+
116
+ - `Element#on(:event)` subscribes to `on_event` for reactrb components and `onEvent` for native components. (#153)
117
+
118
+ ### Deprecated
119
+
120
+ - `Element#on(:event)` subscription to `_onEvent` is deprecated. Once you have changed params named `_on...` to `on_...` you can `require 'reactrb/new-event-name-convention.rb'` to avoid spurious react warning messages. (#153)
121
+
122
+
123
+ ### Fixed
124
+
125
+ - The `Element['#container'].render...` method generates a spurious react error (#154)
126
+
127
+
128
+
129
+
130
+ ## [0.8.7] - 2016-07-08
131
+
132
+
133
+ ### Fixed
134
+
135
+ - Opal 0.10.x compatibility
136
+
137
+
138
+ ## [0.8.6] - 2016-06-30
139
+
140
+
141
+ ### Fixed
142
+
143
+ - Method missing within a component was being reported as `incorrect const name` (#151)
data/DOCS.md ADDED
@@ -0,0 +1,1515 @@
1
+ # Hyperloop Components
2
+
3
+ ## Components DSL Overview
4
+
5
+ Hyperloop **Components** are implemented in the hyper-component and hyper-react Gems.
6
+
7
+ Hyperloop Component DSL (Domain Specific Language) is a set of class and instance methods that are used to describe your React components.
8
+
9
+ The DSL has the following major areas:
10
+
11
+ + The `Hyperloop::Component` class and the equivalent `Hyperloop::Component::Mixin` mixin
12
+ + Class methods or *macros* that describe component class level behaviors
13
+ + The four data accessors methods: `params`, `state`, `mutate`, and `children`
14
+ + The tag and component rendering methods
15
+ + Event handlers
16
+ + Miscellaneous methods
17
+
18
+ ### Hyperloop::Component
19
+
20
+ Hyperloop Components classes either include `Hyperloop::Component::Mixin` or are subclasses of `Hyperloop::Component`.
21
+
22
+ ```ruby
23
+ class Component < Hyperloop::Component
24
+ end
25
+
26
+ # if subclassing is inappropriate, you can mixin instead
27
+ class AnotherComponent
28
+ include Hyperloop::Component::Mixin
29
+ end
30
+ ```
31
+
32
+ At a minimum every component class must define a `render` macro which returns **one single** child element. That child may in turn have an arbitrarily deep structure.
33
+
34
+ ```ruby
35
+ class Component < Hyperloop::Component
36
+ render do
37
+ DIV { } # render an empty div
38
+ end
39
+ end
40
+ ```
41
+
42
+ You may also include the top level element to be rendered:
43
+
44
+ ```ruby
45
+ class Component < Hyperloop::Component
46
+ render(DIV) do
47
+ # everything will be rendered in a div
48
+ end
49
+ end
50
+ ```
51
+
52
+ To render a component, you reference its class name in the DSL as a method call. This creates a new instance, passes any parameters proceeds with the component lifecycle.
53
+
54
+ ```ruby
55
+ class AnotherComponent < Hyperloop::Component
56
+ render do
57
+ Component() # ruby syntax requires either () or {} following the class name
58
+ end
59
+ end
60
+ ```
61
+
62
+ Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` callback.
63
+
64
+ ### Macros (Class Methods)
65
+
66
+ Macros specify class wide behaviors.
67
+
68
+ ```ruby
69
+ class MyComponent < Hyperloop::Component
70
+ param ...
71
+ before_mount ...
72
+ after_mount ...
73
+ before_unmount ...
74
+ render ...
75
+ end
76
+ ```
77
+
78
+ The `param` macro describes the parameters the component expects.
79
+
80
+ The `before_mount` macro defines code to be run (a callback) when a component instance is first initialized.
81
+
82
+ The `after_mount` macro likewise runs after the instance has completed initialization, and is visible in the DOM.
83
+
84
+ The `before_unmount` macro provides any cleanup actions before the instance is destroyed.
85
+
86
+ The `render` macro defines the render method.
87
+
88
+ The available macros are: `render, param, state, mutate, before_mount, after_mount, before_receive_props, before_update, after_update, before_unmount`
89
+
90
+ ### Data Accessor Methods
91
+
92
+ The four data accessor methods - `params, state, mutate, and children` are instance methods that give access to a component's React specific instance data.
93
+
94
+ #### Params
95
+
96
+ The `params` method gives *read-only* access to each of the scalar params passed to the Component.
97
+
98
+ ```ruby
99
+ class WelcomeUser < Hyperloop::Component
100
+ param: id
101
+
102
+ render(DIV) do
103
+ user = User.find(params.id) # user is mutable
104
+ user.name = "Unknown" unless user.name
105
+ SayHello(name: user.name)
106
+ ...
107
+ end
108
+ end
109
+
110
+ class SayHello < Hyperloop::Component
111
+ param :name, type: String # params.name is immutable and will validate as a String
112
+
113
+ render do
114
+ H1 { "Hello #{params.name}" } # notice how you access name through parans
115
+ end
116
+ ```
117
+
118
+ A core design concept taken from React is that data flows down to child Components via params and params (called props in React) are immutable.
119
+
120
+ In Hyperloop, there are two exceptions to this rule:
121
+
122
+ + An instance of a Store (passed as a param) is mutable and changes to the state of the Store will cause a re-render
123
+ + An instance of a Model (which is a type of Store) will also case a re-render when changed
124
+
125
+ In the example below, clicking on the button will cause the Component to re-render (even though `book` is a `param`) because `book` is a Model. If `book` were not a Model then the Component would not re-render.
126
+
127
+ ```ruby
128
+ class Likes < Hyperloop::Component
129
+ param :book # book is an instance of the Book model
130
+
131
+ render(DIV) do
132
+ P { "#{params.book.likes.count} likes" }
133
+ BUTTON { "Like" }.on(:click) { params.book.likes += 1}
134
+ end
135
+ end
136
+ ```
137
+
138
+ >Note: Non-scalar params (objects) which are mutable through their methods are not read only. Care should be taken here as changes made to these objects will **not** cause a re-render of the Component. Specifically, if you pass a non-scalar param into a Component, and modify the internal data of that param, Hyperloop will not be notified to re-render the Component (as it does not know about the internal structure of your object). To achieve a re-render in this circumstance you will need to ensure that the parts of your object which are mutable are declared as state in a higher-order parent Component so that data can flow down from the parent to the child as per the React pattern.
139
+
140
+
141
+ #### State
142
+
143
+ In React (and Hyperloop) state is mutable. Changes to state variables cause Components to re-render and where state is passed into a child Component as a param, it will cause a re-rendering of that child Component. Change flows from a parent to a child - change does not flow upward and this is why params are not mutable.
144
+
145
+ State variables are (optionally) initialized and accessed through the `state` method.
146
+
147
+ ```ruby
148
+ class Counter < Hyperloop::Component
149
+ state count: 0 # optional initialization
150
+
151
+ render(DIV) do
152
+ BUTTON { "+" }.on(:click) { mutate.count(state.count + 1) }
153
+ P { state.count.to_s } # note how we access the count variable
154
+ end
155
+ end
156
+ ```
157
+
158
+ See [Using State](#using-state) for more information on State.
159
+
160
+ #### Mutate
161
+
162
+ The `mutate` method initializes (or updates) a reactive state variable. State variables are like *reactive* instance variables. They can only be changed using the `mutate` method, and when they change they will cause a re-render.
163
+
164
+ ```ruby
165
+ before_mount do
166
+ mutate.game_over false
167
+ end
168
+ ```
169
+
170
+ More on the details of these methods can be found in the [Component API](#top-level-api) section.
171
+
172
+ ### Tag and Component Rendering
173
+
174
+ ```ruby
175
+ ...
176
+ DIV(class: :time) do
177
+ ...
178
+ end
179
+ ...
180
+ ```
181
+
182
+ >**Note on coding style:** In the Hyperloop documentation and tutorials we use uppercase HTML elements like `DIV` and `BUTTON` as we believe this makes for greater readability in the code; specifically with code highlighting. If you do not like this you can use lowercase `div` and `button` instead.
183
+
184
+ HTML such as `DIV, A, SELECT, OPTION` etc. each have a corresponding instance method that will render that tag. For all the tags the
185
+ method call looks like this:
186
+
187
+ ```ruby
188
+ tag_name(attribute1 => value1, attribute2 => value2 ...) do
189
+ ...nested tags...
190
+ end
191
+ ```
192
+
193
+ Each key-value pair in the parameter block is passed down as an attribute to the tag as you would expect, with the exception of the `style` attribute, which takes a hash that is translated to the corresponding style string.
194
+
195
+ The same rules apply for application defined components, except that the class constant is used to reference the component.
196
+
197
+ ```ruby
198
+ Clock(mode: 12)
199
+ ```
200
+
201
+ ### Using Strings
202
+
203
+ Strings are treated specially as follows:
204
+
205
+ If a render method or a nested tag block returns a string, the string is automatically wrapped in a `<span>` tag.
206
+
207
+ The code `SPAN { "hello" }` can be shortened to `"hello".SPAN`, likewise for `BR, PARA, TD, TH` tags.
208
+
209
+ `"some string".BR` generates `<span>some string<span><br/>`
210
+
211
+
212
+ ```ruby
213
+ Time.now.strftime(FORMATS[state.mode]).SPAN # generates <span>...current time formatted...</span>
214
+ ...
215
+ OPTION(value: 12) { "12 Hour Clock" } # generates <option value=12><span>12 Hour Clock</span></option>
216
+ ```
217
+
218
+ ### Event Handlers
219
+
220
+ Event Handlers are attached to tags and components using the `on` method.
221
+
222
+ ```ruby
223
+ SELECT ... do
224
+ ...
225
+ end.on(:change) do |e|
226
+ mutate.mode(e.target.value.to_i)
227
+ end
228
+ ```
229
+
230
+ The `on` method takes the event name symbol (note that `onClick` becomes `:click`) and the block is passed the React.js event object.
231
+
232
+ Event handlers can be chained like so
233
+
234
+ ```ruby
235
+ INPUT ... do
236
+ ...
237
+ end.on(:key_up) do |e|
238
+ ...
239
+ end.on(:change) do |e|
240
+ ...
241
+ end
242
+ ```
243
+
244
+ ### Miscellaneous Methods
245
+
246
+ `force_update!` is a component instance method that causes the component to re-rerender. This method is seldom (if ever) needed.
247
+
248
+ `as_node` can be attached to a component or tag, and removes the element from the rendering buffer and returns it. This is useful when you need store an element in some data structure, or passing to a native JS component. When passing an element to another Hyperloop Component `.as_node` will be automatically applied so you normally don't need it.
249
+
250
+ `render` can be applied to the objects returned by `as_node` and `children` to actually render the node.
251
+
252
+ ```ruby
253
+ class Test < Hyperloop::Component
254
+ param :node
255
+
256
+ render do
257
+ DIV do
258
+ children.each do |child|
259
+ params.node.render
260
+ child.render
261
+ end
262
+ params.node.render
263
+ end
264
+ end
265
+ end
266
+ ```
267
+
268
+ ### Ruby and Hyperloop
269
+
270
+ A key design goal of the DSL is to make it work seamlessly with the rest of Ruby. Notice in the above example, the use of constant declaration (`FORMATS`), regular instance variables (`@timer`), and other non-react methods like `every` (an Opal Browser method).
271
+
272
+ Component classes can be organized like any other class into a logical module hierarchy or even subclassed.
273
+
274
+ Likewise the render method can invoke other methods to compute values or even internally build tags.
275
+
276
+ ### DSL Gotchas
277
+
278
+ There are few gotchas with the DSL you should be aware of:
279
+
280
+ React has implemented a browser-independent events and DOM system for performance and cross-browser compatibility reasons. We took the opportunity to clean up a few rough edges in browser DOM implementations.
281
+
282
+ * All DOM properties and attributes (including event handlers) should be snake_cased to be consistent with standard Ruby style. We intentionally break with the spec here since the spec is inconsistent. **However**, `data-*` and `aria-*` attributes [conform to the specs](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#data-*) and should be lower-cased only.
283
+ * The `style` attribute accepts a Hash with camelCased properties rather than a CSS string. This is more efficient, and prevents XSS security holes.
284
+ * All event objects conform to the W3C spec, and all events (including submit) bubble correctly per the W3C spec. See [Event System](#event-handling-and-synthetic-events) for more details.
285
+ * The `onChange` event (`on(:change)`) behaves as you would expect it to: whenever a form field is changed this event is fired rather than inconsistently on blur. We intentionally break from existing browser behavior because `onChange` is a misnomer for its behavior and React relies on this event to react to user input in real time.
286
+ * Form input attributes such as `value` and `checked`, as well as `textarea`.
287
+
288
+ #### HTML Entities
289
+
290
+ If you want to display an HTML entity within dynamic content, you will run into double escaping issues as React.js escapes all the strings you are displaying in order to prevent a wide range of XSS attacks by default.
291
+
292
+ ```ruby
293
+ DIV {'First &middot; Second' }
294
+ # Bad: It displays "First &middot; Second"
295
+ ```
296
+
297
+ To workaround this you have to insert raw HTML.
298
+
299
+ ```ruby
300
+ DIV(dangerously_set_inner_HTML: { __html: "First &middot; Second"})
301
+ ```
302
+
303
+ #### Custom HTML Attributes
304
+
305
+ If you pass properties to native HTML elements that do not exist in the HTML specification, React will not render them. If you want to use a custom attribute, you should prefix it with `data-`.
306
+
307
+ ```ruby
308
+ DIV("data-custom-attribute" => "foo")
309
+ ```
310
+
311
+ [Web Accessibility](http://www.w3.org/WAI/intro/aria) attributes starting with `aria-` will be rendered properly.
312
+
313
+ ```ruby
314
+ DIV("aria-hidden" => true)
315
+ ```
316
+
317
+ #### Invoking Application Components
318
+
319
+ When invoking a custom component you must have a (possibly empty) parameter list or (possibly empty) block. This is not necessary
320
+ with standard html tags.
321
+
322
+ ```ruby
323
+ MyCustomComponent() # okay
324
+ MyCustomComponent {} # okay
325
+ MyCustomComponent # breaks
326
+ br # okay
327
+ ```
328
+
329
+ ## Components and State
330
+
331
+ ### Using State
332
+
333
+ #### A Simple Example
334
+
335
+ <div class="codemirror-live-edit"
336
+ data-heading="A simple Component rendering state"
337
+ data-rows=12
338
+ data-top-level-component="LikeButton">
339
+ <pre>
340
+ class LikeButton < Hyperloop::Component
341
+
342
+ render(DIV) do
343
+ P do
344
+ "You #{state.liked ? 'like' : 'haven\'t liked'} this. Click to toggle."
345
+ end.on(:click) do
346
+ mutate.liked !state.liked
347
+ end
348
+ end
349
+ end
350
+ </pre></div>
351
+
352
+
353
+ ### Components are Just State Machines
354
+
355
+ React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent.
356
+
357
+ In React, you simply update a component's state, and then the new UI will be rendered on this new state. React takes care of updating the DOM for you in the most efficient way.
358
+
359
+ ### How State Works
360
+
361
+ To change a state variable you use `mutate.state_variable` and pass the new value. For example `mutate.liked(!state.like)` *gets* the current value of like, toggles it, and then *updates* it. This in turn causes the component to be rerendered. For more details on how this works, and the full syntax of the update method see [the component API reference](#top-level-api)
362
+
363
+ ### What Components Should Have State?
364
+
365
+ Most of your components should simply take some params and render based on their value. However, sometimes you need to respond to user input, a server request or the passage of time. For this you use state.
366
+
367
+ **Try to keep as many of your components as possible stateless.** By doing this you'll isolate the state to its most logical place and minimize redundancy, making it easier to reason about your application.
368
+
369
+ A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via `params`. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
370
+
371
+ ### What *Should* Go in State?
372
+
373
+ **State should contain data that a component's event handlers, timers, or http requests may change and trigger a UI update.**
374
+
375
+ When building a stateful component, think about the minimal possible representation of its state, and only store those properties in `state`. Add to your class methods to compute higher level values from your state variables. Avoid adding redundant or computed values as state variables as
376
+ these values must then be kept in sync whenever state changes.
377
+
378
+ ### What *Shouldn't* Go in State?
379
+
380
+ `state` should only contain the minimal amount of data needed to represent your UI's state. As such, it should not contain:
381
+
382
+ * **Computed data:** Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation during rendering. For example, if you have an array of list items in state and you want to render the count as a string, simply render `"#{state.list_items.length} list items'` in your `render` method rather than storing the count as another state.
383
+ * **Data that does not effect rendering:** For example handles on timers, that need to be cleaned up when a component unmounts should go
384
+ in plain old instance variables.
385
+
386
+ ## Multiple Components
387
+
388
+ So far, we've looked at how to write a single component to display data and handle user input. Next let's examine one of React's finest features: composability.
389
+
390
+ ### Motivation: Separation of Concerns
391
+
392
+ By building modular components that reuse other components with well-defined interfaces, you get much of the same benefits that you get by using functions or classes. Specifically you can *separate the different concerns* of your app however you please simply by building new components. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain.
393
+
394
+ ### Composition Example
395
+
396
+ Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API.
397
+
398
+ ```ruby
399
+ class Avatar < Hyperloop::Component
400
+ param :user_name
401
+ render(DIV) do
402
+ ProfilePic user_name: params.user_name
403
+ ProfileLink user_name: params.user_name
404
+ end
405
+ end
406
+
407
+ class ProfilePic < Hyperloop::Component
408
+ param :user_name
409
+ render do
410
+ IMG src: "https://graph.facebook.com/#{params.user_name}/picture"
411
+ end
412
+ end
413
+
414
+ class ProfileLink < Hyperloop::Component
415
+ param :user_name
416
+ render do
417
+ A href: "https://www.facebook.com/#{params.user_name}" do
418
+ params.user_name
419
+ end
420
+ end
421
+ end
422
+ ```
423
+
424
+ ### Ownership
425
+
426
+ In the above example, instances of `Avatar` *own* instances of `ProfilePic` and `ProfileLink`. In React, **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is *owned by* `Y`. As discussed earlier, a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent.
427
+
428
+ It's important to draw a distinction between the owner-ownee relationship and the parent-child relationship. The owner-ownee relationship is specific to React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `div`, `ProfilePic` and `ProfileLink` instances, and `div` is the **parent** (but not owner) of the `ProfilePic` and `ProfileLink` instances.
429
+
430
+ ### Children
431
+
432
+ When you create a React component instance, you can include additional React components or JavaScript expressions between the opening and closing tags like this:
433
+
434
+ ```ruby
435
+ Parent { Child() }
436
+ ```
437
+
438
+ `Parent` can iterate over its children by accessing its `children` method.
439
+
440
+ ### Child Reconciliation
441
+
442
+ **Reconciliation is the process by which React updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass
443
+ the items will be completely rerendered:
444
+
445
+ ```ruby
446
+ render do
447
+ params.items.each do |item|
448
+ para do
449
+ item[:text]
450
+ end
451
+ end
452
+ end
453
+ ```
454
+
455
+ What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`?
456
+ Intuitively, the paragraph `<p>foo</p>` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the *order* of the children.
457
+
458
+ ### Stateful Children
459
+
460
+ For most components, this is not a big deal. However, for stateful components that maintain data in `state` across render passes, this can be very problematic.
461
+
462
+ In most cases, this can be sidestepped by hiding elements based on some property change:
463
+
464
+ ```ruby
465
+ render do
466
+ state.items.each do |item|
467
+ PARA(style: {display: item[:some_property] == "some state" ? :block : :none}) do
468
+ item[:text]
469
+ end
470
+ end
471
+ end
472
+ ```
473
+
474
+ ### Dynamic Children
475
+
476
+ The situation gets more complicated when the children are shuffled around (as in search results) or if new components are added onto the front of the list (as in streams). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a `key`:
477
+
478
+ ```ruby
479
+ param :results, type: [Hash] # each result is a hash of the form {id: ..., text: ....}
480
+ render do
481
+ OL do
482
+ params.results.each do |result|
483
+ LI(key: result[:id]) { result[:text] }
484
+ end
485
+ end
486
+ end
487
+ ```
488
+
489
+ When React reconciles the keyed children, it will ensure that any child with `key` will be reordered (instead of clobbered) or destroyed (instead of reused).
490
+
491
+ The `key` should *always* be supplied directly to the components in the array, not to the container HTML child of each component in the array:
492
+
493
+ ```ruby
494
+ # WRONG!
495
+ class ListItemWrapper < Hyperloop::Component
496
+ param :data
497
+ render do
498
+ LI(key: params.data[:id]) { params.data[:text] }
499
+ end
500
+ end
501
+ class MyComponent < Hyperloop::Component
502
+ param :results
503
+ render do
504
+ UL do
505
+ params.result.each do |result|
506
+ ListItemWrapper data: result
507
+ end
508
+ end
509
+ end
510
+ end
511
+ ```
512
+ ```ruby
513
+ # CORRECT
514
+ class ListItemWrapper < Hyperloop::Component
515
+ param :data
516
+ render do
517
+ LI { params.data[:text] }
518
+ end
519
+ end
520
+ class MyComponent < Hyperloop::Component
521
+ param :results
522
+ render do
523
+ UL do
524
+ params.result.each do |result|
525
+ ListItemWrapper key: result[:id], data: result
526
+ end
527
+ end
528
+ end
529
+ end
530
+ ```
531
+
532
+ ### Data Flow
533
+
534
+ In React, data flows from owner to owned component through the params as discussed above. This is effectively one-way data binding: owners bind their owned component's param to some value the owner has computed based on its `params` or `state`. Since this process happens recursively, data changes are automatically reflected everywhere they are used.
535
+
536
+ ### Stores
537
+
538
+ Managing state between components is best done using Stores as many Components can access one store. This saves passing data btween Components. Please see the [Store documentation](/docs/stores/overview) for details.
539
+
540
+ ### Reusable Components
541
+
542
+ When designing interfaces, break down the common design elements (buttons, form fields, layout components, etc.) into reusable components with well-defined interfaces. That way, the next time you need to build some UI, you can write much less code. This means faster development time, fewer bugs, and fewer bytes down the wire.
543
+
544
+ ### Param Validation
545
+
546
+ As your app grows it's helpful to ensure that your components are used correctly. We do this by allowing you to specify the expected ruby class of your parameters. When an invalid value is provided for a param, a warning will be shown in the JavaScript console. Note that for performance reasons type checking is only done in development mode. Here is an example showing typical type specifications:
547
+
548
+ ```ruby
549
+ class ManyParams < Hyperloop::Component
550
+ param :an_array, type: [] # or type: Array
551
+ param :a_string, type: String
552
+ param :array_of_strings, type: [String]
553
+ param :a_hash, type: Hash
554
+ param :some_class, type: SomeClass # works with any class
555
+ param :a_string_or_nil, type: String, allow_nil: true
556
+ end
557
+ ```
558
+
559
+ Note that if the param can be nil, add `allow_nil: true` to the specification.
560
+
561
+ ### Default Param Values
562
+
563
+ React lets you define default values for your `params`:
564
+
565
+ ```ruby
566
+ class ManyParams < Hyperloop::Component
567
+ param :an_optional_param, default: "hello", type: String, allow_nil: true
568
+ ```
569
+
570
+ If no value is provided for `:an_optional_param` it will be given the value `"hello"`
571
+
572
+ ### Params of type Proc
573
+
574
+ A Ruby `Proc` can be passed to a component like any other object. The `param` macro treats params declared as type `Proc` specially, and will automatically call the proc when the param name is used on the params method.
575
+
576
+ ```ruby
577
+ param :all_done, type: Proc
578
+ ...
579
+ # typically in an event handler
580
+ params.all_done(data) # instead of params.all_done.call(data)
581
+ ```
582
+
583
+ Proc params can be optional, using the `default: nil` and `allow_nil: true` options. Invoking a nil proc param will do nothing. This is handy for allowing optional callbacks.
584
+
585
+ ### Other Params
586
+
587
+ A common type of React component is one that extends a basic HTML element in a simple way. Often you'll want to copy any HTML attributes passed to your component to the underlying HTML element.
588
+
589
+ To do this use the `collect_other_params_as` macro which will gather all the params you did not declare into a hash. Then you can pass this hash on to the child component
590
+
591
+ ```ruby
592
+ class CheckLink < Hyperloop::Component
593
+ collect_other_params_as :attributes
594
+ render do
595
+ # we just pass along any incoming attributes
596
+ a(attributes) { '√ '.span; children.each &:render }
597
+ end
598
+ end
599
+ # CheckLink(href: "/checked.html")
600
+ ```
601
+
602
+ Note: `collect_other_params_as` builds a hash, so you can merge other data in or even delete elements out as needed.
603
+
604
+
605
+ ### Mixins and Inheritance
606
+
607
+ Ruby has a rich set of mechanisms enabling code reuse, and Hyperloop is intended to be a team player in your Ruby application. Components can be subclassed, and they can include (or mixin) other modules. You can also create a component by including `Hyperloop::Component::Mixin` which allows a class to inherit from some other non-react class, and then mixin the React DSL.
608
+
609
+ ```ruby
610
+ # make a SuperFoo react component class
611
+ class Foo < SuperFoo
612
+ include Hyperloop::Component::Mixin
613
+ end
614
+ ```
615
+
616
+ One common use case is a component wanting to update itself on a time interval. It's easy to use the kernel method `every`, but it's important to cancel your interval when you don't need it anymore to save memory. React provides [lifecycle methods](/docs/working-with-the-browser.html#component-lifecycle) that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide a React friendly `every` function that will automatically get cleaned up when your component is destroyed.
617
+
618
+
619
+ <div class="codemirror-live-edit"
620
+ data-heading="Using state"
621
+ data-rows=33
622
+ data-top-level-component="TickTock">
623
+ <pre>
624
+ module ReactInterval
625
+
626
+ def self.included(base)
627
+ base.before_mount do
628
+ @intervals = []
629
+ end
630
+
631
+ base.before_unmount do
632
+ @intervals.each(&:stop)
633
+ end
634
+ end
635
+
636
+ def every(seconds, &block)
637
+ Kernel.every(seconds, &block).tap { |i| @intervals << i }
638
+ end
639
+ end
640
+
641
+ class TickTock < Hyperloop::Component
642
+ include ReactInterval
643
+
644
+ before_mount do
645
+ state.seconds! 0
646
+ end
647
+
648
+ after_mount do
649
+ every(1) { mutate.seconds state.seconds+1}
650
+ end
651
+
652
+ render(DIV) do
653
+ "Hyperloop has been running for #{state.seconds} seconds".para
654
+ end
655
+ end
656
+ </pre></div>
657
+
658
+ Notice that TickTock effectively has two before_mount callbacks, one that is called to initialize the `@intervals` array and another to initialize `state.seconds`
659
+
660
+ ## Lifecycle Callbacks
661
+
662
+ A component may define callbacks for each phase of the components lifecycle:
663
+
664
+ * `before_mount`
665
+ * `render`
666
+ * `after_mount`
667
+ * `before_receive_props`
668
+ * `before_update`
669
+ * `after_update`
670
+ * `before_unmount`
671
+
672
+ All the callback macros may take a block or the name of an instance method to be called.
673
+
674
+ ```ruby
675
+ class AComponent < Hyperloop::Component
676
+ before_mount do
677
+ # initialize stuff here
678
+ end
679
+ before_unmount :cleanup # call the cleanup method before unmounting
680
+ ...
681
+ end
682
+ ```
683
+
684
+ Except for the render callback, multiple callbacks may be defined for each lifecycle phase, and will be executed in the order defined, and from most deeply nested subclass outwards.
685
+
686
+ Details on the component lifecycle is described [here](docs/component-specs.html)
687
+
688
+ ### The param macro
689
+
690
+ Within a React Component the `param` macro is used to define the parameter signature of the component. You can think of params as
691
+ the values that would normally be sent to the instance's `initialize` method, but with the difference that a React Component gets new parameters when it is rerendered.
692
+
693
+ The param macro has the following syntax:
694
+
695
+ ```ruby
696
+ param symbol, ...options... # or
697
+ param symbol => default_value, ...options...
698
+ ```
699
+
700
+ Available options are `:default_value => ...any value...` and `:type => ...class_spec...`
701
+ where class_spec is either a class name, or `[]` (shorthand for Array), or `[ClassName]` (meaning array of `ClassName`.)
702
+
703
+ Note that the default value can be specied either as the hash value of the symbol, or explicitly using the `:default_value` key.
704
+
705
+ Examples:
706
+
707
+ ```ruby
708
+ param :foo # declares that we must be provided with a parameter foo when the component is instantiated or re-rerendered.
709
+ param :foo => "some default" # declares that foo is optional, and if not present the value "some default" will be used.
710
+ param foo: "some default" # same as above using ruby 1.9 JSON style syntax
711
+ param :foo, default: "some default" # same as above but uses explicit default key
712
+ param :foo, type: String # foo is required and must be of type String
713
+ param :foo, type: [String] # foo is required and must be an array of Strings
714
+ param foo: [], type: [String] # foo must be an array of strings, and has a default value of the empty array.
715
+ ```
716
+
717
+ #### Accessing param values
718
+
719
+ The component instance method `params` gives access to all declared params. So for example
720
+
721
+ ```ruby
722
+ class Hello < Hyperloop::Component
723
+ param visitor: "World", type: String
724
+
725
+ render do
726
+ "Hello #{params.visitor}"
727
+ end
728
+ end
729
+ ```
730
+
731
+ #### Params of type Proc
732
+
733
+ A param of type proc (i.e. `param :update, type: Proc`) gets special treatment that will directly
734
+ call the proc when the param is accessed.
735
+
736
+ ```ruby
737
+ class Alarm < Hyperloop::Component
738
+ param :at, type: Time
739
+ param :notify, type: Proc
740
+
741
+ after_mount do
742
+ @clock = every(1) do
743
+ if Time.now > params.at
744
+ params.notify
745
+ @clock.stop
746
+ end
747
+ force_update!
748
+ end
749
+ end
750
+
751
+ render do
752
+ "#{Time.now}"
753
+ end
754
+ end
755
+ ```
756
+
757
+ If for whatever reason you need to get the actual proc instead of calling it use `params.method(*symbol name of method*)`
758
+
759
+ ### The state instance method
760
+
761
+ React state variables are *reactive* component instance variables that cause rerendering when they change.
762
+
763
+ State variables are accessed via the `state` instance method which works like the `params` method. Like normal instance variables, state variables are created when they are first accessed, so there is no explicit declaration.
764
+
765
+ To access the value of a state variable `foo` you would say `state.foo`.
766
+
767
+ To initialize or update a state variable you use `mutate.` followed by its name. For example `mutate.foo []` would initialize `foo` to an empty array. Unlike the assignment operator, the mutate method returns the current value (before it is changed.)
768
+
769
+ Often state variables have complex values with their own internal state, an array for example. The problem is as you push new values onto the array you are not changing the object pointed to by the state variable, but its internal state.
770
+
771
+ To handle this use the same `mutate` prefix with **no** parameter, and then apply any update methods to the resulting value. The underlying value will be updated, **and** the underlying system will be notified that a state change has occurred.
772
+
773
+ For example:
774
+
775
+ ```ruby
776
+ mutate.foo [] # initialize foo (returns nil)
777
+ #...later...
778
+ mutate.foo << 12 # push 12 onto foo's array
779
+ #...or...
780
+ mutate.foo {}
781
+ mutate.foo[:house => :boat]
782
+ ```
783
+
784
+ The rule is simple: anytime you are updating a state variable use `mutate`.
785
+ <br><br>
786
+
787
+ > #### Tell Me How That Works???
788
+ >
789
+ > A state variables mutate method can optionally accept one parameter. If a parameter is passed, then the method will 1) save the current value, 2) update the value to the passed parameter, 3) update the underlying react.js state object, 4) return the saved value.
790
+
791
+ #### The force_update! method
792
+
793
+ The `force_update!` instance method causes the component to re-render. Usually this is not necessary as rendering will occur when state variables change, or new params are passed. For a good example of using `force_update!` see the `Alarm` component above. In this case there is no reason to have a state track of the time separately, so we just call `force_update!` every second.
794
+
795
+ #### The dom_node method
796
+
797
+ Returns the dom_node that this component instance is mounted to. Typically used in the `after_mount` callback to setup linkages to external libraries.
798
+
799
+ #### The children method
800
+
801
+ Along with params components may be passed a block which is used to build the components children.
802
+
803
+ The instance method `children` returns an enumerable that is used to access the unrendered children of a component.
804
+
805
+ <div class="codemirror-live-edit"
806
+ data-heading="The children method"
807
+ data-rows=20
808
+ data-top-level-component="Indenter">
809
+ <pre>
810
+ class IndentEachLine < Hyperloop::Component
811
+ param by: 20, type: Integer
812
+
813
+ render(DIV) do
814
+ children.each_with_index do |child, i|
815
+ child.render(style: {"margin-left" => params.by*i})
816
+ end
817
+ end
818
+ end
819
+
820
+ class Indenter < Hyperloop::Component
821
+ render(DIV) do
822
+ IndentEachLine(by: 100) do
823
+ DIV {"Line 1"}
824
+ DIV {"Line 2"}
825
+ DIV {"Line 3"}
826
+ end
827
+ end
828
+ end
829
+ </pre></div>
830
+
831
+ ### Lifecycle Methods
832
+
833
+ A component class may define callbacks for specific points in a component's lifecycle.
834
+
835
+ #### Rendering
836
+
837
+ The lifecycle revolves around rendering the component. As the state or parameters of a component changes, its render method will be called to generate the new HTML. The rest of the callbacks hook into the lifecycle before or after rendering.
838
+
839
+ For reasons described below Hyperloop provides a render callback to simplify defining the render method:
840
+
841
+ ```ruby
842
+ render do ....
843
+ end
844
+ ```
845
+
846
+ The render callback will generate the components render method. It may optionally take the container component and params:
847
+
848
+ ```ruby
849
+ render(:DIV, class: 'my-class') do
850
+ ...
851
+ end
852
+ ```
853
+
854
+ which would be equivilent to:
855
+
856
+ ```ruby
857
+ render do
858
+ DIV(class: 'my-class') do
859
+ ...
860
+ end
861
+ end
862
+ ```
863
+
864
+ The purpose of the render callback is syntactic. Many components consist of a static outer container with possibly some parameters, and most component's render method by necessity will be longer than the normal *10 line* ruby style guideline. The render call back solves both these problems by allowing the outer container to be specified as part of the callback parameter (which reads very nicely) and because the render code is now specified as a block you avoid the 10 line limitation, while encouraging the rest of your methods to adhere to normal ruby style guides
865
+
866
+ #### Before Mounting (first render)
867
+
868
+ ```ruby
869
+ before_mount do ...
870
+ end
871
+ ```
872
+
873
+ Invoked once when the component is first instantiated, immediately before the initial rendering occurs. This is where state variables should
874
+ be initialized.
875
+
876
+ This is the only life cycle method that is called during `render_to_string` used in server side pre-rendering.
877
+
878
+ #### After Mounting (first render)
879
+
880
+ ```ruby
881
+ after_mount do ...
882
+ end
883
+ ```
884
+
885
+ Invoked once, only on the client (not on the server), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children (e.g., to access the underlying DOM representation). The `after_mount` callbacks of children components are invoked before that of parent components.
886
+
887
+ If you want to integrate with other JavaScript frameworks, set timers using the `after` or `every` methods, or send AJAX requests, perform those operations in this method. Attempting to perform such operations in before_mount will cause errors during prerendering because none of these operations are available in the server environment.
888
+
889
+
890
+ #### Before Receiving New Params
891
+
892
+ ```ruby
893
+ before_receive_props do |new_params_hash| ...
894
+ end
895
+ ```
896
+
897
+ Invoked when a component is receiving *new* params (React.js props). This method is not called for the initial render.
898
+
899
+ Use this as an opportunity to react to a prop transition before `render` is called by updating any instance or state variables. The
900
+ new_props block parameter contains a hash of the new values.
901
+
902
+ ```ruby
903
+ before_receive_props do |next_props|
904
+ state.likes_increasing! (next_props[:like_count] > params.like_count)
905
+ end
906
+ ```
907
+
908
+ > Note:
909
+ >
910
+ > There is no analogous method `before_receive_state`. An incoming param may cause a state change, but the opposite is not true. If you need to perform operations in response to a state change, use `before_update`.
911
+
912
+
913
+ #### Controlling Updates
914
+
915
+ Normally Hyperloop will only update a component if some state variable or param has changed. To override this behavior you can redefine the `should_component_update?` instance method. For example, assume that we have a state called `funky` that for whatever reason, we
916
+ cannot update using the normal `state.funky!` update method. So what we can do is override `should_component_update?` call `super`, and then double check if the `funky` has changed by doing an explicit comparison.
917
+
918
+ ```ruby
919
+ class RerenderMore < Hyperloop::Component
920
+ def should_component_update?(new_params_hash, new_state_hash)
921
+ super || new_state_hash[:funky] != state.funky
922
+ end
923
+ end
924
+ ```
925
+
926
+ Why would this happen? Most likely there is integration between new Hyperloop Components and other data structures being maintained outside of Hyperloop, and so we have to do some explicit comparisons to detect the state change.
927
+
928
+ Note that `should_component_update?` is not called for the initial render or when `force_update!` is used.
929
+
930
+ > Note to react.js readers. Essentially Hyperloop assumes components are "well behaved" in the sense that all state changes
931
+ > will be explicitly declared using the state update ("!") method when changing state. This gives similar behavior to a
932
+ > "pure" component without the possible performance penalties.
933
+ > To achieve the standard react.js behavior add this line to your class `def should_component_update?; true; end`
934
+
935
+ #### Before Updating (re-rendering)
936
+
937
+ ```ruby
938
+ before_update do ...
939
+ end
940
+ ```
941
+
942
+ Invoked immediately before rendering when new params or state are bein#g received.
943
+
944
+
945
+ #### After Updating (re-rendering)
946
+
947
+ ```ruby
948
+ after_update do ...
949
+ end
950
+ ```
951
+
952
+ Invoked immediately after the component's updates are flushed to the DOM. This method is not called for the initial render.
953
+
954
+ Use this as an opportunity to operate on the DOM when the component has been updated.
955
+
956
+ #### Unmounting
957
+
958
+ ```ruby
959
+ before_unmount do ...
960
+ end
961
+ ```
962
+
963
+ Invoked immediately before a component is unmounted from the DOM.
964
+
965
+ Perform any necessary cleanup in this method, such as invalidating timers or cleaning up any DOM elements that were created in the `after_mount` callback.
966
+
967
+ ### Event Handlers
968
+
969
+ #### Event Handling and Synthetic Events
970
+
971
+ With React you attach event handlers to elements using the `on` method. React ensures that all events behave identically in IE8 and above by implementing a synthetic event system. That is, React knows how to bubble and capture events according to the spec, and the events passed to your event handler are guaranteed to be consistent with [the W3C spec](http://www.w3.org/TR/DOM-Level-3-Events/), regardless of which browser you're using.
972
+
973
+ #### Under the Hood: Event Delegation
974
+
975
+ React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see [David Walsh's excellent blog post](http://davidwalsh.name/event-delegate).
976
+
977
+ #### React::Event
978
+
979
+ Your event handlers will be passed instances of `React::Event`, a wrapper around react.js's `SyntheticEvent` which in turn is a cross browser wrapper around the browser's native event. It has the same interface as the browser's native event, including `stopPropagation()` and `preventDefault()`, except the events work identically across all browsers.
980
+
981
+ For example:
982
+
983
+ ```ruby
984
+ class YouSaid < Hyperloop::Component
985
+
986
+ render(DIV) do
987
+ INPUT(value: state.value).
988
+ on(:key_down) do |e|
989
+ alert "You said: #{state.value}" if e.key_code == 13
990
+ end.
991
+ on(:change) do |e|
992
+ mutate.value e.target.value
993
+ end
994
+ end
995
+ end
996
+ ```
997
+
998
+ If you find that you need the underlying browser event for some reason use the `native_event`.
999
+
1000
+ In the following responses shown as (native ...) indicate the value returned is a native object with an Opal wrapper. In some cases there will be opal methods available (i.e. for native DOMNode values) and in other cases you will have to convert to the native value
1001
+ with `.to_n` and then use javascript directly.
1002
+
1003
+ Every `React::Event` has the following methods:
1004
+
1005
+ ```ruby
1006
+ bubbles -> Boolean
1007
+ cancelable -> Boolean
1008
+ current_target -> (native DOM node)
1009
+ default_prevented -> Boolean
1010
+ event_phase -> Integer
1011
+ is_trusted -> Boolean
1012
+ native_event -> (native Event)
1013
+ prevent_default -> Proc
1014
+ is_default_prevented -> Boolean
1015
+ stop_propagation -> Proc
1016
+ is_propagation_stopped -> Boolean
1017
+ target -> (native DOMEventTarget)
1018
+ timestamp -> Integer (use Time.at to convert to Time)
1019
+ type -> String
1020
+ ```
1021
+
1022
+ #### Event pooling
1023
+
1024
+ The underlying React `SyntheticEvent` is pooled. This means that the `SyntheticEvent` object will be reused and all properties will be nullified after the event callback has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way.
1025
+
1026
+ #### Supported Events
1027
+
1028
+ React normalizes events so that they have consistent properties across
1029
+ different browsers.
1030
+
1031
+
1032
+ #### Clipboard Events
1033
+
1034
+ Event names:
1035
+
1036
+ ```ruby
1037
+ :copy, :cut, :paste
1038
+ ```
1039
+
1040
+ Available Methods:
1041
+
1042
+ ```ruby
1043
+ clipboard_data -> (native DOMDataTransfer)
1044
+ ```
1045
+
1046
+ #### Composition Events (not tested)
1047
+
1048
+ Event names:
1049
+
1050
+ ```ruby
1051
+ :composition_end, :composition_start, :composition_update
1052
+ ```
1053
+
1054
+ Available Methods:
1055
+
1056
+ ```ruby
1057
+ data -> String
1058
+ ```
1059
+
1060
+ #### Keyboard Events
1061
+
1062
+ Event names:
1063
+
1064
+ ```ruby
1065
+ :key_down, :key_press, :key_up
1066
+ ```
1067
+
1068
+ Available Methods:
1069
+
1070
+ ```ruby
1071
+ alt_key -> Boolean
1072
+ char_code -> Integer
1073
+ ctrl_key -> Boolean
1074
+ get_modifier_state(key) -> Boolean (i.e. get_modifier_key(:Shift)
1075
+ key -> String
1076
+ key_code -> Integer
1077
+ locale -> String
1078
+ location -> Integer
1079
+ meta_key -> Boolean
1080
+ repeat -> Boolean
1081
+ shift_key -> Boolean
1082
+ which -> Integer
1083
+ ```
1084
+
1085
+
1086
+ #### Focus Events
1087
+
1088
+ Event names:
1089
+
1090
+ ```ruby
1091
+ :focus, :blur
1092
+ ```
1093
+
1094
+ Available Methods:
1095
+
1096
+ ```ruby
1097
+ related_target -> (Native DOMEventTarget)
1098
+ ```
1099
+
1100
+ These focus events work on all elements in the React DOM, not just form elements.
1101
+
1102
+ #### Form Events
1103
+
1104
+ Event names:
1105
+
1106
+ ```ruby
1107
+ :change, :input, :submit
1108
+ ```
1109
+
1110
+ #### Mouse Events
1111
+
1112
+ Event names:
1113
+
1114
+ ```ruby
1115
+ :click, :context_menu, :double_click, :drag, :drag_end, :drag_enter, :drag_exit
1116
+ :drag_leave, :drag_over, :drag_start, :drop, :mouse_down, :mouse_enter,
1117
+ :mouse_leave, :mouse_move, :mouse_out, :mouse_over, :mouse_up
1118
+ ```
1119
+
1120
+ The `:mouse_enter` and `:mouse_leave` events propagate from the element being left to the one being entered instead of ordinary bubbling and do not have a capture phase.
1121
+
1122
+ Available Methods:
1123
+
1124
+ ```ruby
1125
+ alt_key -> Boolean
1126
+ button -> Integer
1127
+ buttons -> Integer
1128
+ client_x -> Integer
1129
+ number client_y -> Integer
1130
+ ctrl_key -> Boolean
1131
+ get_modifier_state(key) -> Boolean
1132
+ meta_key -> Boolean
1133
+ page_x -> Integer
1134
+ page_y -> Integer
1135
+ related_target -> (Native DOMEventTarget)
1136
+ screen_x -> Integer
1137
+ screen_y -> Integer
1138
+ shift_key -> Boolean
1139
+ ```
1140
+
1141
+ #### Drag and Drop example
1142
+
1143
+ Here is a Hyperloop version of this [w3schools.com](https://www.w3schools.com/html/html5_draganddrop.asp) example:
1144
+
1145
+ ```ruby
1146
+ DIV(id: "div1", style: {width: 350, height: 70, padding: 10, border: '1px solid #aaaaaa'})
1147
+ .on(:drop) do |ev|
1148
+ ev.prevent_default
1149
+ data = `#{ev.native_event}.native.dataTransfer.getData("text")`
1150
+ `#{ev.target}.native.appendChild(document.getElementById(data))`
1151
+ end
1152
+ .on(:drag_over) { |ev| ev.prevent_default }
1153
+
1154
+ IMG(id: "drag1", src: "https://www.w3schools.com/html/img_logo.gif", draggable: "true", width: 336, height: 69)
1155
+ .on(:drag_start) do |ev|
1156
+ `#{ev.native_event}.native.dataTransfer.setData("text", #{ev.target}.native.id)`
1157
+ end
1158
+ ```
1159
+
1160
+ #### Selection events
1161
+
1162
+ Event names:
1163
+
1164
+ ```ruby
1165
+ onSelect
1166
+ ```
1167
+
1168
+
1169
+ #### Touch events
1170
+
1171
+ Event names:
1172
+
1173
+ ```ruby
1174
+ :touch_cancel, :touch_end, :touch_move, :touch_start
1175
+ ```
1176
+
1177
+ Available Methods:
1178
+
1179
+ ```ruby
1180
+ alt_key -> Boolean
1181
+ changed_touches -> (Native DOMTouchList)
1182
+ ctrl_key -> Boolean
1183
+ get_modifier_state(key) -> Boolean
1184
+ meta_key -> Boolean
1185
+ shift_key -> Boolean
1186
+ target_touches -> (Native DOMTouchList)
1187
+ touches -> (Native DomTouchList)
1188
+ ```
1189
+
1190
+ #### UI Events
1191
+
1192
+ Event names:
1193
+
1194
+ ```ruby
1195
+ :scroll
1196
+ ```
1197
+
1198
+ Available Methods:
1199
+
1200
+ ```ruby
1201
+ detail -> Integer
1202
+ view -> (Native DOMAbstractView)
1203
+ ```
1204
+
1205
+
1206
+ #### Wheel Events
1207
+
1208
+ Event names:
1209
+
1210
+ ```ruby
1211
+ wheel
1212
+ ```
1213
+
1214
+ Available Methods:
1215
+
1216
+ ```ruby
1217
+ delta_mode -> Integer
1218
+ delta_x -> Integer
1219
+ delta_y -> Integer
1220
+ delta_z -> Integer
1221
+ ```
1222
+
1223
+ #### Media Events
1224
+
1225
+ Event names:
1226
+
1227
+ ```ruby
1228
+ :abort, :can_play, :can_play_through, :duration_change,:emptied, :encrypted, :ended, :error, :loaded_data,
1229
+ :loaded_metadata, :load_start, :pause, :play, :playing, :progress, :rate_change, :seeked, :seeking, :stalled,
1230
+ :on_suspend, :time_update, :volume_change, :waiting
1231
+ ```
1232
+
1233
+ #### Image Events
1234
+
1235
+ Event names:
1236
+
1237
+ ```ruby
1238
+ :load, :error
1239
+ ```
1240
+
1241
+ ### Elements and Rendering
1242
+
1243
+ #### React.create_element
1244
+
1245
+ A React Element is a component class, a set of parameters, and a group of children. When an element is rendered the parameters and used to initialize a new instance of the component.
1246
+
1247
+ `React.create_element` creates a new element. It takes either the component class, or a string (representing a built in tag such as div, or span), the parameters (properties) to be passed to the element, and optionally a block that will be evaluated to build the enclosed children elements
1248
+
1249
+ ```ruby
1250
+ React.create_element("div", prop1: "foo", prop2: 12) { para { "hello" }; para { "goodby" } )
1251
+ # when rendered will generates <div prop1="foo" prop2="12"><p>hello</p><p>goodby</p></div>
1252
+ ```
1253
+
1254
+ **You almost never need to directly call `create_element`, the DSL, Rails, and jQuery interfaces take care of this for you.**
1255
+
1256
+ ```ruby
1257
+ # dsl - creates element and pushes it into the rendering buffer
1258
+ MyComponent(...params...) { ...optional children... }
1259
+
1260
+ # dsl - component will NOT be placed in the rendering buffer
1261
+ MyComponent(...params...) { ... }.as_node
1262
+
1263
+ # in a rails controller - renders component as the view
1264
+ render_component("MyComponent", ...params...)
1265
+
1266
+ # in a rails view helper - renders component into the view (like a partial)
1267
+ react_component("MyComponent", ...)
1268
+
1269
+ # from jQuery (Note Element is the Opal jQuery wrapper, not be confused with React::Element)
1270
+ Element['#container'].render { MyComponent(...params...) { ...optional children... } }
1271
+ ```
1272
+
1273
+ #### React.is\_valid\_element?
1274
+
1275
+ ```ruby
1276
+ is_valid_element?(object)
1277
+ ```
1278
+
1279
+ Verifies `object` is a valid react element. Note that `React::Element` wraps the React.js native class,
1280
+ `React.is_valid_element?` returns true for both classes unlike `object.is_a? React::Element`
1281
+
1282
+ #### React.render
1283
+
1284
+ ```ruby
1285
+ React.render(element, container) { puts "element rendered" }
1286
+ ```
1287
+
1288
+ Render an `element` into the DOM in the supplied `container` and return a [reference](/docs/more-about-refs.html) to the component.
1289
+
1290
+ The container can either be a DOM node or a jQuery selector (i.e. Element['#container']) in which case the first element is the container.
1291
+
1292
+ If the element was previously rendered into `container`, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React component.
1293
+
1294
+ If the optional block is provided, it will be executed after the component is rendered or updated.
1295
+
1296
+ > Note:
1297
+ >
1298
+ > `React.render()` controls the contents of the container node you pass in. Any existing DOM elements inside are replaced when first called. Later calls use React’s DOM diffing algorithm for efficient updates.
1299
+ >
1300
+ > `React.render()` does not modify the container node (only modifies the children of the container). In the future, it may be possible to insert a component to an existing DOM node without overwriting the existing children.
1301
+
1302
+
1303
+ #### React.unmount\_component\_at\_node
1304
+
1305
+ ```ruby
1306
+ React.unmount_component_at_node(container)
1307
+ ```
1308
+
1309
+ Remove a mounted React component from the DOM and clean up its event handlers and state. If no component was mounted in the container, calling this function does nothing. Returns `true` if a component was unmounted and `false` if there was no component to unmount.
1310
+
1311
+ #### React.render\_to\_string
1312
+
1313
+ ```ruby
1314
+ React.render_to_string(element)
1315
+ ```
1316
+
1317
+ Render an element to its initial HTML. This is should only be used on the server for prerendering content. React will return a string containing the HTML. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes.
1318
+
1319
+ If you call `React.render` on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
1320
+
1321
+ If you are using rails, then the prerendering functions are automatically performed. Otherwise you can use `render_to_string` to build your own prerendering system.
1322
+
1323
+
1324
+ #### React.render\_to\_static\_markup
1325
+
1326
+ ```ruby
1327
+ React.render_to_static_markup(element)
1328
+ ```
1329
+
1330
+ Similar to `render_to_string`, except this doesn't create extra DOM attributes such as `data-react-id`, that React uses internally. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save lots of bytes.
1331
+
1332
+ ### Using Javascript Components
1333
+
1334
+ While it is quite possible to develop large applications purely in Hyperloop Components with a ruby back end like rails, you may eventually find you want to use some pre-existing React Javascript library. Or you may be working with an existing React-JS application, and want to just start adding some Hyperloop Components.
1335
+
1336
+ Either way you are going to need to import Javascript components into the Hyperloop namespace. Hyperloop provides both manual and automatic mechanisms to do this depending on the level of control you need.
1337
+
1338
+ #### Importing Components
1339
+
1340
+ Lets say you have an existing React Component written in javascript that you would like to access from Hyperloop.
1341
+
1342
+ Here is a simple hello world component:
1343
+
1344
+ ```javascript
1345
+ window.SayHello = React.createClass({
1346
+ displayName: "SayHello",
1347
+ render: function render() {
1348
+ return React.createElement("div", null, "Hello ", this.props.name);
1349
+ }
1350
+ })
1351
+ ```
1352
+
1353
+ Assuming that this component is loaded some place in your assets, you can then access this from Hyperloop by creating a wrapper Component:
1354
+
1355
+ ```ruby
1356
+ class SayHello < Hyperloop::Component
1357
+ imports 'SayHello'
1358
+ end
1359
+
1360
+ class MyBigApp < Hyperloop::Component
1361
+ render(DIV) do
1362
+ # SayHello will now act like any other Hyperloop component
1363
+ SayHello name: 'Matz'
1364
+ end
1365
+ end
1366
+ ```
1367
+
1368
+ The `imports` directive takes a string (or a symbol) and will simply evaluate it and check to make sure that the value looks like a React component, and then set the underlying native component to point to the imported component.
1369
+
1370
+
1371
+ #### Importing Libraries
1372
+
1373
+ Many React components come in libraries. The `ReactBootstrap` library is one example. You can import the whole library at once using the `React::NativeLibrary` class. Assuming that you have initialized `ReactBootstrap` elsewhere, this is how you would bring it into Hyperloop.
1374
+
1375
+ ```ruby
1376
+ class RBS < React::NativeLibrary
1377
+ imports 'ReactBootstrap'
1378
+ end
1379
+ ```
1380
+
1381
+ We can now access our bootstrap components as components defined within the RBS scope:
1382
+
1383
+ ```ruby
1384
+ class Show < Hyperloop::Component
1385
+
1386
+ def say_hello(i)
1387
+ alert "Hello from number #{i}"
1388
+ end
1389
+
1390
+ render RBS::Navbar, bsStyle: :inverse do
1391
+ RBS::Nav() do
1392
+ RBS::NavbarBrand() do
1393
+ A(href: '#') { 'Hyperloop Showcase' }
1394
+ end
1395
+ RBS::NavDropdown(eventKey: 1, title: 'Things', id: :drop_down) do
1396
+ (1..5).each do |n|
1397
+ RBS::MenuItem(href: '#', key: n, eventKey: "1.#{n}") do
1398
+ "Number #{n}"
1399
+ end.on(:click) { say_hello(n) }
1400
+ end
1401
+ end
1402
+ end
1403
+ end
1404
+ end
1405
+ ```
1406
+
1407
+ Besides the `imports` directive, `React::NativeLibrary` also provides a rename directive that takes pairs in the form `oldname => newname`. For example:
1408
+
1409
+ ```ruby
1410
+ rename 'NavDropdown' => 'NavDD', 'Navbar' => 'NavBar', 'NavbarBrand' => 'NavBarBrand'
1411
+ ```
1412
+
1413
+ `React::NativeLibrary` will import components that may be deeply nested in the library. For example consider a component was defined as `MyLibrary.MySubLibrary.MyComponent`:
1414
+
1415
+ ```ruby
1416
+ class MyLib < React::NativeLibrary
1417
+ imports 'MyLibrary'
1418
+ end
1419
+
1420
+ class App < React::NativeLibrary
1421
+ render do
1422
+ ...
1423
+ MyLib::MySubLibrary::MyComponent ...
1424
+ ...
1425
+ end
1426
+ end
1427
+ ```
1428
+
1429
+ Note that the `rename` directive can be used to rename both components and sublibraries, giving you full control over the ruby names of the components and libraries.
1430
+
1431
+ #### Auto Import
1432
+
1433
+ If you use a lot of libraries and are using a Javascript tool chain with Webpack, having to import the libraries in both Hyperloop and Webpack is redundant and just hard work.
1434
+
1435
+ Instead you can opt-in for *auto importing* Javascript components into Hyperloop as you need them. Simply `require hyper-react/auto-import` immediately after you `require hyper-react`.
1436
+
1437
+ Now you do not have to use component `imports` directive or `React::NativeLibrary` unless you need to rename a component.
1438
+
1439
+ In Ruby all module and class names normally begin with an uppercase letter. However in Javascript this is not always the case, so the auto import will first try the Javascript name that exactly matches the Ruby name, and if that fails it will try the same name with the first character downcased. For example
1440
+
1441
+ `MyComponent` will first try `MyComponent` in the Javascript name space, then `myComponent`.
1442
+
1443
+ Likewise MyLib::MyComponent would match any of the following in the Javascript namespace: `MyLib.MyComponent`, `myLib.MyComponent`, `MyLib.myComponent`, `myLib.myComponent`
1444
+
1445
+ *How it works: The first time Ruby hits a native library or component name, the constant value will not be defined. This will trigger a lookup in the javascript name space for the matching component or library name. This will generate either a new subclass of Hyperloop::Component or React::NativeLibrary that imports the javascript object, and no further lookups will be needed.*
1446
+
1447
+ #### Including React Source
1448
+
1449
+ If you are in the business of importing components with a tool like Webpack, then you will need to let Webpack (or whatever dependency manager you are using) take care of including the React source code. Just make sure that you are *not* including it on the ruby side of things. Hyperloop is currently tested with React versions 13, 14, and 15, so its not sensitive to the version you use.
1450
+
1451
+ However it gets a little tricky if you are using the react-rails gem. Each version of this gem depends on a specific version of React, and so you will need to manually declare this dependency in your Javascript dependency manager. Consult this [table](https://github.com/reactjs/react-rails/blob/master/VERSIONS.md) to determine which version of React you need. For example assuming you are using `npm` to install modules and you are using version 1.7.2 of react-rails you would say something like this:
1452
+
1453
+ ```bash
1454
+ npm install react@15.0.2 react-dom@15.0.2 --save
1455
+ ```
1456
+
1457
+ #### Using Webpack
1458
+
1459
+ Just a word on Webpack: If you a Ruby developer who is new to using Javascript libraries then we recommend using Webpack to manage javascript component dependencies. Webpack is essentially bundler for Javascript. Please see our Tutorials section for more information.
1460
+
1461
+ There are also good tutorials on integrating Webpack with existing rails apps a google search away.
1462
+
1463
+ ### Server-side rendering (or Prerendering)
1464
+
1465
+ **Prerendering is controllable at three levels:**
1466
+
1467
+ + In the rails hyperloop initializer you can say:
1468
+
1469
+ ```ruby
1470
+ Hyperloop.configuration do |config|
1471
+ config.prerendering = :on # :off by default
1472
+ end
1473
+ ```
1474
+
1475
+ + In a route you can override the config setting by setting a default for hyperloop_prerendering:
1476
+
1477
+ ```ruby
1478
+ get '/some_page', to: 'hyperloop#some_page', defaults: {hyperloop_prerendering: :off} # or :on
1479
+ ```
1480
+
1481
+ This allows you to override the prerendering option for specific pages. For example the application may have prererendering off by default (via the config setting) but you can still turn it on for a specific page.
1482
+
1483
+ + You can override the route, and config setting using the hyperloop-prerendering query param:
1484
+
1485
+ ```html
1486
+ http://localhost:3000/my_hyper_app/some_page?hyperloop-prerendering=off
1487
+ ```
1488
+
1489
+ This is useful for development and testing
1490
+
1491
+ NOTE: in the route you say hyperloop_prererendering but in the query string its hyperloop-prerendering (underscore vs. dash). This is because of rails security protection when using defaults.
1492
+
1493
+ ### Further Reading
1494
+
1495
+ **Note:** The Hyperloop gems have recently been renamed. The links below will take you to the correct Github projects but you might find the name of the project does not quite match the name of the gem on this page. Hyperloop Components were previously known as HyperReact or Reactrb.
1496
+
1497
+ #### Other Hyperloop tutorials and examples
1498
+ + [Hyperloop Tutorials](http://ruby-hyperloop.io/tutorials/)
1499
+
1500
+ #### React under the covers
1501
+
1502
+ Hyperloop Components and friends are in most cases simple DSL Ruby wrappers to the underlying native JavaScript libraries and React Components. It is really important to have a solid grip on how these technologies work to complement your understanding of Hyperloop. Most searches for help on Google will take you to examples written in JSX or ES6 JavaScript but you will learn over time to translate this to Hyperloop equivalents. To make headway with Hyperloop you do need a solid understanding of the underlying philosophy of React and its component based architecture. The 'Thinking in React' tutorial below is an excellent place to start.
1503
+
1504
+ + [Thinking in React](https://facebook.github.io/react/docs/thinking-in-react.html)
1505
+ + [React](https://facebook.github.io/react/docs/getting-started.html)
1506
+ + [React Router](https://github.com/reactjs/react-router)
1507
+
1508
+
1509
+ #### Opal under the covers
1510
+
1511
+ Hyperloop Components are a DSL wrapper of React which uses Opal to compile Ruby code to ES5 native JavaScript. If you have not used Opal before then you should at a minimum read the excellent guides as they will teach you enough Opal to get you started with Hyperloop.
1512
+
1513
+ + [Opal](http://opalrb.org/)
1514
+ + [Opal Guides](http://opalrb.org/docs/guides/v0.9.2/index.html)
1515
+ + [To see the full power of Opal in action watch this video](https://www.youtube.com/watch?v=vhIrrlcWphU)