hyper-component 0.99.6 → 1.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -3
  3. data/Gemfile +4 -3
  4. data/Gemfile.lock +51 -36
  5. data/{misc/how-component-name-lookup-works.md → how-component-name-lookup-works.md} +1 -1
  6. data/hyper-component.gemspec +9 -8
  7. data/lib/hyper-component.rb +31 -43
  8. data/lib/hyperstack/component.rb +145 -0
  9. data/lib/hyperstack/component/auto-import.rb +44 -0
  10. data/lib/hyperstack/component/children.rb +40 -0
  11. data/lib/hyperstack/component/element.rb +129 -0
  12. data/lib/hyperstack/component/event.rb +78 -0
  13. data/lib/hyperstack/component/haml.rb +18 -0
  14. data/lib/hyperstack/component/isomorphic_helpers.rb +235 -0
  15. data/lib/hyperstack/component/jquery.rb +2 -0
  16. data/lib/hyperstack/component/native_library.rb +92 -0
  17. data/lib/hyperstack/component/react_api.rb +142 -0
  18. data/lib/hyperstack/component/server.rb +21 -0
  19. data/lib/hyperstack/component/version.rb +5 -0
  20. data/lib/hyperstack/ext/component/boolean.rb +14 -0
  21. data/lib/{react/ext/opal-jquery → hyperstack/ext/component}/element.rb +17 -12
  22. data/lib/{react/ext → hyperstack/ext/component}/hash.rb +0 -0
  23. data/lib/{react/to_key.rb → hyperstack/ext/component/number.rb} +0 -12
  24. data/lib/hyperstack/ext/component/object.rb +32 -0
  25. data/lib/{reactive-ruby → hyperstack/ext/component}/serializers.rb +0 -0
  26. data/lib/{react/ext → hyperstack/ext/component}/string.rb +0 -0
  27. data/lib/hyperstack/internal/component.rb +16 -0
  28. data/lib/hyperstack/internal/component/class_methods.rb +212 -0
  29. data/lib/hyperstack/internal/component/haml.rb +56 -0
  30. data/lib/hyperstack/internal/component/instance_methods.rb +92 -0
  31. data/lib/hyperstack/internal/component/props_wrapper.rb +125 -0
  32. data/lib/hyperstack/internal/component/rails.rb +11 -0
  33. data/lib/hyperstack/internal/component/rails/component_loader.rb +49 -0
  34. data/lib/hyperstack/internal/component/rails/component_mount.rb +52 -0
  35. data/lib/{reactive-ruby → hyperstack/internal/component}/rails/controller_helper.rb +0 -0
  36. data/lib/hyperstack/internal/component/rails/railtie.rb +24 -0
  37. data/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +52 -0
  38. data/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +52 -0
  39. data/lib/hyperstack/internal/component/react_wrapper.rb +308 -0
  40. data/lib/hyperstack/internal/component/rendering_context.rb +165 -0
  41. data/lib/hyperstack/internal/component/should_component_update.rb +101 -0
  42. data/lib/hyperstack/internal/component/tags.rb +109 -0
  43. data/lib/hyperstack/internal/component/top_level_rails_component.rb +83 -0
  44. data/lib/hyperstack/internal/component/validator.rb +149 -0
  45. data/lib/react/react-source.rb +2 -2
  46. data/unmounting-objects.md +78 -0
  47. metadata +73 -85
  48. data/DOCS.md +0 -1515
  49. data/LICENSE +0 -19
  50. data/README.md +0 -49
  51. data/lib/hyper-component/jquery.rb +0 -2
  52. data/lib/rails-helpers/top_level_rails_component.rb +0 -79
  53. data/lib/react/api.rb +0 -272
  54. data/lib/react/callbacks.rb +0 -42
  55. data/lib/react/children.rb +0 -38
  56. data/lib/react/component.rb +0 -189
  57. data/lib/react/component/api.rb +0 -70
  58. data/lib/react/component/base.rb +0 -13
  59. data/lib/react/component/class_methods.rb +0 -175
  60. data/lib/react/component/dsl_instance_methods.rb +0 -23
  61. data/lib/react/component/params.rb +0 -6
  62. data/lib/react/component/props_wrapper.rb +0 -90
  63. data/lib/react/component/should_component_update.rb +0 -99
  64. data/lib/react/component/tags.rb +0 -116
  65. data/lib/react/config.rb +0 -5
  66. data/lib/react/element.rb +0 -167
  67. data/lib/react/event.rb +0 -76
  68. data/lib/react/native_library.rb +0 -87
  69. data/lib/react/object.rb +0 -15
  70. data/lib/react/ref_callback.rb +0 -31
  71. data/lib/react/rendering_context.rb +0 -149
  72. data/lib/react/server.rb +0 -19
  73. data/lib/react/state_wrapper.rb +0 -23
  74. data/lib/react/test.rb +0 -16
  75. data/lib/react/test/dsl.rb +0 -17
  76. data/lib/react/test/matchers/render_html_matcher.rb +0 -56
  77. data/lib/react/test/rspec.rb +0 -15
  78. data/lib/react/test/session.rb +0 -37
  79. data/lib/react/test/utils.rb +0 -71
  80. data/lib/react/top_level.rb +0 -110
  81. data/lib/react/top_level_render.rb +0 -30
  82. data/lib/react/validator.rb +0 -132
  83. data/lib/reactive-ruby/component_loader.rb +0 -43
  84. data/lib/reactive-ruby/isomorphic_helpers.rb +0 -233
  85. data/lib/reactive-ruby/rails.rb +0 -8
  86. data/lib/reactive-ruby/rails/component_mount.rb +0 -48
  87. data/lib/reactive-ruby/rails/railtie.rb +0 -20
  88. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +0 -46
  89. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +0 -46
  90. data/lib/reactive-ruby/version.rb +0 -5
  91. data/lib/reactrb/auto-import.rb +0 -27
  92. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +0 -3
  93. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +0 -5
  94. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +0 -2
  95. data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +0 -6
  96. data/misc/generators/reactive_ruby/test_app/templates/script/rails +0 -5
  97. data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +0 -13
  98. data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +0 -11
  99. data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +0 -14
  100. data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  101. data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +0 -121
  102. data/misc/hyperloop-logo-small-pink.png +0 -0
  103. data/misc/logo1.png +0 -0
  104. data/misc/logo2.png +0 -0
  105. data/misc/logo3.png +0 -0
  106. data/path_release_steps.md +0 -9
