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.
- 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
|
+
[![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
|
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
|