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.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +6 -0
  3. data/.gitignore +33 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +19 -0
  7. data/README.md +117 -0
  8. data/Rakefile +28 -0
  9. data/config.ru +16 -0
  10. data/example/examples/Gemfile +7 -0
  11. data/example/examples/app/basics.js.rb +42 -0
  12. data/example/examples/app/items.rb +11 -0
  13. data/example/examples/app/jquery.js +5 -0
  14. data/example/examples/app/nodes.rb +61 -0
  15. data/example/examples/app/react-router.js +6 -0
  16. data/example/examples/app/react_api_demo.rb +29 -0
  17. data/example/examples/app/rerendering.rb +72 -0
  18. data/example/examples/app/reuse.rb +59 -0
  19. data/example/examples/app/show.rb +52 -0
  20. data/example/examples/config.ru +38 -0
  21. data/example/rails-tutorial/.gitignore +17 -0
  22. data/example/rails-tutorial/Gemfile +51 -0
  23. data/example/rails-tutorial/README.rdoc +28 -0
  24. data/example/rails-tutorial/Rakefile +6 -0
  25. data/example/rails-tutorial/app/assets/images/.keep +0 -0
  26. data/example/rails-tutorial/app/assets/javascripts/application.rb +15 -0
  27. data/example/rails-tutorial/app/assets/stylesheets/application.css +15 -0
  28. data/example/rails-tutorial/app/controllers/application_controller.rb +6 -0
  29. data/example/rails-tutorial/app/controllers/concerns/.keep +0 -0
  30. data/example/rails-tutorial/app/controllers/home_controller.rb +6 -0
  31. data/example/rails-tutorial/app/helpers/application_helper.rb +2 -0
  32. data/example/rails-tutorial/app/mailers/.keep +0 -0
  33. data/example/rails-tutorial/app/models/.keep +0 -0
  34. data/example/rails-tutorial/app/models/concerns/.keep +0 -0
  35. data/example/rails-tutorial/app/views/components.rb +3 -0
  36. data/example/rails-tutorial/app/views/components/home/show.rb +47 -0
  37. data/example/rails-tutorial/app/views/layouts/application.html.erb +14 -0
  38. data/example/rails-tutorial/bin/bundle +3 -0
  39. data/example/rails-tutorial/bin/rails +8 -0
  40. data/example/rails-tutorial/bin/rake +8 -0
  41. data/example/rails-tutorial/bin/setup +29 -0
  42. data/example/rails-tutorial/bin/spring +15 -0
  43. data/example/rails-tutorial/config.ru +4 -0
  44. data/example/rails-tutorial/config/application.rb +26 -0
  45. data/example/rails-tutorial/config/boot.rb +3 -0
  46. data/example/rails-tutorial/config/database.yml +25 -0
  47. data/example/rails-tutorial/config/environment.rb +5 -0
  48. data/example/rails-tutorial/config/environments/development.rb +41 -0
  49. data/example/rails-tutorial/config/environments/production.rb +79 -0
  50. data/example/rails-tutorial/config/environments/test.rb +42 -0
  51. data/example/rails-tutorial/config/initializers/assets.rb +11 -0
  52. data/example/rails-tutorial/config/initializers/backtrace_silencers.rb +7 -0
  53. data/example/rails-tutorial/config/initializers/cookies_serializer.rb +3 -0
  54. data/example/rails-tutorial/config/initializers/filter_parameter_logging.rb +4 -0
  55. data/example/rails-tutorial/config/initializers/inflections.rb +16 -0
  56. data/example/rails-tutorial/config/initializers/mime_types.rb +4 -0
  57. data/example/rails-tutorial/config/initializers/session_store.rb +3 -0
  58. data/example/rails-tutorial/config/initializers/wrap_parameters.rb +14 -0
  59. data/example/rails-tutorial/config/locales/en.yml +23 -0
  60. data/example/rails-tutorial/config/routes.rb +59 -0
  61. data/example/rails-tutorial/config/secrets.yml +22 -0
  62. data/example/rails-tutorial/db/seeds.rb +7 -0
  63. data/example/rails-tutorial/lib/assets/.keep +0 -0
  64. data/example/rails-tutorial/lib/tasks/.keep +0 -0
  65. data/example/rails-tutorial/log/.keep +0 -0
  66. data/example/rails-tutorial/public/404.html +67 -0
  67. data/example/rails-tutorial/public/422.html +67 -0
  68. data/example/rails-tutorial/public/500.html +66 -0
  69. data/example/rails-tutorial/public/favicon.ico +0 -0
  70. data/example/rails-tutorial/public/robots.txt +5 -0
  71. data/example/rails-tutorial/test/controllers/.keep +0 -0
  72. data/example/rails-tutorial/test/fixtures/.keep +0 -0
  73. data/example/rails-tutorial/test/helpers/.keep +0 -0
  74. data/example/rails-tutorial/test/integration/.keep +0 -0
  75. data/example/rails-tutorial/test/mailers/.keep +0 -0
  76. data/example/rails-tutorial/test/models/.keep +0 -0
  77. data/example/rails-tutorial/test/test_helper.rb +10 -0
  78. data/example/rails-tutorial/vendor/assets/javascripts/.keep +0 -0
  79. data/example/rails-tutorial/vendor/assets/stylesheets/.keep +0 -0
  80. data/example/sinatra-tutorial/.DS_Store +0 -0
  81. data/example/sinatra-tutorial/Gemfile +5 -0
  82. data/example/sinatra-tutorial/README.md +8 -0
  83. data/example/sinatra-tutorial/_comments.json +42 -0
  84. data/example/sinatra-tutorial/app/example.rb +290 -0
  85. data/example/sinatra-tutorial/app/jquery.js +5 -0
  86. data/example/sinatra-tutorial/config.ru +58 -0
  87. data/example/sinatra-tutorial/public/base.css +62 -0
  88. data/example/todos/Gemfile +11 -0
  89. data/example/todos/README.md +37 -0
  90. data/example/todos/Rakefile +8 -0
  91. data/example/todos/app/application.rb +22 -0
  92. data/example/todos/app/components/app.react.rb +61 -0
  93. data/example/todos/app/components/footer.react.rb +31 -0
  94. data/example/todos/app/components/todo_item.react.rb +46 -0
  95. data/example/todos/app/components/todo_list.react.rb +25 -0
  96. data/example/todos/app/models/todo.rb +19 -0
  97. data/example/todos/config.ru +14 -0
  98. data/example/todos/index.html.haml +16 -0
  99. data/example/todos/spec/todo_spec.rb +28 -0
  100. data/example/todos/vendor/base.css +410 -0
  101. data/example/todos/vendor/bg.png +0 -0
  102. data/example/todos/vendor/jquery.js +4 -0
  103. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
  104. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  105. data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  106. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  107. data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  108. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  109. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  110. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +105 -0
  111. data/lib/rails-helpers/top_level_rails_component.rb +54 -0
  112. data/lib/react/api.rb +127 -0
  113. data/lib/react/callbacks.rb +42 -0
  114. data/lib/react/component.rb +269 -0
  115. data/lib/react/component/api.rb +50 -0
  116. data/lib/react/component/base.rb +9 -0
  117. data/lib/react/component/class_methods.rb +190 -0
  118. data/lib/react/component/props_wrapper.rb +82 -0
  119. data/lib/react/element.rb +77 -0
  120. data/lib/react/event.rb +76 -0
  121. data/lib/react/ext/hash.rb +9 -0
  122. data/lib/react/ext/string.rb +8 -0
  123. data/lib/react/native_library.rb +53 -0
  124. data/lib/react/observable.rb +29 -0
  125. data/lib/react/rendering_context.rb +109 -0
  126. data/lib/react/state.rb +140 -0
  127. data/lib/react/top_level.rb +97 -0
  128. data/lib/react/validator.rb +136 -0
  129. data/lib/reactive-ruby/component_loader.rb +45 -0
  130. data/lib/reactive-ruby/isomorphic_helpers.rb +196 -0
  131. data/lib/reactive-ruby/rails.rb +7 -0
  132. data/lib/reactive-ruby/rails/component_mount.rb +44 -0
  133. data/lib/reactive-ruby/rails/controller_helper.rb +13 -0
  134. data/lib/reactive-ruby/rails/railtie.rb +14 -0
  135. data/lib/reactive-ruby/serializers.rb +15 -0
  136. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
  137. data/lib/reactive-ruby/version.rb +3 -0
  138. data/lib/reactrb.rb +50 -0
  139. data/lib/sources/react-latest.js +21167 -0
  140. data/lib/sources/react-v13.js +21642 -0
  141. data/lib/sources/react-v14.js +20818 -0
  142. data/lib/sources/react-v15.js +21167 -0
  143. data/logo1.png +0 -0
  144. data/logo2.png +0 -0
  145. data/logo3.png +0 -0
  146. data/path_release_steps.md +9 -0
  147. data/reactrb.gemspec +43 -0
  148. data/spec/controller_helper_spec.rb +22 -0
  149. data/spec/index.html.erb +12 -0
  150. data/spec/react/callbacks_spec.rb +106 -0
  151. data/spec/react/component/base_spec.rb +36 -0
  152. data/spec/react/component_spec.rb +721 -0
  153. data/spec/react/dsl_spec.rb +161 -0
  154. data/spec/react/element_spec.rb +47 -0
  155. data/spec/react/event_spec.rb +24 -0
  156. data/spec/react/native_library_spec.rb +10 -0
  157. data/spec/react/observable_spec.rb +7 -0
  158. data/spec/react/param_declaration_spec.rb +286 -0
  159. data/spec/react/react_spec.rb +211 -0
  160. data/spec/react/state_spec.rb +26 -0
  161. data/spec/react/top_level_component_spec.rb +68 -0
  162. data/spec/react/tutorial/tutorial_spec.rb +35 -0
  163. data/spec/react/validator_spec.rb +128 -0
  164. data/spec/reactive-ruby/component_loader_spec.rb +68 -0
  165. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
  166. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +9 -0
  167. data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
  168. data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
  169. data/spec/spec_helper.rb +109 -0
  170. data/spec/support/react/spec_helpers.rb +57 -0
  171. data/spec/vendor/es5-shim.min.js +6 -0
  172. data/spec/vendor/jquery-2.2.4.min.js +4 -0
  173. metadata +441 -0
@@ -0,0 +1,11 @@
1
+ module Components
2
+ class HelloWorld
3
+ include React::Component
4
+
5
+ def render
6
+ div do
7
+ "Hello, World!".span
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module Components
2
+ class Todo
3
+ include React::Component
4
+ export_component
5
+
6
+ params do
7
+ requires :todo
8
+ end
9
+
10
+ def render
11
+ li { "#{params[:todo]}" }
12
+ end
13
+ end
14
+ end
@@ -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