reactrb 0.7.42
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 +7 -0
- data/.codeclimate.yml +6 -0
- data/.gitignore +33 -0
- data/.travis.yml +9 -0
- data/Gemfile +2 -0
- data/LICENSE +19 -0
- data/README.md +117 -0
- data/Rakefile +28 -0
- data/config.ru +16 -0
- data/example/examples/Gemfile +7 -0
- data/example/examples/app/basics.js.rb +42 -0
- data/example/examples/app/items.rb +11 -0
- data/example/examples/app/jquery.js +5 -0
- data/example/examples/app/nodes.rb +61 -0
- data/example/examples/app/react-router.js +6 -0
- data/example/examples/app/react_api_demo.rb +29 -0
- data/example/examples/app/rerendering.rb +72 -0
- data/example/examples/app/reuse.rb +59 -0
- data/example/examples/app/show.rb +52 -0
- data/example/examples/config.ru +38 -0
- data/example/rails-tutorial/.gitignore +17 -0
- data/example/rails-tutorial/Gemfile +51 -0
- data/example/rails-tutorial/README.rdoc +28 -0
- data/example/rails-tutorial/Rakefile +6 -0
- data/example/rails-tutorial/app/assets/images/.keep +0 -0
- data/example/rails-tutorial/app/assets/javascripts/application.rb +15 -0
- data/example/rails-tutorial/app/assets/stylesheets/application.css +15 -0
- data/example/rails-tutorial/app/controllers/application_controller.rb +6 -0
- data/example/rails-tutorial/app/controllers/concerns/.keep +0 -0
- data/example/rails-tutorial/app/controllers/home_controller.rb +6 -0
- data/example/rails-tutorial/app/helpers/application_helper.rb +2 -0
- data/example/rails-tutorial/app/mailers/.keep +0 -0
- data/example/rails-tutorial/app/models/.keep +0 -0
- data/example/rails-tutorial/app/models/concerns/.keep +0 -0
- data/example/rails-tutorial/app/views/components.rb +3 -0
- data/example/rails-tutorial/app/views/components/home/show.rb +47 -0
- data/example/rails-tutorial/app/views/layouts/application.html.erb +14 -0
- data/example/rails-tutorial/bin/bundle +3 -0
- data/example/rails-tutorial/bin/rails +8 -0
- data/example/rails-tutorial/bin/rake +8 -0
- data/example/rails-tutorial/bin/setup +29 -0
- data/example/rails-tutorial/bin/spring +15 -0
- data/example/rails-tutorial/config.ru +4 -0
- data/example/rails-tutorial/config/application.rb +26 -0
- data/example/rails-tutorial/config/boot.rb +3 -0
- data/example/rails-tutorial/config/database.yml +25 -0
- data/example/rails-tutorial/config/environment.rb +5 -0
- data/example/rails-tutorial/config/environments/development.rb +41 -0
- data/example/rails-tutorial/config/environments/production.rb +79 -0
- data/example/rails-tutorial/config/environments/test.rb +42 -0
- data/example/rails-tutorial/config/initializers/assets.rb +11 -0
- data/example/rails-tutorial/config/initializers/backtrace_silencers.rb +7 -0
- data/example/rails-tutorial/config/initializers/cookies_serializer.rb +3 -0
- data/example/rails-tutorial/config/initializers/filter_parameter_logging.rb +4 -0
- data/example/rails-tutorial/config/initializers/inflections.rb +16 -0
- data/example/rails-tutorial/config/initializers/mime_types.rb +4 -0
- data/example/rails-tutorial/config/initializers/session_store.rb +3 -0
- data/example/rails-tutorial/config/initializers/wrap_parameters.rb +14 -0
- data/example/rails-tutorial/config/locales/en.yml +23 -0
- data/example/rails-tutorial/config/routes.rb +59 -0
- data/example/rails-tutorial/config/secrets.yml +22 -0
- data/example/rails-tutorial/db/seeds.rb +7 -0
- data/example/rails-tutorial/lib/assets/.keep +0 -0
- data/example/rails-tutorial/lib/tasks/.keep +0 -0
- data/example/rails-tutorial/log/.keep +0 -0
- data/example/rails-tutorial/public/404.html +67 -0
- data/example/rails-tutorial/public/422.html +67 -0
- data/example/rails-tutorial/public/500.html +66 -0
- data/example/rails-tutorial/public/favicon.ico +0 -0
- data/example/rails-tutorial/public/robots.txt +5 -0
- data/example/rails-tutorial/test/controllers/.keep +0 -0
- data/example/rails-tutorial/test/fixtures/.keep +0 -0
- data/example/rails-tutorial/test/helpers/.keep +0 -0
- data/example/rails-tutorial/test/integration/.keep +0 -0
- data/example/rails-tutorial/test/mailers/.keep +0 -0
- data/example/rails-tutorial/test/models/.keep +0 -0
- data/example/rails-tutorial/test/test_helper.rb +10 -0
- data/example/rails-tutorial/vendor/assets/javascripts/.keep +0 -0
- data/example/rails-tutorial/vendor/assets/stylesheets/.keep +0 -0
- data/example/sinatra-tutorial/.DS_Store +0 -0
- data/example/sinatra-tutorial/Gemfile +5 -0
- data/example/sinatra-tutorial/README.md +8 -0
- data/example/sinatra-tutorial/_comments.json +42 -0
- data/example/sinatra-tutorial/app/example.rb +290 -0
- data/example/sinatra-tutorial/app/jquery.js +5 -0
- data/example/sinatra-tutorial/config.ru +58 -0
- data/example/sinatra-tutorial/public/base.css +62 -0
- data/example/todos/Gemfile +11 -0
- data/example/todos/README.md +37 -0
- data/example/todos/Rakefile +8 -0
- data/example/todos/app/application.rb +22 -0
- data/example/todos/app/components/app.react.rb +61 -0
- data/example/todos/app/components/footer.react.rb +31 -0
- data/example/todos/app/components/todo_item.react.rb +46 -0
- data/example/todos/app/components/todo_list.react.rb +25 -0
- data/example/todos/app/models/todo.rb +19 -0
- data/example/todos/config.ru +14 -0
- data/example/todos/index.html.haml +16 -0
- data/example/todos/spec/todo_spec.rb +28 -0
- data/example/todos/vendor/base.css +410 -0
- data/example/todos/vendor/bg.png +0 -0
- data/example/todos/vendor/jquery.js +4 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +105 -0
- data/lib/rails-helpers/top_level_rails_component.rb +54 -0
- data/lib/react/api.rb +127 -0
- data/lib/react/callbacks.rb +42 -0
- data/lib/react/component.rb +269 -0
- data/lib/react/component/api.rb +50 -0
- data/lib/react/component/base.rb +9 -0
- data/lib/react/component/class_methods.rb +190 -0
- data/lib/react/component/props_wrapper.rb +82 -0
- data/lib/react/element.rb +77 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/native_library.rb +53 -0
- data/lib/react/observable.rb +29 -0
- data/lib/react/rendering_context.rb +109 -0
- data/lib/react/state.rb +140 -0
- data/lib/react/top_level.rb +97 -0
- data/lib/react/validator.rb +136 -0
- data/lib/reactive-ruby/component_loader.rb +45 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +196 -0
- data/lib/reactive-ruby/rails.rb +7 -0
- data/lib/reactive-ruby/rails/component_mount.rb +44 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +13 -0
- data/lib/reactive-ruby/rails/railtie.rb +14 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/lib/reactrb.rb +50 -0
- data/lib/sources/react-latest.js +21167 -0
- data/lib/sources/react-v13.js +21642 -0
- data/lib/sources/react-v14.js +20818 -0
- data/lib/sources/react-v15.js +21167 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- data/reactrb.gemspec +43 -0
- data/spec/controller_helper_spec.rb +22 -0
- data/spec/index.html.erb +12 -0
- data/spec/react/callbacks_spec.rb +106 -0
- data/spec/react/component/base_spec.rb +36 -0
- data/spec/react/component_spec.rb +721 -0
- data/spec/react/dsl_spec.rb +161 -0
- data/spec/react/element_spec.rb +47 -0
- data/spec/react/event_spec.rb +24 -0
- data/spec/react/native_library_spec.rb +10 -0
- data/spec/react/observable_spec.rb +7 -0
- data/spec/react/param_declaration_spec.rb +286 -0
- data/spec/react/react_spec.rb +211 -0
- data/spec/react/state_spec.rb +26 -0
- data/spec/react/top_level_component_spec.rb +68 -0
- data/spec/react/tutorial/tutorial_spec.rb +35 -0
- data/spec/react/validator_spec.rb +128 -0
- data/spec/reactive-ruby/component_loader_spec.rb +68 -0
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
- data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +9 -0
- data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
- data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
- data/spec/spec_helper.rb +109 -0
- data/spec/support/react/spec_helpers.rb +57 -0
- data/spec/vendor/es5-shim.min.js +6 -0
- data/spec/vendor/jquery-2.2.4.min.js +4 -0
- metadata +441 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'rails/generators/rails/app/app_generator'
|
|
2
|
+
|
|
3
|
+
module ReactiveRuby
|
|
4
|
+
class TestAppGenerator < ::Rails::Generators::Base
|
|
5
|
+
def self.source_paths
|
|
6
|
+
paths = self.superclass.source_paths
|
|
7
|
+
paths << File.expand_path('../templates', __FILE__)
|
|
8
|
+
paths.flatten
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def remove_existing_app
|
|
12
|
+
remove_dir(test_app_path) if File.directory?(test_app_path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def generate_test_app
|
|
16
|
+
opts = options.dup
|
|
17
|
+
opts[:database] = 'sqlite3' if opts[:database].blank?
|
|
18
|
+
opts[:force] = true
|
|
19
|
+
opts[:skip_bundle] = true
|
|
20
|
+
|
|
21
|
+
puts "Generating Test Rails Application..."
|
|
22
|
+
invoke ::Rails::Generators::AppGenerator,
|
|
23
|
+
[ File.expand_path(test_app_path, destination_root) ], opts
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def configure_test_app
|
|
27
|
+
template 'boot.rb.erb', "#{test_app_path}/config/boot.rb", force: true
|
|
28
|
+
template 'test_application.rb.erb', "#{test_app_path}/config/application.rb", force: true
|
|
29
|
+
template 'assets/javascripts/test_application.rb',
|
|
30
|
+
"#{test_app_path}/app/assets/javascripts/application.rb", force: true
|
|
31
|
+
template 'assets/javascripts/components.rb',
|
|
32
|
+
"#{test_app_path}/app/views/components.rb", force: true
|
|
33
|
+
template 'views/components/hello_world.rb',
|
|
34
|
+
"#{test_app_path}/app/views/components/hello_world.rb", force: true
|
|
35
|
+
template 'views/components/todo.rb',
|
|
36
|
+
"#{test_app_path}/app/views/components/todo.rb", force: true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def clean_superfluous_files
|
|
40
|
+
inside test_app_path do
|
|
41
|
+
remove_file '.gitignore'
|
|
42
|
+
remove_file 'doc'
|
|
43
|
+
remove_file 'Gemfile'
|
|
44
|
+
remove_file 'lib/tasks'
|
|
45
|
+
remove_file 'app/assets/images/rails.png'
|
|
46
|
+
remove_file 'app/assets/javascripts/application.js'
|
|
47
|
+
remove_file 'public/index.html'
|
|
48
|
+
remove_file 'public/robots.txt'
|
|
49
|
+
remove_file 'README.rdoc'
|
|
50
|
+
remove_file 'test'
|
|
51
|
+
remove_file 'vendor'
|
|
52
|
+
remove_file 'spec'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def configure_opal_rspec
|
|
57
|
+
inject_into_file "#{test_app_path}/config/application.rb",
|
|
58
|
+
after: /class Application < Rails::Application/, verbose: true do
|
|
59
|
+
%Q[
|
|
60
|
+
config.opal.method_missing = true
|
|
61
|
+
config.opal.optimized_operators = true
|
|
62
|
+
config.opal.arity_check = false
|
|
63
|
+
config.opal.const_missing = true
|
|
64
|
+
config.opal.dynamic_require_severity = :ignore
|
|
65
|
+
config.opal.enable_specs = true
|
|
66
|
+
config.opal.spec_location = 'spec-opal'
|
|
67
|
+
]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
protected
|
|
72
|
+
|
|
73
|
+
def application_definition
|
|
74
|
+
@application_definition ||= begin
|
|
75
|
+
test_application_contents
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
alias :store_application_definition! :application_definition
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def test_app_path
|
|
83
|
+
'spec/test_app'
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_application_path
|
|
87
|
+
File.expand_path("#{test_app_path}/config/application.rb",
|
|
88
|
+
destination_root)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_application_contents
|
|
92
|
+
return unless File.exists?(test_application_path) && !options[:pretend]
|
|
93
|
+
contents = File.read(test_application_path)
|
|
94
|
+
contents[(contents.index("module #{module_name}"))..-1]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def module_name
|
|
98
|
+
'TestApp'
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def gemfile_path
|
|
102
|
+
'../../../../Gemfile'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module React
|
|
2
|
+
class TopLevelRailsComponent
|
|
3
|
+
include React::Component
|
|
4
|
+
|
|
5
|
+
def self.search_path
|
|
6
|
+
@search_path ||= [Module]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
export_component
|
|
10
|
+
|
|
11
|
+
param :component_name
|
|
12
|
+
param :controller
|
|
13
|
+
param :render_params
|
|
14
|
+
|
|
15
|
+
backtrace :on
|
|
16
|
+
|
|
17
|
+
def render
|
|
18
|
+
paths_searched = []
|
|
19
|
+
if params.component_name.start_with? "::"
|
|
20
|
+
paths_searched << params.component_name.gsub(/^\:\:/,"")
|
|
21
|
+
component = params.component_name.gsub(/^\:\:/,"").split("::").inject(Module) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
|
22
|
+
return present component, params.render_params if component && component.method_defined?(:render)
|
|
23
|
+
else
|
|
24
|
+
self.class.search_path.each do |path|
|
|
25
|
+
# try each path + params.controller + params.component_name
|
|
26
|
+
paths_searched << "#{path.name + '::' unless path == Module}#{params.controller}::#{params.component_name}"
|
|
27
|
+
component = "#{params.controller}::#{params.component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
|
28
|
+
return present component, params.render_params if component && component.method_defined?(:render)
|
|
29
|
+
end
|
|
30
|
+
self.class.search_path.each do |path|
|
|
31
|
+
# then try each path + params.component_name
|
|
32
|
+
paths_searched << "#{path.name + '::' unless path == Module}#{params.component_name}"
|
|
33
|
+
component = "#{params.component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
|
34
|
+
return present component, params.render_params if component && component.method_defined?(:render)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
raise "Could not find component class '#{params.component_name}' for params.controller '#{params.controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Module
|
|
43
|
+
def add_to_react_search_path(replace_search_path = nil)
|
|
44
|
+
if replace_search_path
|
|
45
|
+
React::TopLevelRailsComponent.search_path = [self]
|
|
46
|
+
elsif !React::TopLevelRailsComponent.search_path.include? self
|
|
47
|
+
React::TopLevelRailsComponent.search_path << self
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module Components
|
|
53
|
+
add_to_react_search_path
|
|
54
|
+
end
|
data/lib/react/api.rb
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require 'react/native_library'
|
|
2
|
+
|
|
3
|
+
module React
|
|
4
|
+
class API
|
|
5
|
+
@@component_classes = {}
|
|
6
|
+
|
|
7
|
+
def self.import_native_component(opal_class, native_class)
|
|
8
|
+
@@component_classes[opal_class.to_s] = native_class
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.create_native_react_class(type)
|
|
12
|
+
raise "Provided class should define `render` method" if !(type.method_defined? :render)
|
|
13
|
+
render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
|
|
14
|
+
# 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"
|
|
15
|
+
@@component_classes[type] ||= %x{
|
|
16
|
+
React.createClass({
|
|
17
|
+
displayName: #{type.name},
|
|
18
|
+
propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
|
|
19
|
+
getDefaultProps: function(){
|
|
20
|
+
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
|
|
21
|
+
},
|
|
22
|
+
mixins: #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`},
|
|
23
|
+
statics: #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`},
|
|
24
|
+
componentWillMount: function() {
|
|
25
|
+
var instance = this._getOpalInstance.apply(this);
|
|
26
|
+
return #{`instance`.component_will_mount if type.method_defined? :component_will_mount};
|
|
27
|
+
},
|
|
28
|
+
componentDidMount: function() {
|
|
29
|
+
var instance = this._getOpalInstance.apply(this);
|
|
30
|
+
return #{`instance`.component_did_mount if type.method_defined? :component_did_mount};
|
|
31
|
+
},
|
|
32
|
+
componentWillReceiveProps: function(next_props) {
|
|
33
|
+
var instance = this._getOpalInstance.apply(this);
|
|
34
|
+
return #{`instance`.component_will_receive_props(`next_props`) if type.method_defined? :component_will_receive_props};
|
|
35
|
+
},
|
|
36
|
+
shouldComponentUpdate: function(next_props, next_state) {
|
|
37
|
+
var instance = this._getOpalInstance.apply(this);
|
|
38
|
+
return #{`instance`.should_component_update?(`next_props`, `next_state`) if type.method_defined? :should_component_update?};
|
|
39
|
+
},
|
|
40
|
+
componentWillUpdate: function(next_props, next_state) {
|
|
41
|
+
var instance = this._getOpalInstance.apply(this);
|
|
42
|
+
return #{`instance`.component_will_update(`next_props`, `next_state`) if type.method_defined? :component_will_update};
|
|
43
|
+
},
|
|
44
|
+
componentDidUpdate: function(prev_props, prev_state) {
|
|
45
|
+
var instance = this._getOpalInstance.apply(this);
|
|
46
|
+
return #{`instance`.component_did_update(`prev_props`, `prev_state`) if type.method_defined? :component_did_update};
|
|
47
|
+
},
|
|
48
|
+
componentWillUnmount: function() {
|
|
49
|
+
var instance = this._getOpalInstance.apply(this);
|
|
50
|
+
return #{`instance`.component_will_unmount if type.method_defined? :component_will_unmount};
|
|
51
|
+
},
|
|
52
|
+
_getOpalInstance: function() {
|
|
53
|
+
if (this.__opalInstance == undefined) {
|
|
54
|
+
var instance = #{type.new(`this`)};
|
|
55
|
+
} else {
|
|
56
|
+
var instance = this.__opalInstance;
|
|
57
|
+
}
|
|
58
|
+
this.__opalInstance = instance;
|
|
59
|
+
return instance;
|
|
60
|
+
},
|
|
61
|
+
render: function() {
|
|
62
|
+
var instance = this._getOpalInstance.apply(this);
|
|
63
|
+
return #{`instance`.send(render_fn).to_n};
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.create_element(type, properties = {}, &block)
|
|
70
|
+
params = []
|
|
71
|
+
|
|
72
|
+
# Component Spec, Normal DOM, String or Native Component
|
|
73
|
+
if @@component_classes[type]
|
|
74
|
+
params << @@component_classes[type]
|
|
75
|
+
elsif type.kind_of?(Class)
|
|
76
|
+
params << create_native_react_class(type)
|
|
77
|
+
elsif HTML_TAGS.include?(type)
|
|
78
|
+
params << type
|
|
79
|
+
elsif type.is_a? String
|
|
80
|
+
return React::Element.new(type)
|
|
81
|
+
else
|
|
82
|
+
raise "#{type} not implemented"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Passed in properties
|
|
86
|
+
params << convert_props(properties)
|
|
87
|
+
|
|
88
|
+
# Children Nodes
|
|
89
|
+
if block_given?
|
|
90
|
+
children = [yield].flatten.each do |ele|
|
|
91
|
+
params << ele.to_n
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
return React::Element.new(`React.createElement.apply(null, #{params})`, type, properties, block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.clear_component_class_cache
|
|
98
|
+
@@component_classes = {}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.convert_props(properties)
|
|
102
|
+
raise "Component parameters must be a hash. Instead you sent #{properties}" unless properties.is_a? Hash
|
|
103
|
+
props = {}
|
|
104
|
+
properties.map do |key, value|
|
|
105
|
+
if key == "class_name" && value.is_a?(Hash)
|
|
106
|
+
props[lower_camelize(key)] = `React.addons.classSet(#{value.to_n})`
|
|
107
|
+
elsif key == "class"
|
|
108
|
+
props["className"] = value
|
|
109
|
+
elsif ["style", "dangerously_set_inner_HTML"].include? key
|
|
110
|
+
props[lower_camelize(key)] = value.to_n
|
|
111
|
+
else
|
|
112
|
+
props[React::ATTRIBUTES.include?(lower_camelize(key)) ? lower_camelize(key) : key] = value
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
props.shallow_to_n
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def self.lower_camelize(snake_cased_word)
|
|
121
|
+
words = snake_cased_word.split("_")
|
|
122
|
+
result = [words.first]
|
|
123
|
+
result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] })
|
|
124
|
+
result.join("")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
|
2
|
+
|
|
3
|
+
module React
|
|
4
|
+
module Callbacks
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def run_callback(name, *args)
|
|
10
|
+
self.class.callbacks_for(name).each do |callback|
|
|
11
|
+
if callback.is_a?(Proc)
|
|
12
|
+
instance_exec(*args, &callback)
|
|
13
|
+
else
|
|
14
|
+
send(callback, *args)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module ClassMethods
|
|
20
|
+
def define_callback(callback_name)
|
|
21
|
+
attribute_name = "_#{callback_name}_callbacks"
|
|
22
|
+
class_attribute(attribute_name)
|
|
23
|
+
self.send("#{attribute_name}=", [])
|
|
24
|
+
define_singleton_method(callback_name) do |*args, &block|
|
|
25
|
+
callbacks = self.send(attribute_name)
|
|
26
|
+
callbacks.concat(args)
|
|
27
|
+
callbacks.push(block) if block_given?
|
|
28
|
+
self.send("#{attribute_name}=", callbacks)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def callbacks_for(callback_name)
|
|
33
|
+
attribute_name = "_#{callback_name}_callbacks"
|
|
34
|
+
if superclass.respond_to? :callbacks_for
|
|
35
|
+
superclass.callbacks_for(callback_name)
|
|
36
|
+
else
|
|
37
|
+
[]
|
|
38
|
+
end + self.send(attribute_name)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
require 'react/ext/string'
|
|
2
|
+
require 'react/ext/hash'
|
|
3
|
+
require 'active_support/core_ext/class/attribute'
|
|
4
|
+
require 'react/callbacks'
|
|
5
|
+
require 'react/rendering_context'
|
|
6
|
+
require 'react/observable'
|
|
7
|
+
require 'react/state'
|
|
8
|
+
require 'react/component/api'
|
|
9
|
+
require 'react/component/class_methods'
|
|
10
|
+
require 'react/component/props_wrapper'
|
|
11
|
+
require 'native'
|
|
12
|
+
|
|
13
|
+
module React
|
|
14
|
+
module Component
|
|
15
|
+
def self.included(base)
|
|
16
|
+
base.include(API)
|
|
17
|
+
base.include(Callbacks)
|
|
18
|
+
base.class_eval do
|
|
19
|
+
class_attribute :initial_state
|
|
20
|
+
define_callback :before_mount
|
|
21
|
+
define_callback :after_mount
|
|
22
|
+
define_callback :before_receive_props
|
|
23
|
+
define_callback :before_update
|
|
24
|
+
define_callback :after_update
|
|
25
|
+
define_callback :before_unmount
|
|
26
|
+
end
|
|
27
|
+
base.extend(ClassMethods)
|
|
28
|
+
|
|
29
|
+
if base.name
|
|
30
|
+
parent = base.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
|
|
31
|
+
|
|
32
|
+
class << parent
|
|
33
|
+
def method_missing(n, *args, &block)
|
|
34
|
+
name = n
|
|
35
|
+
if name =~ /_as_node$/
|
|
36
|
+
node_only = true
|
|
37
|
+
name = name.gsub(/_as_node$/, "")
|
|
38
|
+
end
|
|
39
|
+
begin
|
|
40
|
+
name = const_get(name)
|
|
41
|
+
rescue Exception
|
|
42
|
+
name = nil
|
|
43
|
+
end
|
|
44
|
+
unless name && name.method_defined?(:render)
|
|
45
|
+
return super
|
|
46
|
+
end
|
|
47
|
+
React::RenderingContext.build_or_render(node_only, name, *args, &block)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def initialize(native_element)
|
|
54
|
+
@native = native_element
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def render
|
|
58
|
+
raise "no render defined"
|
|
59
|
+
end unless method_defined?(:render)
|
|
60
|
+
|
|
61
|
+
def deprecated_params_method(name, *args, &block)
|
|
62
|
+
self.class.deprecation_warning "Direct access to param `#{name}`. Use `params.#{name}` instead."
|
|
63
|
+
params.send(name, *args, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def children
|
|
67
|
+
nodes = if `#{@native}.props.children==undefined`
|
|
68
|
+
[]
|
|
69
|
+
else
|
|
70
|
+
[`#{@native}.props.children`].flatten
|
|
71
|
+
end
|
|
72
|
+
class << nodes
|
|
73
|
+
include Enumerable
|
|
74
|
+
|
|
75
|
+
def to_n
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def each(&block)
|
|
80
|
+
if block_given?
|
|
81
|
+
%x{
|
|
82
|
+
React.Children.forEach(#{self.to_n}, function(context){
|
|
83
|
+
#{block.call(React::Element.new(`context`))}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
nil
|
|
87
|
+
else
|
|
88
|
+
Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
|
|
89
|
+
%x{
|
|
90
|
+
React.Children.forEach(#{self.to_n}, function(context){
|
|
91
|
+
#{y << React::Element.new(`context`)}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
nodes
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def params
|
|
103
|
+
@props_wrapper
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def props
|
|
107
|
+
Hash.new(`#{@native}.props`)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def refs
|
|
111
|
+
Hash.new(`#{@native}.refs`)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def state
|
|
115
|
+
#raise "No native ReactComponent associated" unless @native
|
|
116
|
+
@state_wrapper ||= StateWrapper.new(@native, self)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def update_react_js_state(object, name, value)
|
|
120
|
+
if object
|
|
121
|
+
set_state({"***_state_updated_at-***" => Time.now.to_f, "#{object.class.to_s+'.' unless object == self}#{name}" => value})
|
|
122
|
+
else
|
|
123
|
+
set_state({name => value})
|
|
124
|
+
end rescue nil
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def emit(event_name, *args)
|
|
128
|
+
self.params["_on#{event_name.to_s.event_camelize}"].call(*args)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def component_will_mount
|
|
132
|
+
IsomorphicHelpers.load_context(true) if IsomorphicHelpers.on_opal_client?
|
|
133
|
+
@props_wrapper = self.class.props_wrapper.new(Hash.new(`#{@native}.props`))
|
|
134
|
+
set_state! initial_state if initial_state
|
|
135
|
+
State.initialize_states(self, initial_state)
|
|
136
|
+
State.set_state_context_to(self) { self.run_callback(:before_mount) }
|
|
137
|
+
rescue Exception => e
|
|
138
|
+
self.class.process_exception(e, self)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def component_did_mount
|
|
142
|
+
State.set_state_context_to(self) do
|
|
143
|
+
self.run_callback(:after_mount)
|
|
144
|
+
State.update_states_to_observe
|
|
145
|
+
end
|
|
146
|
+
rescue Exception => e
|
|
147
|
+
self.class.process_exception(e, self)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def component_will_receive_props(next_props)
|
|
151
|
+
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
|
|
152
|
+
# for now we are just using it to clear processed_params
|
|
153
|
+
State.set_state_context_to(self) { self.run_callback(:before_receive_props, Hash.new(next_props)) }
|
|
154
|
+
rescue Exception => e
|
|
155
|
+
self.class.process_exception(e, self)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def props_changed?(next_props)
|
|
159
|
+
return true unless props.keys.sort == next_props.keys.sort
|
|
160
|
+
props.detect { |k, v| `#{next_props[k]} != #{params[k]}`}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def should_component_update?(next_props, next_state)
|
|
164
|
+
State.set_state_context_to(self) do
|
|
165
|
+
next_props = Hash.new(next_props)
|
|
166
|
+
if self.respond_to?(:needs_update?)
|
|
167
|
+
!!self.needs_update?(next_props, Hash.new(next_state))
|
|
168
|
+
elsif false # switch to true to force updates per standard react
|
|
169
|
+
true
|
|
170
|
+
elsif props_changed? next_props
|
|
171
|
+
true
|
|
172
|
+
elsif `!next_state != !#{@native}.state`
|
|
173
|
+
true
|
|
174
|
+
elsif `!next_state && !#{@native}.state`
|
|
175
|
+
false
|
|
176
|
+
elsif `next_state["***_state_updated_at-***"] != #{@native}.state["***_state_updated_at-***"]`
|
|
177
|
+
true
|
|
178
|
+
else
|
|
179
|
+
false
|
|
180
|
+
end.to_n
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def component_will_update(next_props, next_state)
|
|
185
|
+
State.set_state_context_to(self) { self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state)) }
|
|
186
|
+
@props_wrapper = self.class.props_wrapper.new(Hash.new(next_props), @props_wrapper)
|
|
187
|
+
rescue Exception => e
|
|
188
|
+
self.class.process_exception(e, self)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def component_did_update(prev_props, prev_state)
|
|
192
|
+
State.set_state_context_to(self) do
|
|
193
|
+
self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
|
|
194
|
+
State.update_states_to_observe
|
|
195
|
+
end
|
|
196
|
+
rescue Exception => e
|
|
197
|
+
self.class.process_exception(e, self)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def component_will_unmount
|
|
201
|
+
State.set_state_context_to(self) do
|
|
202
|
+
self.run_callback(:before_unmount)
|
|
203
|
+
State.remove
|
|
204
|
+
end
|
|
205
|
+
rescue Exception => e
|
|
206
|
+
self.class.process_exception(e, self)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def p(*args, &block)
|
|
210
|
+
if block || args.count == 0 || (args.count == 1 && args.first.is_a?(Hash))
|
|
211
|
+
_p_tag(*args, &block)
|
|
212
|
+
else
|
|
213
|
+
Kernel.p(*args)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def component?(name)
|
|
218
|
+
name_list = name.split("::")
|
|
219
|
+
scope_list = self.class.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }.reverse
|
|
220
|
+
scope_list.each do |scope|
|
|
221
|
+
component = name_list.inject(scope) do |scope, class_name|
|
|
222
|
+
scope.const_get(class_name)
|
|
223
|
+
end rescue nil
|
|
224
|
+
return component if component && component.method_defined?(:render)
|
|
225
|
+
end
|
|
226
|
+
nil
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def method_missing(n, *args, &block)
|
|
230
|
+
return props[n] if props.key? n # TODO deprecate and remove - done so that params shadow tags, no longer needed
|
|
231
|
+
name = n
|
|
232
|
+
if name =~ /_as_node$/
|
|
233
|
+
node_only = true
|
|
234
|
+
name = name.gsub(/_as_node$/, "")
|
|
235
|
+
end
|
|
236
|
+
unless (HTML_TAGS.include?(name) || name == 'present' || name == '_p_tag' || (name = component?(name, self)))
|
|
237
|
+
return super
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
if name == "present"
|
|
241
|
+
name = args.shift
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
if name == "_p_tag"
|
|
245
|
+
name = "p"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
React::RenderingContext.build_or_render(node_only, name, *args, &block)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def watch(value, &on_change)
|
|
252
|
+
Observable.new(value, on_change)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def define_state(*args, &block)
|
|
256
|
+
State.initialize_states(self, self.class.define_state(*args, &block))
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
attr_reader :waiting_on_resources
|
|
260
|
+
|
|
261
|
+
def _render_wrapper
|
|
262
|
+
State.set_state_context_to(self) do
|
|
263
|
+
React::RenderingContext.render(nil) {render || ""}.tap { |element| @waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources }
|
|
264
|
+
end
|
|
265
|
+
rescue Exception => e
|
|
266
|
+
self.class.process_exception(e, self)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|