@@ -1,15 +0,0 @@
1
- require 'react/test/dsl'
2
- require 'react/test/matchers/render_html_matcher'
3
-
4
- RSpec.configure do |config|
5
- config.include React::Test::DSL, type: :component
6
- config.include React::Test::Matchers, type: :component
7
-
8
- config.after do
9
- React::Test.reset_session!
10
- end
11
-
12
- config.before do
13
- # nothing yet
14
- end
15
- end
@@ -1,37 +0,0 @@
1
- module React
2
- module Test
3
- class Session
4
- DSL_METHODS = %i[mount instance update_params html].freeze
5
-
6
- def mount(component_klass, params = {})
7
- @element = React.create_element(component_klass, params)
8
- instance
9
- end
10
-
11
- def instance
12
- unless @instance
13
- @container = `document.createElement('div')`
14
- @instance = React.render(@element, @container)
15
- end
16
- @instance
17
- end
18
-
19
- def update_params(params, &block)
20
- cloned_element = React::Element.new(`React.cloneElement(#{@element.to_n}, #{params.to_n})`)
21
- React.render(cloned_element, @container, &block)
22
- nil
23
- end
24
-
25
- def html
26
- html = `#@container.innerHTML`
27
- %x{
28
- var REGEX_REMOVE_ROOT_IDS = /\s?data-reactroot="[^"]*"/g;
29
- var REGEX_REMOVE_IDS = /\s?data-reactid="[^"]+"/g;
30
- html = html.replace(REGEX_REMOVE_ROOT_IDS, '');
31
- html = html.replace(REGEX_REMOVE_IDS, '');
32
- }
33
- return html
34
- end
35
- end
36
- end
37
- end
@@ -1,71 +0,0 @@
1
- module React
2
- module Test
3
- class Utils
4
- def self.render_component_into_document(component, args = {})
5
- element = React.create_element(component, args)
6
- render_into_document(element)
7
- end
8
-
9
- def self.render_into_document(element)
10
- raise "You should pass a valid React::Element" unless React.is_valid_element?(element)
11
- dom_el = `document.body.querySelector('div[data-react-class="React.TopLevelRailsComponent"]').appendChild(document.createElement('div'))`
12
- React.render(element, dom_el)
13
- end
14
-
15
- def self.simulate_click(element)
16
- # element must be a component or a dom node or a element
17
- el = if `typeof element.nodeType !== "undefined"`
18
- element
19
- elsif element.respond_to? :dom_node
20
- element.dom_node
21
- elsif element.is_a? React::Element
22
- `ReactDOM.findDOMNode(#{element.to_n}.native)`
23
- else
24
- element
25
- end
26
- %x{
27
- var evob = new MouseEvent('click', {
28
- view: window,
29
- bubbles: true,
30
- cancelable: true
31
- });
32
- el.dispatchEvent(evob);
33
- }
34
- end
35
-
36
- def self.simulate_keydown(element, key_name = "Enter")
37
- # element must be a component or a dom node or a element
38
- el = if `typeof element.nodeType !== "undefined"`
39
- element
40
- elsif element.respond_to? :dom_node
41
- element.dom_node
42
- elsif element.is_a? React::Element
43
- `ReactDOM.findDOMNode(#{element.to_n}.native)`
44
- else
45
- element
46
- end
47
- %x{
48
- var evob = new KeyboardEvent('keydown', { key: key_name, bubbles: true, cancelable: true });
49
- el.dispatchEvent(evob);
50
- }
51
- end
52
-
53
- def self.simulate_submit(element)
54
- # element must be a component or a dom node or a element
55
- el = if `typeof element.nodeType !== "undefined"`
56
- element
57
- elsif element.respond_to? :dom_node
58
- element.dom_node
59
- elsif element.is_a? React::Element
60
- `ReactDOM.findDOMNode(#{element.to_n}.native)`
61
- else
62
- element
63
- end
64
- %x{
65
- var evob = new Event('submit', { bubbles: true, cancelable: true });
66
- el.dispatchEvent(evob);
67
- }
68
- end
69
- end
70
- end
71
- end
@@ -1,110 +0,0 @@
1
- require "native"
2
- require 'active_support/core_ext/object/try'
3
- require 'react/component/tags'
4
- require 'react/component/base'
5
-
6
- module React
7
-
8
- ATTRIBUTES = %w(accept acceptCharset accessKey action allowFullScreen allowTransparency alt
9
- async autoComplete autoPlay cellPadding cellSpacing charSet checked classID
10
- className cols colSpan content contentEditable contextMenu controls coords
11
- crossOrigin data dateTime defer dir disabled download draggable encType form
12
- formAction formEncType formMethod formNoValidate formTarget frameBorder height
13
- hidden href hrefLang htmlFor httpEquiv icon id label lang list loop manifest
14
- marginHeight marginWidth max maxLength media mediaGroup method min multiple
15
- muted name noValidate open pattern placeholder poster preload radioGroup
16
- readOnly rel required role rows rowSpan sandbox scope scrolling seamless
17
- selected shape size sizes span spellCheck src srcDoc srcSet start step style
18
- tabIndex target title type useMap value width wmode dangerouslySetInnerHTML) +
19
- #SVG ATTRIBUTES
20
- %w(clipPath cx cy d dx dy fill fillOpacity fontFamily
21
- fontSize fx fy gradientTransform gradientUnits markerEnd
22
- markerMid markerStart offset opacity patternContentUnits
23
- patternUnits points preserveAspectRatio r rx ry spreadMethod
24
- stopColor stopOpacity stroke strokeDasharray strokeLinecap
25
- strokeOpacity strokeWidth textAnchor transform version
26
- viewBox x1 x2 x xlinkActuate xlinkArcrole xlinkHref xlinkRole
27
- xlinkShow xlinkTitle xlinkType xmlBase xmlLang xmlSpace y1 y2 y)
28
- HASH_ATTRIBUTES = %w(data aria)
29
- HTML_TAGS = React::Component::Tags::HTML_TAGS
30
-
31
- def self.html_tag?(name)
32
- tags = HTML_TAGS
33
- %x{
34
- for(var i = 0; i < tags.length; i++) {
35
- if(tags[i] === name)
36
- return true;
37
- }
38
- return false;
39
- }
40
- end
41
-
42
- def self.html_attr?(name)
43
- attrs = ATTRIBUTES
44
- %x{
45
- for(var i = 0; i < attrs.length; i++) {
46
- if(attrs[i] === name)
47
- return true;
48
- }
49
- return false;
50
- }
51
- end
52
-
53
- def self.create_element(type, properties = {}, &block)
54
- React::API.create_element(type, properties, &block)
55
- end
56
-
57
- def self.render(element, container)
58
- %x{
59
- console.error(
60
- "Warning: Using deprecated behavior of `React.render`,",
61
- "require \"react/top_level_render\" to get the correct behavior."
62
- );
63
- }
64
- container = `container.$$class ? container[0] : container`
65
- if !(`typeof ReactDOM === 'undefined'`)
66
- component = Native(`ReactDOM.render(#{element.to_n}, container, function(){#{yield if block_given?}})`) # v0.15+
67
- else
68
- raise "render is not defined. In React >= v15 you must import it with ReactDOM"
69
- end
70
-
71
- component.class.include(React::Component::API)
72
- component
73
- end
74
-
75
- def self.is_valid_element(element)
76
- %x{ console.error("Warning: `is_valid_element` is deprecated in favor of `is_valid_element?`."); }
77
- element.kind_of?(React::Element) && `React.isValidElement(#{element.to_n})`
78
- end
79
-
80
- def self.is_valid_element?(element)
81
- element.kind_of?(React::Element) && `React.isValidElement(#{element.to_n})`
82
- end
83
-
84
- def self.render_to_string(element)
85
- %x{ console.error("Warning: `React.render_to_string` is deprecated in favor of `React::Server.render_to_string`."); }
86
- if !(`typeof ReactDOMServer === 'undefined'`)
87
- React::RenderingContext.build { `ReactDOMServer.renderToString(#{element.to_n})` } # v0.15+
88
- else
89
- raise "renderToString is not defined. In React >= v15 you must import it with ReactDOMServer"
90
- end
91
- end
92
-
93
- def self.render_to_static_markup(element)
94
- %x{ console.error("Warning: `React.render_to_static_markup` is deprecated in favor of `React::Server.render_to_static_markup`."); }
95
- if !(`typeof ReactDOMServer === 'undefined'`)
96
- React::RenderingContext.build { `ReactDOMServer.renderToStaticMarkup(#{element.to_n})` } # v0.15+
97
- else
98
- raise "renderToStaticMarkup is not defined. In React >= v15 you must import it with ReactDOMServer"
99
- end
100
- end
101
-
102
- def self.unmount_component_at_node(node)
103
- if !(`typeof ReactDOM === 'undefined'`)
104
- `ReactDOM.unmountComponentAtNode(node.$$class ? node[0] : node)` # v0.15+
105
- else
106
- raise "unmountComponentAtNode is not defined. In React >= v15 you must import it with ReactDOM"
107
- end
108
- end
109
-
110
- end
@@ -1,30 +0,0 @@
1
- module React
2
- def self.render(element, container)
3
- raise "ReactDOM.render is not defined. In React >= v15 you must import it with ReactDOM" if (`typeof ReactDOM === 'undefined'`)
4
-
5
- container = `container.$$class ? container[0] : container`
6
-
7
- if block_given?
8
- cb = %x{
9
- function(){
10
- setTimeout(function(){
11
- #{yield}
12
- }, 0)
13
- }
14
- }
15
- native = `ReactDOM.render(#{element.to_n}, container, cb)`
16
- else
17
- native = `ReactDOM.render(#{element.to_n}, container)`
18
- end
19
-
20
- return unless `#{native} !== null`
21
-
22
- if `#{native}.__opalInstance !== undefined && #{native}.__opalInstance !== null`
23
- `#{native}.__opalInstance`
24
- elsif `ReactDOM.findDOMNode !== undefined && #{native}.nodeType === undefined`
25
- `ReactDOM.findDOMNode(#{native})`
26
- else
27
- native
28
- end
29
- end
30
- end
@@ -1,132 +0,0 @@
1
- module React
2
- class Validator
3
- attr_accessor :errors
4
- attr_reader :props_wrapper
5
- private :errors, :props_wrapper
6
-
7
- def initialize(props_wrapper = Class.new(Component::PropsWrapper))
8
- @props_wrapper = props_wrapper
9
- end
10
-
11
- def self.build(&block)
12
- self.new.build(&block)
13
- end
14
-
15
- def build(&block)
16
- instance_eval(&block)
17
- self
18
- end
19
-
20
- def requires(name, options = {})
21
- options[:required] = true
22
- define_rule(name, options)
23
- end
24
-
25
- def optional(name, options = {})
26
- options[:required] = false
27
- define_rule(name, options)
28
- end
29
-
30
- def all_other_params(name)
31
- @allow_undefined_props = true
32
- props_wrapper.define_all_others(name) { |props| props.reject { |name, value| rules[name] } }
33
- end
34
-
35
- def validate(props)
36
- self.errors = []
37
- validate_undefined(props) unless allow_undefined_props?
38
- props = coerce_native_hash_values(defined_props(props))
39
- validate_required(props)
40
- props.each do |name, value|
41
- validate_types(name, value)
42
- validate_allowed(name, value)
43
- end
44
- errors
45
- end
46
-
47
- def default_props
48
- rules
49
- .select {|key, value| value.keys.include?("default") }
50
- .inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
51
- end
52
-
53
- private
54
-
55
- def defined_props(props)
56
- props.select { |name| rules.keys.include?(name) }
57
- end
58
-
59
- def allow_undefined_props?
60
- !!@allow_undefined_props
61
- end
62
-
63
- def rules
64
- @rules ||= { children: { required: false } }
65
- end
66
-
67
- def define_rule(name, options = {})
68
- rules[name] = coerce_native_hash_values(options)
69
- props_wrapper.define_param(name, options[:type])
70
- end
71
-
72
- def errors
73
- @errors ||= []
74
- end
75
-
76
- def validate_types(prop_name, value)
77
- return unless klass = rules[prop_name][:type]
78
- if !klass.is_a?(Array)
79
- allow_nil = !!rules[prop_name][:allow_nil]
80
- type_check("`#{prop_name}`", value, klass, allow_nil)
81
- elsif klass.length > 0
82
- validate_value_array(prop_name, value)
83
- else
84
- allow_nil = !!rules[prop_name][:allow_nil]
85
- type_check("`#{prop_name}`", value, Array, allow_nil)
86
- end
87
- end
88
-
89
- def type_check(prop_name, value, klass, allow_nil)
90
- return if allow_nil && value.nil?
91
- return if value.is_a?(klass)
92
- return if klass.respond_to?(:_react_param_conversion) &&
93
- klass._react_param_conversion(value, :validate_only)
94
- errors << "Provided prop #{prop_name} could not be converted to #{klass}"
95
- end
96
-
97
- def validate_allowed(prop_name, value)
98
- return unless values = rules[prop_name][:values]
99
- return if values.include?(value)
100
- errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value"
101
- end
102
-
103
- def validate_required(props)
104
- (rules.keys - props.keys).each do |name|
105
- next unless rules[name][:required]
106
- errors << "Required prop `#{name}` was not specified"
107
- end
108
- end
109
-
110
- def validate_undefined(props)
111
- (props.keys - rules.keys).each do |prop_name|
112
- errors << "Provided prop `#{prop_name}` not specified in spec"
113
- end
114
- end
115
-
116
- def validate_value_array(name, value)
117
- klass = rules[name][:type]
118
- allow_nil = !!rules[name][:allow_nil]
119
- value.each_with_index do |item, index|
120
- type_check("`#{name}`[#{index}]", Native(item), klass[0], allow_nil)
121
- end
122
- rescue NoMethodError
123
- errors << "Provided prop `#{name}` was not an Array"
124
- end
125
-
126
- def coerce_native_hash_values(hash)
127
- hash.each do |key, value|
128
- hash[key] = Native(value)
129
- end
130
- end
131
- end
132
- end
@@ -1,43 +0,0 @@
1
- module ReactiveRuby
2
- class ComponentLoader
3
- attr_reader :v8_context
4
- private :v8_context
5
-
6
- def initialize(v8_context)
7
- unless v8_context
8
- raise ArgumentError.new('Could not obtain ExecJS runtime context')
9
- end
10
- @v8_context = v8_context
11
- end
12
-
13
- def load(file = components)
14
- return true if loaded?
15
- !!v8_context.eval(opal(file))
16
- end
17
-
18
- def load!(file = components)
19
- return true if loaded?
20
- self.load(file)
21
- ensure
22
- raise "No HyperReact components found in #{components}" unless loaded?
23
- end
24
-
25
- def loaded?
26
- !!v8_context.eval('Opal.React !== undefined')
27
- rescue ::ExecJS::Error
28
- false
29
- end
30
-
31
- private
32
-
33
- def components
34
- opts = ::Rails.configuration.react.server_renderer_options
35
- return opts[:files].first.gsub(/.js$/,'') if opts && opts[:files]
36
- 'components'
37
- end
38
-
39
- def opal(file)
40
- Opal::Sprockets.load_asset(file)
41
- end
42
- end
43
- end
@@ -1,233 +0,0 @@
1
- require "react/config"
2
-
3
- module React
4
- module IsomorphicHelpers
5
- def self.included(base)
6
- base.extend(ClassMethods)
7
- end
8
-
9
- if RUBY_ENGINE != 'opal'
10
- def self.load_context(ctx, controller, name = nil)
11
- @context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller, name)
12
- @context.load_opal_context
13
- ::Rails.logger.debug "************************** React Server Context Initialized #{name} #{Time.now.to_f} *********************************************"
14
- @context
15
- end
16
- else
17
- def self.load_context(unique_id = nil, name = nil)
18
- # can be called on the client to force re-initialization for testing purposes
19
- if !unique_id || !@context || @context.unique_id != unique_id
20
- if on_opal_server?
21
- `console.history = []` rescue nil
22
- message = "************************ React Prerendering Context Initialized #{name} ***********************"
23
- else
24
- message = "************************ React Browser Context Initialized ****************************"
25
- end
26
- log(message)
27
- @context = Context.new(unique_id)
28
- end
29
- @context
30
- end
31
- end
32
-
33
- def self.context
34
- @context
35
- end
36
-
37
- def self.log(message, message_type = :info)
38
- message = [message] unless message.is_a? Array
39
-
40
- if (message_type == :info || message_type == :warning) && Hyperloop.env.production?
41
- return
42
- end
43
-
44
- if message_type == :info
45
- if on_opal_server?
46
- style = 'background: #00FFFF; color: red'
47
- else
48
- style = 'background: #222; color: #bada55'
49
- end
50
- message = ["%c" + message[0], style]+message[1..-1]
51
- `console.log.apply(console, message)`
52
- elsif message_type == :warning
53
- `console.warn.apply(console, message)`
54
- else
55
- `console.error.apply(console, message)`
56
- end
57
- end
58
-
59
- if RUBY_ENGINE != 'opal'
60
- def self.on_opal_server?
61
- false
62
- end
63
-
64
- def self.on_opal_client?
65
- false
66
- end
67
- else
68
- def self.on_opal_server?
69
- `typeof Opal.global.document === 'undefined'`
70
- end
71
-
72
- def self.on_opal_client?
73
- !on_opal_server?
74
- end
75
- end
76
-
77
- def log(*args)
78
- IsomorphicHelpers.log(*args)
79
- end
80
-
81
- def on_opal_server?
82
- self.class.on_opal_server?
83
- end
84
-
85
- def on_opal_client?
86
- self.class.on_opal_client?
87
- end
88
-
89
- def self.prerender_footers(controller = nil)
90
- footer = Context.prerender_footer_blocks.collect { |block| block.call controller }.join("\n")
91
- if RUBY_ENGINE != 'opal'
92
- footer = (footer + @context.send_to_opal(:prerender_footers).to_s) if @context
93
- footer = footer.html_safe
94
- end
95
- footer
96
- end
97
-
98
- class Context
99
- attr_reader :controller
100
- attr_reader :unique_id
101
-
102
- def self.define_isomorphic_method(method_name, &block)
103
- @@ctx_methods ||= {}
104
- @@ctx_methods[method_name] = block
105
- end
106
-
107
- def self.before_first_mount_blocks
108
- @before_first_mount_blocks ||= []
109
- end
110
-
111
- def self.prerender_footer_blocks
112
- @prerender_footer_blocks ||= []
113
- end
114
-
115
- def initialize(unique_id, ctx = nil, controller = nil, cname = nil)
116
- @unique_id = unique_id
117
- @cname = cname
118
- if RUBY_ENGINE != 'opal'
119
- @controller = controller
120
- @ctx = ctx
121
- if defined? @@ctx_methods
122
- @@ctx_methods.each do |method_name, block|
123
- @ctx.attach("ServerSideIsomorphicMethod.#{method_name}", proc{|args| block.call(args.to_json)})
124
- end
125
- end
126
- end
127
- Hyperloop::Application::Boot.run(context: self)
128
- self.class.before_first_mount_blocks.each { |block| block.call(self) }
129
- end
130
-
131
- def load_opal_context
132
- send_to_opal(:load_context, @unique_id, @cname)
133
- end
134
-
135
- def eval(js)
136
- @ctx.eval(js) if @ctx
137
- end
138
-
139
- def send_to_opal(method_name, *args)
140
- return unless @ctx
141
- args = [1] if args.length == 0
142
- ::ReactiveRuby::ComponentLoader.new(@ctx).load!
143
- method_args = args.collect do |arg|
144
- quarg = "#{arg}".tr('"', "'")
145
- "\"#{quarg}\""
146
- end.join(', ')
147
- @ctx.eval("Opal.React.$const_get('IsomorphicHelpers').$#{method_name}(#{method_args})")
148
- end
149
-
150
- def self.register_before_first_mount_block(&block)
151
- before_first_mount_blocks << block
152
- end
153
-
154
- def self.register_prerender_footer_block(&block)
155
- prerender_footer_blocks << block
156
- end
157
- end
158
-
159
- class IsomorphicProcCall
160
-
161
- attr_reader :context
162
-
163
- def result
164
- @result.first if @result
165
- end
166
-
167
- def initialize(name, block, context, *args)
168
- @name = name
169
- @context = context
170
- block.call(self, *args)
171
- @result ||= send_to_server(*args)
172
- end
173
-
174
- def when_on_client(&block)
175
- @result = [block.call] if IsomorphicHelpers.on_opal_client?
176
- end
177
-
178
- def send_to_server(*args)
179
- if IsomorphicHelpers.on_opal_server?
180
- method_string = "ServerSideIsomorphicMethod." + @name + "(" + args.to_json + ")"
181
- @result = [JSON.parse(`eval(method_string)`)]
182
- end
183
- end
184
-
185
- def when_on_server(&block)
186
- @result = [block.call.to_json] unless IsomorphicHelpers.on_opal_client? || IsomorphicHelpers.on_opal_server?
187
- end
188
- end
189
-
190
- module ClassMethods
191
- def on_opal_server?
192
- IsomorphicHelpers.on_opal_server?
193
- end
194
-
195
- def on_opal_client?
196
- IsomorphicHelpers.on_opal_client?
197
- end
198
-
199
- def log(*args)
200
- IsomorphicHelpers.log(*args)
201
- end
202
-
203
- def controller
204
- IsomorphicHelpers.context.controller
205
- end
206
-
207
- def before_first_mount(&block)
208
- React::IsomorphicHelpers::Context.register_before_first_mount_block(&block)
209
- end
210
-
211
- def prerender_footer(&block)
212
- React::IsomorphicHelpers::Context.register_prerender_footer_block(&block)
213
- end
214
-
215
- if RUBY_ENGINE != 'opal'
216
- def isomorphic_method(name, &block)
217
- React::IsomorphicHelpers::Context.send(:define_isomorphic_method, name) do |args_as_json|
218
- React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, self, *JSON.parse(args_as_json)).result
219
- end
220
- end
221
- else
222
- require 'json'
223
-
224
- def isomorphic_method(name, &block)
225
- self.class.send(:define_method, name) do | *args |
226
- React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, self, *args).result
227
- end
228
- end
229
- end
230
-
231
- end
232
- end
233
- end