reactrb 0.7.42
Sign up to get free protection for your applications and to get access to all the features.
- 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
|