hyper-component 0.12.3 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +42 -41
  4. data/.travis.yml +29 -0
  5. data/CHANGELOG.md +143 -0
  6. data/DOCS.md +1515 -0
  7. data/Gemfile +5 -2
  8. data/Gemfile.lock +244 -193
  9. data/LICENSE +5 -7
  10. data/README.md +49 -0
  11. data/Rakefile +40 -0
  12. data/hyper-component.gemspec +41 -31
  13. data/lib/hyper-component.rb +44 -9
  14. data/lib/rails-helpers/top_level_rails_component.rb +79 -0
  15. data/lib/react/api.rb +270 -0
  16. data/lib/react/callbacks.rb +42 -0
  17. data/lib/react/children.rb +38 -0
  18. data/lib/react/component.rb +189 -0
  19. data/lib/react/component/api.rb +70 -0
  20. data/lib/react/component/base.rb +13 -0
  21. data/lib/react/component/class_methods.rb +175 -0
  22. data/lib/react/component/dsl_instance_methods.rb +23 -0
  23. data/lib/react/component/params.rb +6 -0
  24. data/lib/react/component/props_wrapper.rb +90 -0
  25. data/lib/react/component/should_component_update.rb +99 -0
  26. data/lib/react/component/tags.rb +116 -0
  27. data/lib/react/config.rb +5 -0
  28. data/lib/react/element.rb +159 -0
  29. data/lib/react/event.rb +76 -0
  30. data/lib/react/ext/hash.rb +9 -0
  31. data/lib/react/ext/opal-jquery/element.rb +37 -0
  32. data/lib/react/ext/string.rb +8 -0
  33. data/lib/react/native_library.rb +87 -0
  34. data/lib/react/object.rb +15 -0
  35. data/lib/react/react-source-server.rb +3 -0
  36. data/lib/react/react-source.rb +17 -0
  37. data/lib/react/ref_callback.rb +31 -0
  38. data/lib/react/rendering_context.rb +149 -0
  39. data/lib/react/server.rb +19 -0
  40. data/lib/react/state_wrapper.rb +23 -0
  41. data/lib/react/test.rb +16 -0
  42. data/lib/react/test/dsl.rb +17 -0
  43. data/lib/react/test/matchers/render_html_matcher.rb +56 -0
  44. data/lib/react/test/rspec.rb +15 -0
  45. data/lib/react/test/session.rb +37 -0
  46. data/lib/react/test/utils.rb +71 -0
  47. data/lib/react/to_key.rb +26 -0
  48. data/lib/react/top_level.rb +110 -0
  49. data/lib/react/top_level_render.rb +28 -0
  50. data/lib/react/validator.rb +132 -0
  51. data/lib/reactive-ruby/component_loader.rb +43 -0
  52. data/lib/reactive-ruby/isomorphic_helpers.rb +233 -0
  53. data/lib/reactive-ruby/rails.rb +8 -0
  54. data/lib/reactive-ruby/rails/component_mount.rb +48 -0
  55. data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
  56. data/lib/reactive-ruby/rails/railtie.rb +20 -0
  57. data/lib/reactive-ruby/serializers.rb +23 -0
  58. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +46 -0
  59. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
  60. data/lib/{hyperloop/component → reactive-ruby}/version.rb +1 -1
  61. data/lib/reactrb/auto-import.rb +27 -0
  62. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
  63. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
  64. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  65. data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  66. data/misc/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  67. data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  68. data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  69. data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  70. data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  71. data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +121 -0
  72. data/misc/how-component-name-lookup-works.md +145 -0
  73. data/misc/hyperloop-logo-small-pink.png +0 -0
  74. data/misc/logo1.png +0 -0
  75. data/misc/logo2.png +0 -0
  76. data/misc/logo3.png +0 -0
  77. data/path_release_steps.md +9 -0
  78. metadata +260 -37
  79. data/CODE_OF_CONDUCT.md +0 -49
