hyper-component 0.12.3 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.codeclimate.yml +27 -0
- data/.gitignore +42 -41
- data/.travis.yml +29 -0
- data/CHANGELOG.md +143 -0
- data/DOCS.md +1515 -0
- data/Gemfile +5 -2
- data/Gemfile.lock +244 -193
- data/LICENSE +5 -7
- data/README.md +49 -0
- data/Rakefile +40 -0
- data/hyper-component.gemspec +41 -31
- data/lib/hyper-component.rb +44 -9
- data/lib/rails-helpers/top_level_rails_component.rb +79 -0
- data/lib/react/api.rb +270 -0
- data/lib/react/callbacks.rb +42 -0
- data/lib/react/children.rb +38 -0
- data/lib/react/component.rb +189 -0
- data/lib/react/component/api.rb +70 -0
- data/lib/react/component/base.rb +13 -0
- data/lib/react/component/class_methods.rb +175 -0
- data/lib/react/component/dsl_instance_methods.rb +23 -0
- data/lib/react/component/params.rb +6 -0
- data/lib/react/component/props_wrapper.rb +90 -0
- data/lib/react/component/should_component_update.rb +99 -0
- data/lib/react/component/tags.rb +116 -0
- data/lib/react/config.rb +5 -0
- data/lib/react/element.rb +159 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/opal-jquery/element.rb +37 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/native_library.rb +87 -0
- data/lib/react/object.rb +15 -0
- data/lib/react/react-source-server.rb +3 -0
- data/lib/react/react-source.rb +17 -0
- data/lib/react/ref_callback.rb +31 -0
- data/lib/react/rendering_context.rb +149 -0
- data/lib/react/server.rb +19 -0
- data/lib/react/state_wrapper.rb +23 -0
- data/lib/react/test.rb +16 -0
- data/lib/react/test/dsl.rb +17 -0
- data/lib/react/test/matchers/render_html_matcher.rb +56 -0
- data/lib/react/test/rspec.rb +15 -0
- data/lib/react/test/session.rb +37 -0
- data/lib/react/test/utils.rb +71 -0
- data/lib/react/to_key.rb +26 -0
- data/lib/react/top_level.rb +110 -0
- data/lib/react/top_level_render.rb +28 -0
- data/lib/react/validator.rb +132 -0
- data/lib/reactive-ruby/component_loader.rb +43 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +233 -0
- data/lib/reactive-ruby/rails.rb +8 -0
- data/lib/reactive-ruby/rails/component_mount.rb +48 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
- data/lib/reactive-ruby/rails/railtie.rb +20 -0
- data/lib/reactive-ruby/serializers.rb +23 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +46 -0
- data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
- data/lib/{hyperloop/component → reactive-ruby}/version.rb +1 -1
- data/lib/reactrb/auto-import.rb +27 -0
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/misc/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +121 -0
- data/misc/how-component-name-lookup-works.md +145 -0
- data/misc/hyperloop-logo-small-pink.png +0 -0
- data/misc/logo1.png +0 -0
- data/misc/logo2.png +0 -0
- data/misc/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- metadata +260 -37
- data/CODE_OF_CONDUCT.md +0 -49
data/LICENSE
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
|
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
|
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
|
21
|
-
SOFTWARE.
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -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
|
+
[](https://travis-ci.org/ruby-hyperloop/hyper-react)
|
24
|
+
[](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
|
data/hyper-component.gemspec
CHANGED
@@ -1,41 +1,51 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
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 =
|
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.
|
13
|
-
spec.
|
14
|
-
spec.
|
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
|
17
|
-
spec.
|
18
|
-
spec.
|
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-
|
22
|
-
spec.add_dependency 'hyperloop-config',
|
23
|
-
spec.add_dependency '
|
24
|
-
spec.add_dependency '
|
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 '
|
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 '
|
29
|
-
spec.add_development_dependency '
|
30
|
-
spec.add_development_dependency '
|
31
|
-
spec.add_development_dependency 'rails'
|
32
|
-
spec.add_development_dependency 'rspec'
|
33
|
-
spec.add_development_dependency '
|
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
|
data/lib/hyper-component.rb
CHANGED
@@ -1,18 +1,53 @@
|
|
1
|
-
require "hyperloop/component/version"
|
2
1
|
require 'hyperloop-config'
|
3
|
-
Hyperloop.import 'hyper-
|
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 '
|
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
|
-
|
15
|
-
require '
|
16
|
-
require 'opal-
|
17
|
-
|
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
|
data/lib/react/api.rb
ADDED
@@ -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
|