reactrb 0.7.42

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