isomorfeus-react 16.5.1

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +1 -0
  3. data/README.md +620 -0
  4. data/isomorfeus-react.gemspec +23 -0
  5. data/lib/isomorfeus-react.rb +131 -0
  6. data/lib/isomorfeus/config.rb +84 -0
  7. data/lib/isomorfeus/top_level.rb +48 -0
  8. data/lib/isomorfeus/view_helpers.rb +38 -0
  9. data/lib/lucid_app/api.rb +22 -0
  10. data/lib/lucid_app/base.rb +7 -0
  11. data/lib/lucid_app/context.rb +7 -0
  12. data/lib/lucid_app/mixin.rb +17 -0
  13. data/lib/lucid_app/native_component_constructor.rb +70 -0
  14. data/lib/lucid_component/api.rb +97 -0
  15. data/lib/lucid_component/base.rb +7 -0
  16. data/lib/lucid_component/event_handler.rb +17 -0
  17. data/lib/lucid_component/initializer.rb +12 -0
  18. data/lib/lucid_component/mixin.rb +17 -0
  19. data/lib/lucid_component/native_component_constructor.rb +131 -0
  20. data/lib/react.rb +147 -0
  21. data/lib/react/active_support_support.rb +13 -0
  22. data/lib/react/component/api.rb +226 -0
  23. data/lib/react/component/base.rb +9 -0
  24. data/lib/react/component/elements.rb +78 -0
  25. data/lib/react/component/event_handler.rb +19 -0
  26. data/lib/react/component/features.rb +47 -0
  27. data/lib/react/component/history.rb +36 -0
  28. data/lib/react/component/initializer.rb +11 -0
  29. data/lib/react/component/location.rb +15 -0
  30. data/lib/react/component/match.rb +31 -0
  31. data/lib/react/component/mixin.rb +19 -0
  32. data/lib/react/component/native_component_constructor.rb +76 -0
  33. data/lib/react/component/native_component_validate_prop.rb +37 -0
  34. data/lib/react/component/props.rb +49 -0
  35. data/lib/react/component/resolution.rb +71 -0
  36. data/lib/react/component/should_component_update.rb +14 -0
  37. data/lib/react/component/state.rb +52 -0
  38. data/lib/react/component/unsafe_api.rb +33 -0
  39. data/lib/react/context_wrapper.rb +47 -0
  40. data/lib/react/function_component/creator.rb +47 -0
  41. data/lib/react/function_component/resolution.rb +61 -0
  42. data/lib/react/function_component/runner.rb +19 -0
  43. data/lib/react/native_constant_wrapper.rb +34 -0
  44. data/lib/react/pure_component/base.rb +9 -0
  45. data/lib/react/pure_component/mixin.rb +17 -0
  46. data/lib/react/redux_component/api.rb +132 -0
  47. data/lib/react/redux_component/app_store_defaults.rb +38 -0
  48. data/lib/react/redux_component/app_store_proxy.rb +46 -0
  49. data/lib/react/redux_component/base.rb +9 -0
  50. data/lib/react/redux_component/class_store_proxy.rb +50 -0
  51. data/lib/react/redux_component/component_class_store_defaults.rb +40 -0
  52. data/lib/react/redux_component/component_instance_store_defaults.rb +41 -0
  53. data/lib/react/redux_component/initializer.rb +14 -0
  54. data/lib/react/redux_component/instance_store_proxy.rb +50 -0
  55. data/lib/react/redux_component/mixin.rb +18 -0
  56. data/lib/react/redux_component/native_component_constructor.rb +119 -0
  57. data/lib/react/redux_component/reducers.rb +53 -0
  58. data/lib/react/ref.rb +19 -0
  59. data/lib/react/synthetic_event.rb +53 -0
  60. data/lib/react/version.rb +3 -0
  61. data/lib/react_dom.rb +31 -0
  62. data/lib/react_dom_server.rb +17 -0
  63. metadata +167 -0
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require_relative 'lib/react/version.rb'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'isomorfeus-react'
6
+ s.version = React::VERSION
7
+
8
+ s.authors = ['Jan Biedermann']
9
+ s.email = ['jan@kursator.com']
10
+ s.homepage = 'http://isomorfeus.com'
11
+ s.summary = 'React for Opal Ruby.'
12
+ s.license = 'MIT'
13
+ s.description = 'Write React Components in Ruby.'
14
+
15
+ s.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(gemfiles|s)/}) }
16
+ #s.test_files = `git ls-files -- {test,s,features}/*`.split("\n")
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
20
+ s.add_dependency 'opal-activesupport', '~> 0.3.1'
21
+ s.add_dependency 'opal-browser', '~> 0.2.0'
22
+ s.add_dependency 'isomorfeus-redux', '~> 4.0.0'
23
+ end
@@ -0,0 +1,131 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ require 'opal'
3
+ require 'native'
4
+ require 'active_support/core_ext/string'
5
+ require 'react/active_support_support'
6
+ require 'browser/support'
7
+ require 'browser/event'
8
+ require 'browser/event_source'
9
+ require 'browser/screen'
10
+ require 'browser/socket'
11
+ require 'browser/window'
12
+ require 'browser/dom/node'
13
+ require 'browser/dom/element'
14
+ require 'isomorfeus-redux'
15
+ require 'react/version'
16
+ require 'react'
17
+ # require 'react/element' # usually not needed
18
+ require 'react/synthetic_event'
19
+ require 'react_dom'
20
+ # React::Component
21
+ require 'react/component/api'
22
+ require 'react/component/unsafe_api'
23
+ require 'react/component/initializer'
24
+ require 'react/component/features'
25
+ require 'react/native_constant_wrapper'
26
+ require 'react/context_wrapper'
27
+ require 'react/component/native_component_constructor'
28
+ require 'react/component/native_component_validate_prop'
29
+ require 'react/component/props'
30
+ require 'react/component/state'
31
+ require 'react/component/match'
32
+ require 'react/component/location'
33
+ require 'react/component/history'
34
+ require 'react/component/elements'
35
+ require 'react/component/resolution'
36
+ require 'react/component/should_component_update'
37
+ require 'react/component/event_handler'
38
+ require 'react/component/mixin'
39
+ require 'react/component/base'
40
+ # React::PureComponent
41
+ require 'react/pure_component/mixin'
42
+ require 'react/pure_component/base'
43
+ # Function Component
44
+ require 'react/function_component/resolution'
45
+ require 'react/function_component/runner'
46
+ require 'react/function_component/creator'
47
+ # Redux::Component
48
+ require 'react/redux_component/component_class_store_defaults'
49
+ require 'react/redux_component/component_instance_store_defaults'
50
+ require 'react/redux_component/app_store_defaults'
51
+ require 'react/redux_component/api'
52
+ require 'react/redux_component/initializer'
53
+ require 'react/redux_component/app_store_proxy'
54
+ require 'react/redux_component/class_store_proxy'
55
+ require 'react/redux_component/instance_store_proxy'
56
+ require 'react/redux_component/native_component_constructor'
57
+ require 'react/redux_component/mixin'
58
+ require 'react/redux_component/base'
59
+ require 'react/redux_component/reducers'
60
+ require 'isomorfeus/config'
61
+
62
+ # init component reducers
63
+ React::ReduxComponent::Reducers.add_component_reducers_to_store
64
+
65
+ # init LucidApplicationContext (Store Provider and Consumer)
66
+ require 'lucid_app/context'
67
+
68
+ LucidApp::Context.create_application_context
69
+
70
+ # LucidComponent
71
+ require 'lucid_component/api'
72
+ require 'lucid_component/initializer'
73
+ require 'lucid_component/native_component_constructor'
74
+ require 'lucid_component/event_handler'
75
+ require 'lucid_component/mixin'
76
+ require 'lucid_component/base'
77
+
78
+ # LucidApp
79
+ require 'lucid_app/api'
80
+ require 'lucid_app/native_component_constructor'
81
+ require 'lucid_app/mixin'
82
+ require 'lucid_app/base'
83
+
84
+ # allow mounting of components
85
+ require 'isomorfeus/top_level'
86
+
87
+ # initalize Store, options, etc.
88
+ Isomorfeus.init
89
+ else
90
+ require 'opal'
91
+ require 'opal-activesupport'
92
+ require 'opal-browser'
93
+ require 'isomorfeus-redux'
94
+ require 'react/version'
95
+ require 'isomorfeus/config'
96
+ require 'isomorfeus/view_helpers'
97
+
98
+ Opal.append_path(__dir__.untaint)
99
+
100
+ if defined?(Rails)
101
+ module Isomorfeus
102
+ module Model
103
+ class Railtie < Rails::Railtie
104
+ def delete_first(a, e)
105
+ a.delete_at(a.index(e) || a.length)
106
+ end
107
+
108
+ config.before_configuration do |_|
109
+ Rails.configuration.tap do |config|
110
+ # rails will add everything immediately below app to eager and auto load, so we need to remove it
111
+ delete_first config.eager_load_paths, "#{config.root}/app/isomorfeus"
112
+
113
+ unless Rails.env.production?
114
+ # rails will add everything immediately below app to eager and auto load, so we need to remove it
115
+ delete_first config.autoload_paths, "#{config.root}/app/isomorfeus"
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ if Dir.exist?(File.join('app', 'isomorfeus'))
125
+ # Opal.append_path(File.expand_path(File.join('app', 'isomorfeus', 'components')))
126
+ Opal.append_path(File.expand_path(File.join('app', 'isomorfeus'))) unless Opal.paths.include?(File.expand_path(File.join('app', 'isomorfeus')))
127
+ elsif Dir.exist?('isomorfeus')
128
+ # Opal.append_path(File.expand_path(File.join('isomorfeus', 'components')))
129
+ Opal.append_path(File.expand_path('isomorfeus')) unless Opal.paths.include?(File.expand_path('isomorfeus'))
130
+ end
131
+ end
@@ -0,0 +1,84 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ module Isomorfeus
3
+ class << self
4
+ attr_reader :options
5
+ attr_reader :initialized
6
+ attr_reader :store
7
+
8
+ def init
9
+ return if initialized
10
+ @initialized = true
11
+ # at least one reducer must have been added at this stage
12
+ # this happened in isomorfeus-react.rb, where the component reducers were added
13
+ @store = Redux::Store.init!
14
+ `Opal.Isomorfeus.store = #@store`
15
+ init_options
16
+ execute_init_classes
17
+ end
18
+
19
+ def init_options
20
+ return @options if @options
21
+ @options = Hash.new(`Opal.IsomorfeusOptions`)
22
+ @options.keys.each do |option|
23
+ define_singleton_method(option) do
24
+ @options[option]
25
+ end
26
+ end
27
+ end
28
+
29
+ def execute_init_classes
30
+ if options.has_key?(:client_init_class_names)
31
+ client_init_class_names.each do |constant|
32
+ constant.constantize.send(:init)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ else
39
+ module Isomorfeus
40
+ class << self
41
+ attr_accessor :client_init_class_names
42
+ attr_accessor :prerendering
43
+ attr_accessor :version
44
+
45
+ def add_client_option(option)
46
+ @options_for_client ||= Set.new
47
+ @options_for_client << option
48
+ end
49
+
50
+ def add_client_options(options)
51
+ options.each do |option|
52
+ add_client_option(option)
53
+ end
54
+ end
55
+
56
+ def add_client_init_class_name(class_name)
57
+ client_init_class_names << class_name
58
+ end
59
+
60
+ def configuration(&block)
61
+ block.call(self)
62
+ end
63
+
64
+ def options_for_client
65
+ @options_for_client
66
+ end
67
+
68
+ def options_hash_for_client
69
+ opts = {}
70
+ Isomorfeus.options_for_client.each do |option|
71
+ opts[option] = Isomorfeus.send(option)
72
+ end
73
+ opts
74
+ end
75
+ end
76
+
77
+ self.add_client_option(:client_init_class_names)
78
+ self.add_client_option(:version)
79
+
80
+ self.client_init_class_names = []
81
+ self.prerendering = :off
82
+ # self.version = ::Isomorfeus::Component::VERSION # thats equal to the isomorfeus version
83
+ end
84
+ end
@@ -0,0 +1,48 @@
1
+ module Isomorfeus
2
+ class TopLevel
3
+ def self.search_path
4
+ @search_path ||= [Object]
5
+ end
6
+
7
+ def self.on_ready_mount(component, params = nil, element_query = nil)
8
+ # init in case it hasn't been run yet
9
+ Isomorfeus.init
10
+ # this looks a bit odd but works across _all_ browsers, and no event handler mess
11
+ # TODO: server rendering
12
+ %x{
13
+ function do_the_mount() { #{mount(component, params, element_query)} };
14
+ function ready_fun() {
15
+ /in/.test(document.readyState) ? setTimeout(ready_fun,5) : do_the_mount();
16
+ };
17
+ ready_fun();
18
+ }
19
+ end
20
+
21
+ def self.mount(component, params = nil, element_or_query = nil)
22
+ # TODO: server rendering
23
+ if element_or_query.nil?
24
+ if params.nil?
25
+ element = `document.body.querySelector('div')`
26
+ elsif params.class == String
27
+ element = `document.body.querySelector(params)`
28
+ params = nil
29
+ elsif params.is_a?(Browser::DOM::Node)
30
+ element = params.to_n
31
+ params = nil
32
+ end
33
+ else
34
+ if element_or_query.class == String
35
+ element = `document.body.querySelector(params)`
36
+ elsif element_or_query.is_a?(Browser::DOM::Node)
37
+ element = params.to_n
38
+ end
39
+ end
40
+
41
+ ReactDOM.render(React.create_element(component, params), element)
42
+ end
43
+
44
+ def self.ujs_mount
45
+ # TODO: implement mount using RailsUJS, for turbolinks and things
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ module Isomorfeus
2
+ module ViewHelpers
3
+ def isomorfeus_script_tag(options = {})
4
+ # client side used options:
5
+ # current_user_id
6
+ # session_id
7
+ # form_authenticity_token
8
+ options_hash = Isomorfeus.options_hash_for_client
9
+ options_hash.merge!(options)
10
+ tag = <<~SCRIPT
11
+ <script type="text/javascript">
12
+ Opal.IsomorfeusOptions = #{options_hash.to_json};
13
+ Opal.Isomorfeus.$init();
14
+ </script>
15
+ SCRIPT
16
+ tag.respond_to?(:html_safe) ? tag.html_safe : tag
17
+ end
18
+
19
+ def react_component(component_name, params)
20
+ component_name_id = component_id_name(component_name)
21
+ tag = <<~SCRIPT
22
+ <div id="#{component_name_id}"></div>
23
+ <script type="text/javascript">
24
+ var component = Opal.Object.$const_get("#{component_name}");
25
+ var json_params = #{Oj.dump(params, mode: :compat)};
26
+ Opal.Isomorfeus.$const_get('TopLevel').$mount(component, Opal.Hash.$new(json_params), "##{component_name_id}" );
27
+ </script>
28
+ SCRIPT
29
+ tag.respond_to?(:html_safe) ? tag.html_safe : tag
30
+ end
31
+
32
+ private
33
+
34
+ def component_id_name(component_name)
35
+ "#{component_name.underscore}_#{Random.rand.to_s[2..-1]}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ module LucidApp
2
+ module API
3
+ def self.included(base)
4
+ base.instance_exec do
5
+ def render(&block)
6
+ %x{
7
+ self.react_component.prototype.render = function() {
8
+ Opal.React.render_buffer.push([]);
9
+ Opal.React.active_components.push(this);
10
+ Opal.React.active_redux_components.push(this.__ruby_instance);
11
+ #{`this.__ruby_instance`.instance_exec(&block)};
12
+ Opal.React.active_redux_components.pop();
13
+ Opal.React.active_components.pop();
14
+ var children = Opal.React.render_buffer.pop();
15
+ return React.createElement(LucidApplicationContext.Provider, { value: this.state.isomorfeus_store_state }, children);
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module LucidApp
2
+ class Base
3
+ def self.inherited(base)
4
+ base.include(::LucidApp::Mixin)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module LucidApp
2
+ module Context
3
+ def self.create_application_context
4
+ React.create_context('LucidApplicationContext', Isomorfeus.store)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module LucidApp
2
+ module Mixin
3
+ def self.included(base)
4
+ base.include(::Native::Wrapper)
5
+ base.extend(::LucidApp::NativeComponentConstructor)
6
+ base.extend(::React::Component::NativeComponentValidateProp)
7
+ base.extend(::React::Component::ShouldComponentUpdate)
8
+ base.extend(::React::Component::EventHandler)
9
+ base.include(::React::Component::Elements)
10
+ base.include(::React::Component::API)
11
+ base.include(::React::ReduxComponent::API)
12
+ base.include(::LucidApp::API)
13
+ base.include(::React::Component::Features)
14
+ base.include(::React::Component::Resolution)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,70 @@
1
+ module LucidApp
2
+ module NativeComponentConstructor
3
+ # for should_component_update we apply ruby semantics for comparing props
4
+ # to do so, we convert the props to ruby hashes and then compare
5
+ # this makes sure, that for example rubys Nil object gets handled properly
6
+ def self.extended(base)
7
+ component_name = base.to_s
8
+ # language=JS
9
+ %x{
10
+ base.react_component = class extends React.Component {
11
+ constructor(props) {
12
+ super(props);
13
+ if (base.$default_state_defined()) {
14
+ this.state = base.$state().$to_n();
15
+ } else {
16
+ this.state = {};
17
+ };
18
+ this.state.isomorfeus_store_state = Opal.Isomorfeus.store.native.getState();
19
+ var current_store_state = this.state.isomorfeus_store_state;
20
+ if (typeof current_store_state.component_class_state[#{component_name}] !== "undefined") {
21
+ this.state.component_class_state = {};
22
+ this.state.component_class_state[#{component_name}] = current_store_state.component_class_state[#{component_name}];
23
+ } else {
24
+ this.state.component_class_state = {};
25
+ this.state.component_class_state[#{component_name}] = {};
26
+ };
27
+ this.__ruby_instance = base.$new(this);
28
+ this.__object_id = this.__ruby_instance.$object_id().$to_s();
29
+ if (!this.state.component_state) {
30
+ this.state.component_state = {};
31
+ this.state.component_state[this.__object_id] = {};
32
+ };
33
+ var event_handlers = #{base.event_handlers};
34
+ for (var i = 0; i < event_handlers.length; i++) {
35
+ this[event_handlers[i]] = this[event_handlers[i]].bind(this);
36
+ }
37
+ var defined_refs = #{base.defined_refs};
38
+ for (var ref in defined_refs) {
39
+ if (defined_refs[ref] != null) {
40
+ this[ref] = function(element) {
41
+ #{`this.__ruby_instance`.instance_exec(React::Ref.new(`element`), `defined_refs[ref]`)}
42
+ }
43
+ this[ref] = this[ref].bind(this);
44
+ } else {
45
+ this[ref] = React.createRef();
46
+ }
47
+ }
48
+ this.listener = this.listener.bind(this);
49
+ this.unsubscriber = Opal.Isomorfeus.store.native.subscribe(this.listener);
50
+ }
51
+ static get displayName() {
52
+ return #{component_name};
53
+ }
54
+ listener() {
55
+ var next_state = Opal.Isomorfeus.store.native.getState();
56
+ var current_ruby_state = Opal.Hash.$new(this.state.isomorfeus_store_state);
57
+ var next_ruby_state = Opal.Hash.$new(next_state);
58
+ if (#{`next_ruby_state` != `current_ruby_state`}) {
59
+ this.setState({isomorfeus_store_state: next_state});
60
+ }
61
+ }
62
+ componentWillUnmount() {
63
+ if (typeof this.unsubscriber === "function") { this.unsubscriber(); };
64
+ }
65
+ }
66
+ }
67
+ end
68
+ end
69
+ end
70
+