data/LICENSE CHANGED
@@ -1,6 +1,4 @@
1
- MIT License
2
-
3
- Copyright (c) 2017 Ruby Hyperloop
1
+ Copyright (c) 2015 Yi-Cheng Chang (http://github.com/zetachang)
4
2
 
5
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
4
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +7,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
7
  copies of the Software, and to permit persons to whom the Software is
10
8
  furnished to do so, subject to the following conditions:
11
9
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
14
12
 
15
13
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
14
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
15
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
16
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
17
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ <div class="githubhyperloopheader">
2
+
3
+ <p align="center">
4
+
5
+ <a href="http://ruby-hyperloop.org/" alt="Hyperloop" title="Hyperloop">
6
+ <img width="350px" src="http://ruby-hyperloop.org/images/hyperloop-github-logo.png">
7
+ </a>
8
+
9
+ </p>
10
+
11
+ <h2 align="center">The Complete Isomorphic Ruby Framework</h2>
12
+
13
+ <br>
14
+
15
+ <a href="http://ruby-hyperloop.org/" alt="Hyperloop" title="Hyperloop">
16
+ <img src="http://ruby-hyperloop.org/images/githubhyperloopbadge.png">
17
+ </a>
18
+
19
+ <a href="https://gitter.im/ruby-hyperloop/chat" alt="Gitter chat" title="Gitter chat">
20
+ <img src="http://ruby-hyperloop.org/images/githubgitterbadge.png">
21
+ </a>
22
+
23
+ [![Build Status](https://travis-ci.org/ruby-hyperloop/hyper-react.svg?branch=master)](https://travis-ci.org/ruby-hyperloop/hyper-react)
24
+ [![Gem Version](https://badge.fury.io/rb/hyper-react.svg)](https://badge.fury.io/rb/hyper-react)
25
+
26
+ <p align="center">
27
+ <img src="http://ruby-hyperloop.org/images/HyperComponents.png" width="100" alt="Hyper-components">
28
+ </p>
29
+
30
+ </div>
31
+
32
+ ## Hyper-React GEM is part of Hyperloop GEMS family
33
+
34
+ Hyper-react GEM comes with the Hyperloop GEM.
35
+
36
+ But if you want to install it separately, please install the [Hyper-component GEM](https://github.com/ruby-hyperloop/hyper-component).
37
+
38
+ ## Community
39
+
40
+ #### Getting Help
41
+ Please **do not post** usage questions to GitHub Issues. For these types of questions use our [Gitter chatroom](https://gitter.im/ruby-hyperloop/chat) or [StackOverflow](http://stackoverflow.com/questions/tagged/hyperloop).
42
+
43
+ #### Submitting Bugs and Enhancements
44
+ [GitHub Issues](https://github.com/ruby-hyperloop/hyperloop/issues) is for suggesting enhancements and reporting bugs. Before submiting a bug make sure you do the following:
45
+ * Check out our [contributing guide](https://github.com/ruby-hyperloop/hyperloop/blob/master/CONTRIBUTING.md) for info on our release cycle.
46
+
47
+ ## License
48
+
49
+ Hyperloop is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,2 +1,42 @@
1
+ # require 'bundler'
2
+ # Bundler.require
3
+ # Bundler::GemHelper.install_tasks
4
+ #
5
+ # # Store the BUNDLE_GEMFILE env, since rake or rspec seems to clean it
6
+ # # while invoking task.
7
+ # ENV['REAL_BUNDLE_GEMFILE'] = ENV['BUNDLE_GEMFILE']
8
+ #
9
+ # require 'rspec/core/rake_task'
10
+ # require 'opal/rspec/rake_task'
11
+ #
12
+ # RSpec::Core::RakeTask.new('ruby:rspec')
13
+ #
14
+ # task :test do
15
+ # Rake::Task['ruby:rspec'].invoke
16
+ # end
17
+ #
18
+ # require 'generators/reactive_ruby/test_app/test_app_generator'
19
+ # desc "Generates a dummy app for testing"
20
+ # task :test_app do
21
+ # ReactiveRuby::TestAppGenerator.start
22
+ # puts "Setting up test app database..."
23
+ # system("bundle exec rake db:drop db:create db:migrate > #{File::NULL}")
24
+ # end
25
+ #
26
+ # task :test_prepare do
27
+ # system("./dciy_prepare.sh")
28
+ # end
29
+ #
30
+ # task default: [ :test ]
31
+
1
32
  require "bundler/gem_tasks"
33
+ require "rspec/core/rake_task"
34
+
35
+ RSpec::Core::RakeTask.new(:spec)
36
+
37
+ namespace :spec do
38
+ task :prepare do
39
+ end
40
+ end
41
+
2
42
  task :default => :spec
@@ -1,41 +1,51 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'hyperloop/component/version'
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib/', __FILE__)
3
+ require 'reactive-ruby/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "hyper-component"
6
+ spec.name = 'hyper-component'
8
7
  spec.version = Hyperloop::Component::VERSION
9
- spec.authors = ["catmando"]
10
- spec.email = ["mitch@catprint.com"]
11
8
 
12
- spec.summary = %q{The Hyperloop rendering engine. Write React components in Ruby.}
13
- spec.homepage = "http://ruby-hyperloop.io"
14
- spec.license = "MIT"
9
+ spec.authors = ['David Chang', 'Adam Jahn', 'Mitch VanDuyn', 'Jan Biedermann', 'Adam Creekroad']
10
+ spec.email = ['mitch@catprint.com']
11
+ spec.homepage = 'http://ruby-hyperloop.org'
12
+ spec.summary = 'Opal Ruby wrapper of React.js library.'
13
+ spec.license = 'MIT'
14
+ spec.description = 'Write React UI components in pure Ruby.'
15
+ # spec.metadata = {
16
+ # "homepage_uri" => 'http://ruby-hyperloop.org',
17
+ # "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component'
18
+ # }
15
19
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
20
+ spec.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(gemfiles|spec)/}) }
21
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ spec.require_paths = ['lib']
20
23
 
21
- spec.add_dependency 'hyper-react', '>= 0.12.3'
22
- spec.add_dependency 'hyperloop-config', '>= 0.9.2'
23
- spec.add_dependency 'react-rails', '< 1.10.0'
24
- spec.add_dependency 'opal-rails', '~> 0.9.0'
24
+ spec.add_dependency 'hyper-store', Hyperloop::Component::VERSION
25
+ spec.add_dependency 'hyperloop-config', Hyperloop::Component::VERSION
26
+ spec.add_dependency 'libv8', '~> 6.3.0' # see https://github.com/discourse/mini_racer/issues/92
27
+ spec.add_dependency 'mini_racer', '~> 0.1.15'
28
+ spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
29
+ spec.add_dependency 'opal-activesupport', '~> 0.3.1'
30
+ spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
25
31
 
26
- spec.add_development_dependency 'hyper-spec'
32
+ spec.add_development_dependency 'bundler', '~> 1.16.0'
33
+ spec.add_development_dependency 'chromedriver-helper'
34
+ spec.add_development_dependency 'hyper-spec', Hyperloop::Component::VERSION
35
+ spec.add_development_dependency 'jquery-rails'
27
36
  spec.add_development_dependency 'listen'
28
- spec.add_development_dependency 'opal'
29
- spec.add_development_dependency 'opal-browser'
30
- spec.add_development_dependency 'pry-byebug'
31
- spec.add_development_dependency 'rails'
32
- spec.add_development_dependency 'rspec'
33
- spec.add_development_dependency 'rspec-steps'
37
+ spec.add_development_dependency 'mime-types'
38
+ spec.add_development_dependency 'nokogiri'
39
+ spec.add_development_dependency 'opal-jquery'
40
+ spec.add_development_dependency 'opal-rails', '~> 0.9.4'
41
+ spec.add_development_dependency 'opal-rspec'
42
+ spec.add_development_dependency 'pry'
43
+ spec.add_development_dependency 'puma'
44
+ spec.add_development_dependency 'rails', '>= 4.0.0'
45
+ spec.add_development_dependency 'rails-controller-testing'
46
+ spec.add_development_dependency 'rake'
47
+ spec.add_development_dependency 'rspec-rails'
48
+ spec.add_development_dependency 'rubocop', '~> 0.51.0'
34
49
  spec.add_development_dependency 'sqlite3'
35
-
36
- # Keep linter-rubocop happy
37
- spec.add_development_dependency 'rubocop'
38
-
39
- spec.add_development_dependency "bundler", "~> 1.12"
40
- spec.add_development_dependency "rake", "~> 10.0"
50
+ spec.add_development_dependency 'timecop', '~> 0.8.1'
41
51
  end
@@ -1,18 +1,53 @@
1
- require "hyperloop/component/version"
2
1
  require 'hyperloop-config'
3
- Hyperloop.import 'hyper-component'
2
+ Hyperloop.import 'hyper-store'
3
+ Hyperloop.js_import 'react/react-source-browser', client_only: true, defines: ['ReactDOM', 'React']
4
+ Hyperloop.js_import 'react/react-source-server', server_only: true, defines: 'React'
5
+ Hyperloop.import 'browser/delay', client_only: true
6
+ Hyperloop.js_import 'react_ujs', defines: 'ReactRailsUJS'
7
+
4
8
  if RUBY_ENGINE == 'opal'
5
9
  module Hyperloop
6
10
  class Component
7
- # defining this before requring hyper-react will turn
8
- # off the hyper-react deprecation notice
9
11
  end
10
12
  end
11
- require 'hyper-react'
13
+ require 'native'
14
+ require 'react/observable'
15
+ require 'react/validator'
16
+ require 'react/element'
17
+ require 'react/api'
18
+ require 'react/component'
19
+ require 'react/component/dsl_instance_methods'
20
+ require 'react/component/should_component_update'
21
+ require 'react/component/tags'
22
+ require 'react/component/base'
23
+ require 'react/event'
24
+ require 'react/rendering_context'
25
+ require 'react/state'
26
+ require 'react/object'
27
+ require 'react/to_key'
28
+ require 'react/ext/opal-jquery/element'
29
+ require 'reactive-ruby/isomorphic_helpers'
30
+ require 'react/top_level'
31
+ require 'react/top_level_render'
32
+ require 'rails-helpers/top_level_rails_component'
33
+ require 'reactive-ruby/version'
34
+ module Hyperloop
35
+ class Component
36
+ def self.inherited(child)
37
+ child.include(Mixin)
38
+ end
39
+ end
40
+ end
12
41
  else
13
42
  require 'opal'
14
- require 'hyper-react'
15
- require 'react-rails'
16
- require 'opal-rails'
17
- Opal.append_path(File.expand_path('../', __FILE__).untaint)
43
+
44
+ require 'hyper-store'
45
+ require 'opal-activesupport'
46
+ require 'reactive-ruby/version'
47
+ require 'reactive-ruby/rails' if defined?(Rails)
48
+ require 'reactive-ruby/isomorphic_helpers'
49
+ require 'reactive-ruby/serializers'
50
+
51
+ Opal.append_path File.expand_path('../', __FILE__).untaint
52
+ require 'react/react-source'
18
53
  end
@@ -0,0 +1,79 @@
1
+ module React
2
+ class TopLevelRailsComponent
3
+ include Hyperloop::Component::Mixin
4
+
5
+ def self.search_path
6
+ @search_path ||= [Object]
7
+ end
8
+
9
+ export_component
10
+
11
+ param :component_name
12
+ param :controller
13
+ param :render_params
14
+
15
+ backtrace :off
16
+
17
+ def render
18
+ top_level_render
19
+ end
20
+
21
+ def top_level_render
22
+ paths_searched = []
23
+ component = nil
24
+ if params.component_name.start_with?('::')
25
+ # if absolute path of component is given, look it up and fail if not found
26
+ paths_searched << params.component_name
27
+ component = begin
28
+ Object.const_get(params.component_name)
29
+ rescue NameError
30
+ nil
31
+ end
32
+ else
33
+ # if relative path is given, look it up like this
34
+ # 1) we check each path + controller-name + component-name
35
+ # 2) if we can't find it there we check each path + component-name
36
+ # if we can't find it we just try const_get
37
+ # so (assuming controller name is Home)
38
+ # ::Foo::Bar will only resolve to some component named ::Foo::Bar
39
+ # but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar
40
+ self.class.search_path.each do |scope|
41
+ paths_searched << "#{scope.name}::#{params.controller}::#{params.component_name}"
42
+ component = begin
43
+ scope.const_get(params.controller, false).const_get(params.component_name, false)
44
+ rescue NameError
45
+ nil
46
+ end
47
+ break if component != nil
48
+ end
49
+ unless component
50
+ self.class.search_path.each do |scope|
51
+ paths_searched << "#{scope.name}::#{params.component_name}"
52
+ component = begin
53
+ scope.const_get(params.component_name, false)
54
+ rescue NameError
55
+ nil
56
+ end
57
+ break if component != nil
58
+ end
59
+ end
60
+ end
61
+ return React::RenderingContext.render(component, params.render_params) if component && component.method_defined?(:render)
62
+ raise "Could not find component class '#{params.component_name}' for params.controller '#{params.controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
63
+ end
64
+ end
65
+ end
66
+
67
+ class Module
68
+ def add_to_react_search_path(replace_search_path = nil)
69
+ if replace_search_path
70
+ React::TopLevelRailsComponent.search_path = [self]
71
+ elsif !React::TopLevelRailsComponent.search_path.include? self
72
+ React::TopLevelRailsComponent.search_path << self
73
+ end
74
+ end
75
+ end
76
+
77
+ module Components
78
+ add_to_react_search_path
79
+ end
@@ -0,0 +1,270 @@
1
+ require 'react/native_library'
2
+
3
+ module React
4
+ # Provides the internal mechanisms to interface between reactrb and native components
5
+ # the code will attempt to create a js component wrapper on any rb class that has a
6
+ # render (or possibly _render_wrapper) method. The mapping between rb and js components
7
+ # is kept in the @@component_classes hash.
8
+
9
+ # Also provides the mechanism to build react elements
10
+
11
+ # TOOO - the code to deal with components should be moved to a module that will be included
12
+ # in a class which will then create the JS component for that class. That module will then
13
+ # be included in React::Component, but can be used by any class wanting to become a react
14
+ # component (but without other DSL characteristics.)
15
+ class API
16
+ @@component_classes = {}
17
+
18
+ def self.import_native_component(opal_class, native_class)
19
+ opal_class.instance_variable_set("@native_import", true)
20
+ @@component_classes[opal_class] = native_class
21
+ end
22
+
23
+ def self.eval_native_react_component(name)
24
+ component = `eval(name)`
25
+ raise "#{name} is not defined" if `#{component} === undefined`
26
+ is_component_class = `#{component}.prototype !== undefined` &&
27
+ (`!!#{component}.prototype.isReactComponent` ||
28
+ `!!#{component}.prototype.render`)
29
+ is_functional_component = `typeof #{component} === "function"`
30
+ unless is_component_class || is_functional_component
31
+ raise 'does not appear to be a native react component'
32
+ end
33
+ component
34
+ end
35
+
36
+ def self.native_react_component?(name = nil)
37
+ return false unless name
38
+ eval_native_react_component(name)
39
+ rescue
40
+ nil
41
+ end
42
+
43
+ def self.add_after_error_hook(klass)
44
+ add_after_error_hook_to_native(@@component_classes[klass])
45
+ end
46
+
47
+ def self.add_after_error_hook_to_native(native_comp)
48
+ return unless native_comp
49
+ %x{
50
+ native_comp.prototype.componentDidCatch = function(error, info) {
51
+ this.__opalInstanceSyncSetState = false;
52
+ this.__opalInstance.$component_did_catch(error, Opal.Hash.$new(info));
53
+ }
54
+ }
55
+ end
56
+
57
+ def self.create_native_react_class(type)
58
+ raise "Provided class should define `render` method" if !(type.method_defined? :render)
59
+ render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
60
+ # this was hashing type.to_s, not sure why but .to_s does not work as it Foo::Bar::View.to_s just returns "View"
61
+
62
+ @@component_classes[type] ||= begin
63
+ comp = %x{
64
+ class extends React.Component {
65
+ constructor(props) {
66
+ super(props);
67
+ this.mixins = #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`};
68
+ this.statics = #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`};
69
+ this.state = {};
70
+ this.__opalInstanceInitializedState = false;
71
+ this.__opalInstanceSyncSetState = true;
72
+ this.__opalInstance = #{type.new(`this`)};
73
+ this.__opalInstanceInitializedState = true;
74
+ this.__opalInstanceSyncSetState = false;
75
+ this.__name = #{type.name};
76
+ }
77
+ static get displayName() {
78
+ if (typeof this.__name != "undefined") {
79
+ return this.__name;
80
+ } else {
81
+ return #{type.name};
82
+ }
83
+ }
84
+ static set displayName(name) {
85
+ this.__name = name;
86
+ }
87
+ static get defaultProps() {
88
+ return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
89
+ }
90
+ static get propTypes() {
91
+ return #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`};
92
+ }
93
+ componentWillMount() {
94
+ if (#{type.method_defined? :component_will_mount}) {
95
+ this.__opalInstanceSyncSetState = true;
96
+ this.__opalInstance.$component_will_mount();
97
+ this.__opalInstanceSyncSetState = false;
98
+ }
99
+ }
100
+ componentDidMount() {
101
+ this.__opalInstance.is_mounted = true
102
+ if (#{type.method_defined? :component_did_mount}) {
103
+ this.__opalInstanceSyncSetState = false;
104
+ this.__opalInstance.$component_did_mount();
105
+ }
106
+ }
107
+ componentWillReceiveProps(next_props) {
108
+ if (#{type.method_defined? :component_will_receive_props}) {
109
+ this.__opalInstanceSyncSetState = true;
110
+ this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props));
111
+ this.__opalInstanceSyncSetState = false;
112
+ }
113
+ }
114
+ shouldComponentUpdate(next_props, next_state) {
115
+ if (#{type.method_defined? :should_component_update?}) {
116
+ this.__opalInstanceSyncSetState = false;
117
+ return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
118
+ } else { return true; }
119
+ }
120
+ componentWillUpdate(next_props, next_state) {
121
+ if (#{type.method_defined? :component_will_update}) {
122
+ this.__opalInstanceSyncSetState = false;
123
+ this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
124
+ }
125
+ }
126
+ componentDidUpdate(prev_props, prev_state) {
127
+ if (#{type.method_defined? :component_did_update}) {
128
+ this.__opalInstanceSyncSetState = false;
129
+ this.__opalInstance.$component_did_update(Opal.Hash.$new(prev_props), Opal.Hash.$new(prev_state));
130
+ }
131
+ }
132
+ componentWillUnmount() {
133
+ if (#{type.method_defined? :component_will_unmount}) {
134
+ this.__opalInstanceSyncSetState = false;
135
+ this.__opalInstance.$component_will_unmount();
136
+ }
137
+ this.__opalInstance.is_mounted = false;
138
+ }
139
+
140
+ render() {
141
+ this.__opalInstanceSyncSetState = false;
142
+ return this.__opalInstance.$send(render_fn).$to_n();
143
+ }
144
+ }
145
+ }
146
+ # check to see if there is an after_error callback. If there is add a
147
+ # componentDidCatch handler. Because legacy behavior is to allow any object
148
+ # that responds to render to act as a component we have to make sure that
149
+ # we have a callbacks_for method. This all becomes much easier once issue
150
+ # #270 is resolved.
151
+ if type.respond_to?(:callbacks_for) && type.callbacks_for(:after_error) != []
152
+ add_after_error_hook_to_native comp
153
+ end
154
+ comp
155
+ end
156
+ end
157
+
158
+ def self.create_element(type, *args, &block)
159
+ params = []
160
+
161
+ # Component Spec, Normal DOM, String or Native Component
162
+ ncc = @@component_classes[type]
163
+ if ncc
164
+ params << ncc
165
+ elsif type.is_a?(Class)
166
+ params << create_native_react_class(type)
167
+ elsif block_given? || React::Component::Tags::HTML_TAGS.include?(type)
168
+ params << type
169
+ elsif type.is_a?(String)
170
+ return React::Element.new(type)
171
+ else
172
+ raise "#{type} not implemented"
173
+ end
174
+
175
+ # Convert Passed in properties
176
+ properties = convert_props(args)
177
+ params << properties.shallow_to_n
178
+
179
+ # Children Nodes
180
+ if block_given?
181
+ a = [yield].flatten
182
+ %x{
183
+ for(var i=0, l=a.length; i<l; i++) {
184
+ params.push(a[i].$to_n());
185
+ }
186
+ }
187
+ end
188
+ React::Element.new(`React.createElement.apply(null, #{params})`, type, properties, block)
189
+ end
190
+
191
+ def self.clear_component_class_cache
192
+ @@component_classes = {}
193
+ end
194
+
195
+ def self.convert_props(args)
196
+ # merge args together into a single properties hash
197
+ properties = {}
198
+ args.each do |arg|
199
+ if arg.is_a? String
200
+ properties[arg] = true
201
+ elsif arg.is_a? Hash
202
+ arg.each do |key, value|
203
+ if ['class', 'className', 'class_name'].include? key
204
+ next unless value
205
+
206
+ if value.is_a?(String)
207
+ value = value.split(' ')
208
+ elsif !value.is_a?(Array)
209
+ raise "The class param must be a string or array of strings"
210
+ end
211
+
212
+ properties['className'] = [*properties['className'], *value]
213
+ elsif key == 'style'
214
+ next unless value
215
+
216
+ if !value.is_a?(Hash)
217
+ raise "The style param must be a Hash"
218
+ end
219
+
220
+ properties['style'] = (properties['style'] || {}).merge(value)
221
+ elsif React::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
222
+ properties[key] = (properties[key] || {}).merge(value)
223
+ else
224
+ properties[key] = value
225
+ end
226
+ end
227
+ end
228
+ end
229
+ # process properties according to react rules
230
+ props = {}
231
+ properties.each do |key, value|
232
+ if ["style", "dangerously_set_inner_HTML"].include? key
233
+ props[lower_camelize(key)] = value.to_n
234
+
235
+ elsif key == "className"
236
+ props[key] = value.join(' ')
237
+
238
+ elsif key == "key"
239
+ props["key"] = value.to_key
240
+
241
+ elsif key == 'ref' && value.is_a?(Proc)
242
+ props[key] = %x{
243
+ function(dom_node){
244
+ if (dom_node !== null && dom_node.__opalInstance !== undefined && dom_node.__opalInstance !== null) {
245
+ #{ value.call(`dom_node.__opalInstance`) };
246
+ } else if(dom_node !== null && ReactDOM.findDOMNode !== undefined && dom_node.nodeType === undefined) {
247
+ #{ value.call(`ReactDOM.findDOMNode(dom_node)`) };
248
+ } else {
249
+ #{ value.call(`dom_node`) };
250
+ }
251
+ }
252
+ }
253
+ elsif React::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
254
+ value.each { |k, v| props["#{key}-#{k.gsub(/__|_/, '__' => '_', '_' => '-')}"] = v.to_n }
255
+ else
256
+ props[React.html_attr?(lower_camelize(key)) ? lower_camelize(key) : key] = value
257
+ end
258
+ end
259
+ props
260
+ end
261
+
262
+ private
263
+
264
+ def self.lower_camelize(snake_cased_word)
265
+ words = snake_cased_word.split('_')
266
+ result = [words.first]
267
+ result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] }).join('')
268
+ end
269
+ end
270
+ end