hyper-react 0.10.0
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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +27 -0
- data/.gitignore +36 -0
- data/.rubocop.yml +1159 -0
- data/.travis.yml +29 -0
- data/Appraisals +20 -0
- data/CHANGELOG.md +93 -0
- data/Gemfile +6 -0
- data/LICENSE +19 -0
- data/README.md +121 -0
- data/Rakefile +33 -0
- data/UPGRADING.md +24 -0
- data/component-name-lookup.md +145 -0
- data/config.ru +25 -0
- data/gemfiles/opal_0.8_react_13.gemfile +13 -0
- data/gemfiles/opal_0.8_react_14.gemfile +13 -0
- data/gemfiles/opal_0.8_react_15.gemfile +13 -0
- data/gemfiles/opal_0.9_react_13.gemfile +13 -0
- data/gemfiles/opal_0.9_react_14.gemfile +13 -0
- data/gemfiles/opal_0.9_react_15.gemfile +13 -0
- data/hyper-react.gemspec +43 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +109 -0
- data/lib/hyper-react.rb +52 -0
- data/lib/rails-helpers/top_level_rails_component.rb +54 -0
- data/lib/react-sources/react-server.js +2 -0
- data/lib/react/api.rb +162 -0
- data/lib/react/callbacks.rb +42 -0
- data/lib/react/children.rb +30 -0
- data/lib/react/component.rb +139 -0
- data/lib/react/component/api.rb +50 -0
- data/lib/react/component/base.rb +9 -0
- data/lib/react/component/class_methods.rb +214 -0
- data/lib/react/component/dsl_instance_methods.rb +27 -0
- data/lib/react/component/params.rb +6 -0
- data/lib/react/component/props_wrapper.rb +83 -0
- data/lib/react/component/should_component_update.rb +98 -0
- data/lib/react/component/tags.rb +144 -0
- data/lib/react/element.rb +168 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/hash.rb +13 -0
- data/lib/react/native_library.rb +92 -0
- data/lib/react/object.rb +15 -0
- data/lib/react/observable.rb +29 -0
- data/lib/react/react-source.rb +9 -0
- data/lib/react/rendering_context.rb +142 -0
- data/lib/react/state.rb +190 -0
- data/lib/react/test.rb +16 -0
- data/lib/react/test/dsl.rb +17 -0
- data/lib/react/test/matchers/render_html_matcher.rb +49 -0
- data/lib/react/test/rspec.rb +15 -0
- data/lib/react/test/session.rb +46 -0
- data/lib/react/top_level.rb +132 -0
- data/lib/react/validator.rb +136 -0
- data/lib/reactive-ruby/component_loader.rb +49 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +197 -0
- data/lib/reactive-ruby/rails.rb +7 -0
- data/lib/reactive-ruby/rails/component_mount.rb +46 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +15 -0
- data/lib/reactive-ruby/rails/railtie.rb +14 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/lib/reactrb/auto-import.rb +32 -0
- data/lib/reactrb/deep-compare.rb +24 -0
- data/lib/reactrb/new-event-name-convention.rb +11 -0
- data/lib/sources/react-latest.js +21169 -0
- data/lib/sources/react-v13.js +21645 -0
- data/lib/sources/react-v14.js +20821 -0
- data/lib/sources/react-v15.js +21170 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- data/spec/controller_helper_spec.rb +34 -0
- data/spec/index.html.erb +10 -0
- data/spec/react/callbacks_spec.rb +106 -0
- data/spec/react/children_spec.rb +76 -0
- data/spec/react/component/base_spec.rb +32 -0
- data/spec/react/component_spec.rb +872 -0
- data/spec/react/dsl_spec.rb +296 -0
- data/spec/react/element_spec.rb +136 -0
- data/spec/react/event_spec.rb +24 -0
- data/spec/react/native_library_spec.rb +344 -0
- data/spec/react/observable_spec.rb +7 -0
- data/spec/react/opal_jquery_extensions_spec.rb +66 -0
- data/spec/react/param_declaration_spec.rb +258 -0
- data/spec/react/react_spec.rb +209 -0
- data/spec/react/state_spec.rb +55 -0
- data/spec/react/test/dsl_spec.rb +43 -0
- data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
- data/spec/react/test/rspec_spec.rb +62 -0
- data/spec/react/test/session_spec.rb +100 -0
- data/spec/react/test/utils_spec.rb +45 -0
- data/spec/react/top_level_component_spec.rb +96 -0
- data/spec/react/tutorial/tutorial_spec.rb +36 -0
- data/spec/react/validator_spec.rb +124 -0
- data/spec/reactive-ruby/component_loader_spec.rb +71 -0
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
- data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +10 -0
- data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
- data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
- data/spec/spec_helper.rb +115 -0
- data/spec/support/react/spec_helpers.rb +64 -0
- data/spec/vendor/es5-shim.min.js +6 -0
- data/spec/vendor/jquery-2.2.4.min.js +4 -0
- metadata +387 -0
data/lib/react/event.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module React
|
|
2
|
+
class Event
|
|
3
|
+
include Native
|
|
4
|
+
alias_native :bubbles, :bubbles
|
|
5
|
+
alias_native :cancelable, :cancelable
|
|
6
|
+
alias_native :current_target, :currentTarget
|
|
7
|
+
alias_native :default_prevented, :defaultPrevented
|
|
8
|
+
alias_native :event_phase, :eventPhase
|
|
9
|
+
alias_native :is_trusted?, :isTrusted
|
|
10
|
+
alias_native :native_event, :nativeEvent
|
|
11
|
+
alias_native :target, :target
|
|
12
|
+
alias_native :timestamp, :timeStamp
|
|
13
|
+
alias_native :event_type, :type
|
|
14
|
+
alias_native :prevent_default, :preventDefault
|
|
15
|
+
alias_native :stop_propagation, :stopPropagation
|
|
16
|
+
# Clipboard
|
|
17
|
+
alias_native :clipboard_data, :clipboardData
|
|
18
|
+
# Keyboard
|
|
19
|
+
alias_native :alt_key, :altKey
|
|
20
|
+
alias_native :char_code, :charCode
|
|
21
|
+
alias_native :ctrl_key, :ctrlKey
|
|
22
|
+
alias_native :get_modifier_state, :getModifierState
|
|
23
|
+
alias_native :key, :key
|
|
24
|
+
alias_native :key_code, :keyCode
|
|
25
|
+
alias_native :locale, :locale
|
|
26
|
+
alias_native :location, :location
|
|
27
|
+
alias_native :meta_key, :metaKey
|
|
28
|
+
alias_native :repeat, :repeat
|
|
29
|
+
alias_native :shift_key, :shiftKey
|
|
30
|
+
alias_native :which, :which
|
|
31
|
+
# Focus
|
|
32
|
+
alias_native :related_target, :relatedTarget
|
|
33
|
+
# Mouse
|
|
34
|
+
alias_native :alt_key, :altKey
|
|
35
|
+
alias_native :button, :button
|
|
36
|
+
alias_native :buttons, :buttons
|
|
37
|
+
alias_native :client_x, :clientX
|
|
38
|
+
alias_native :client_y, :clientY
|
|
39
|
+
alias_native :ctrl_key, :ctrlKey
|
|
40
|
+
alias_native :get_modifier_state, :getModifierState
|
|
41
|
+
alias_native :meta_key, :metaKey
|
|
42
|
+
alias_native :page_x, :pageX
|
|
43
|
+
alias_native :page_y, :pageY
|
|
44
|
+
alias_native :related_target, :relatedTarget
|
|
45
|
+
alias_native :screen_x, :screen_x
|
|
46
|
+
alias_native :screen_y, :screen_y
|
|
47
|
+
alias_native :shift_key, :shift_key
|
|
48
|
+
# Touch
|
|
49
|
+
alias_native :alt_key, :altKey
|
|
50
|
+
alias_native :changed_touches, :changedTouches
|
|
51
|
+
alias_native :ctrl_key, :ctrlKey
|
|
52
|
+
alias_native :get_modifier_state, :getModifierState
|
|
53
|
+
alias_native :meta_key, :metaKey
|
|
54
|
+
alias_native :shift_key, :shiftKey
|
|
55
|
+
alias_native :target_touches, :targetTouches
|
|
56
|
+
alias_native :touches, :touches
|
|
57
|
+
# UI
|
|
58
|
+
alias_native :detail, :detail
|
|
59
|
+
alias_native :view, :view
|
|
60
|
+
# Wheel
|
|
61
|
+
alias_native :delta_mode, :deltaMode
|
|
62
|
+
alias_native :delta_x, :deltaX
|
|
63
|
+
alias_native :delta_y, :deltaY
|
|
64
|
+
alias_native :delta_z, :deltaZ
|
|
65
|
+
|
|
66
|
+
BUILT_IN_EVENTS = %w{onCopy onCut onPaste onKeyDown onKeyPress onKeyUp
|
|
67
|
+
onFocus onBlur onChange onInput onSubmit onClick onDoubleClick onDrag
|
|
68
|
+
onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop
|
|
69
|
+
onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver
|
|
70
|
+
onMouseUp onTouchCancel onTouchEnd onTouchMove onTouchStart onScroll}
|
|
71
|
+
|
|
72
|
+
def initialize(native_element)
|
|
73
|
+
@native = native_element
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/react/hash.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
|
|
3
|
+
alias_method :_pre_react_patch_initialize, :initialize
|
|
4
|
+
|
|
5
|
+
def initialize(defaults = undefined, &block)
|
|
6
|
+
if (`defaults===null`)
|
|
7
|
+
_pre_react_patch_initialize(&block)
|
|
8
|
+
else
|
|
9
|
+
_pre_react_patch_initialize(defaults, &block)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module React
|
|
2
|
+
# NativeLibrary handles importing JS libraries. Importing native components is handled
|
|
3
|
+
# by the React::Base. It also provides several methods used by auto-import.rb
|
|
4
|
+
|
|
5
|
+
# A NativeLibrary is simply a wrapper that holds the name of the native js library.
|
|
6
|
+
# It responds to const_missing and method_missing by looking up objects within the js library.
|
|
7
|
+
# If the object is a react component it is wrapped by a reactrb component class, otherwise
|
|
8
|
+
# a nested NativeLibrary is returned.
|
|
9
|
+
|
|
10
|
+
# Two macros are provided: imports (for naming the native library) and renames which allows
|
|
11
|
+
# the members of a library to be given different names within the ruby name space.
|
|
12
|
+
|
|
13
|
+
# Public methods used by auto-import.rb are import_const_from_native and find_and_render_component
|
|
14
|
+
class NativeLibrary
|
|
15
|
+
class << self
|
|
16
|
+
def imports(native_name)
|
|
17
|
+
@native_prefix = "#{native_name}."
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def rename(rename_list)
|
|
22
|
+
# rename_list is a hash in the form: native_name => ruby_name, native_name => ruby_name
|
|
23
|
+
rename_list.each do |js_name, ruby_name|
|
|
24
|
+
native_name = lookup_native_name(js_name)
|
|
25
|
+
if lookup_native_name(js_name)
|
|
26
|
+
create_component_wrapper(self, native_name, ruby_name) ||
|
|
27
|
+
create_library_wrapper(self, native_name, ruby_name)
|
|
28
|
+
else
|
|
29
|
+
raise "class #{name} < React::NativeLibrary could not import #{js_name}. "\
|
|
30
|
+
"Native value #{scope_native_name(js_name)} is undefined."
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def import_const_from_native(klass, const_name, create_library)
|
|
36
|
+
native_name = lookup_native_name(const_name) ||
|
|
37
|
+
lookup_native_name(const_name[0].downcase + const_name[1..-1])
|
|
38
|
+
native_name && (
|
|
39
|
+
create_component_wrapper(klass, native_name, const_name) || (
|
|
40
|
+
create_library &&
|
|
41
|
+
create_library_wrapper(klass, native_name, const_name)))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def const_missing(const_name)
|
|
45
|
+
import_const_from_native(self, const_name, true) || super
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def method_missing(method_name, *args, &block)
|
|
49
|
+
method = method_name.gsub(/_as_node$/, '') # remove once _as_node is deprecated.
|
|
50
|
+
component_class = get_const(method) if const_defined?(method)
|
|
51
|
+
component_class ||= import_const_from_native(self, method, false)
|
|
52
|
+
raise 'could not import a react component named: '\
|
|
53
|
+
"#{scope_native_name method}" unless component_class
|
|
54
|
+
if method == method_name
|
|
55
|
+
React::RenderingContext.render(component_class, *args, &block)
|
|
56
|
+
else # remove once _as_node is deprecated.
|
|
57
|
+
React::RenderingContext.build_only(component_class, *args, &block)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def lookup_native_name(js_name)
|
|
64
|
+
native_name = scope_native_name(js_name)
|
|
65
|
+
`eval(#{native_name}) !== undefined && native_name`
|
|
66
|
+
# rubocop:disable Lint/RescueException # that is what eval raises in Opal >= 0.10.
|
|
67
|
+
rescue Exception
|
|
68
|
+
nil
|
|
69
|
+
# rubocop:enable Lint/RescueException
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def scope_native_name(js_name)
|
|
73
|
+
"#{@native_prefix}#{js_name}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def create_component_wrapper(klass, native_name, ruby_name)
|
|
77
|
+
if React::API.native_react_component?(native_name)
|
|
78
|
+
new_klass = klass.const_set ruby_name, Class.new
|
|
79
|
+
new_klass.class_eval do
|
|
80
|
+
include React::Component
|
|
81
|
+
imports native_name
|
|
82
|
+
end
|
|
83
|
+
new_klass
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_library_wrapper(klass, native_name, ruby_name)
|
|
88
|
+
klass.const_set ruby_name, Class.new(React::NativeLibrary).imports(native_name)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/react/object.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Lazy load HTML tag constants in the form DIV or A
|
|
2
|
+
# This is needed to allow for a HAML expression like this DIV.my_class
|
|
3
|
+
class Object
|
|
4
|
+
class << self
|
|
5
|
+
alias _reactrb_tag_original_const_missing const_missing
|
|
6
|
+
|
|
7
|
+
def const_missing(const_name)
|
|
8
|
+
# Opal uses const_missing to initially define things,
|
|
9
|
+
# so we always call the original, and respond to the exception
|
|
10
|
+
_reactrb_tag_original_const_missing(const_name)
|
|
11
|
+
rescue StandardError => e
|
|
12
|
+
React::Component::Tags.html_tag_class_for(const_name) || raise(e)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module React
|
|
2
|
+
class Observable
|
|
3
|
+
def initialize(value, on_change = nil, &block)
|
|
4
|
+
@value = value
|
|
5
|
+
@on_change = on_change || block
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def method_missing(method_sym, *args, &block)
|
|
9
|
+
@value.send(method_sym, *args, &block).tap { |result| @on_change.call @value }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def respond_to?(method, *args)
|
|
13
|
+
if [:call, :to_proc].include? method
|
|
14
|
+
true
|
|
15
|
+
else
|
|
16
|
+
@value.respond_to? method, *args
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(new_value)
|
|
21
|
+
@on_change.call new_value
|
|
22
|
+
@value = new_value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_proc
|
|
26
|
+
lambda { |arg = @value| @on_change.call arg }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
if RUBY_ENGINE == 'opal'
|
|
2
|
+
require 'react.js'
|
|
3
|
+
require "react-server.js"
|
|
4
|
+
else
|
|
5
|
+
require "react/rails/asset_variant"
|
|
6
|
+
react_directory = React::Rails::AssetVariant.new(addons: true).react_directory
|
|
7
|
+
Opal.append_path react_directory.untaint
|
|
8
|
+
Opal.append_path File.expand_path('../../react-sources/', __FILE__).untaint
|
|
9
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
module React
|
|
2
|
+
class RenderingContext
|
|
3
|
+
class << self
|
|
4
|
+
attr_accessor :waiting_on_resources
|
|
5
|
+
|
|
6
|
+
def build_only(name, *args, &block)
|
|
7
|
+
React::Component.deprecation_warning(
|
|
8
|
+
'..._as_node is deprecated. Render component and then use the .node method instead'
|
|
9
|
+
)
|
|
10
|
+
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def render(name, *args, &block)
|
|
14
|
+
remove_nodes_from_args(args)
|
|
15
|
+
@buffer ||= [] unless @buffer
|
|
16
|
+
if block
|
|
17
|
+
element = build do
|
|
18
|
+
saved_waiting_on_resources = waiting_on_resources
|
|
19
|
+
self.waiting_on_resources = nil
|
|
20
|
+
run_child_block(name.nil?, &block)
|
|
21
|
+
if name
|
|
22
|
+
buffer = @buffer.dup
|
|
23
|
+
React.create_element(name, *args) { buffer }.tap do |element|
|
|
24
|
+
element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
|
|
25
|
+
end
|
|
26
|
+
elsif @buffer.last.is_a? React::Element
|
|
27
|
+
@buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
|
|
28
|
+
else
|
|
29
|
+
@buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
elsif name.is_a? React::Element
|
|
33
|
+
element = name
|
|
34
|
+
else
|
|
35
|
+
element = React.create_element(name, *args)
|
|
36
|
+
element.waiting_on_resources = waiting_on_resources
|
|
37
|
+
end
|
|
38
|
+
@buffer << element
|
|
39
|
+
self.waiting_on_resources = nil
|
|
40
|
+
element
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build
|
|
44
|
+
current = @buffer
|
|
45
|
+
@buffer = []
|
|
46
|
+
return_val = yield @buffer
|
|
47
|
+
@buffer = current
|
|
48
|
+
return_val
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def as_node(element)
|
|
52
|
+
@buffer.delete(element)
|
|
53
|
+
element
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
alias delete as_node
|
|
57
|
+
|
|
58
|
+
def rendered?(element)
|
|
59
|
+
@buffer.include? element
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def replace(e1, e2)
|
|
63
|
+
@buffer[@buffer.index(e1)] = e2
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def remove_nodes_from_args(args)
|
|
67
|
+
args[0].each do |key, value|
|
|
68
|
+
value.as_node if value.is_a?(Element) rescue nil
|
|
69
|
+
end if args[0] && args[0].is_a?(Hash)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# run_child_block gathers the element(s) generated by a child block.
|
|
73
|
+
# for example when rendering this div: div { "hello".span; "goodby".span }
|
|
74
|
+
# two child Elements will be generated.
|
|
75
|
+
#
|
|
76
|
+
# the final value of the block should either be
|
|
77
|
+
# 1 an object that responds to :acts_as_string?
|
|
78
|
+
# 2 a string,
|
|
79
|
+
# 3 an element that is NOT yet pushed on the rendering buffer
|
|
80
|
+
# 4 or the last element pushed on the buffer
|
|
81
|
+
#
|
|
82
|
+
# in case 1 we change the object to a string, and then it becomes case 2
|
|
83
|
+
# in case 2 we automatically push the string onto the buffer
|
|
84
|
+
# in case 3 we also push the Element onto the buffer IF the buffer is empty
|
|
85
|
+
# case 4 requires no special processing
|
|
86
|
+
#
|
|
87
|
+
# Once we have taken care of these special cases we do a check IF we are in an
|
|
88
|
+
# outer rendering scope. In this case react only allows us to generate 1 Element
|
|
89
|
+
# so we insure that is the case, and also check to make sure that element in the buffer
|
|
90
|
+
# is the element returned
|
|
91
|
+
|
|
92
|
+
def run_child_block(is_outer_scope)
|
|
93
|
+
result = yield
|
|
94
|
+
result = result.to_s if result.try :acts_as_string?
|
|
95
|
+
@buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
|
|
96
|
+
raise_render_error(result) if is_outer_scope && @buffer != [result]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# heurestically raise a meaningful error based on the situation
|
|
100
|
+
|
|
101
|
+
def raise_render_error(result)
|
|
102
|
+
improper_render 'A different element was returned than was generated within the DSL.',
|
|
103
|
+
'Possibly improper use of Element#delete.' if @buffer.count == 1
|
|
104
|
+
improper_render "Instead #{@buffer.count} elements were generated.",
|
|
105
|
+
'Do you want to wrap your elements in a div?' if @buffer.count > 1
|
|
106
|
+
improper_render "Instead the component #{result} was returned.",
|
|
107
|
+
"Did you mean #{result}()?" if result.try :reactrb_component?
|
|
108
|
+
improper_render "Instead the #{result.class} #{result} was returned.",
|
|
109
|
+
'You may need to convert this to a string.'
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def improper_render(message, solution)
|
|
113
|
+
raise "a component's render method must generate and return exactly 1 element or a string.\n"\
|
|
114
|
+
" #{message} #{solution}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class ::Object
|
|
120
|
+
[:span, :td, :th, :while_loading].each do |tag|
|
|
121
|
+
define_method(tag) do |*args, &block|
|
|
122
|
+
args.unshift(tag)
|
|
123
|
+
return send(*args, &block) if is_a? React::Component
|
|
124
|
+
React::RenderingContext.render(*args) { to_s }
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def para(*args, &block)
|
|
129
|
+
args.unshift(:p)
|
|
130
|
+
return send(*args, &block) if is_a? React::Component
|
|
131
|
+
React::RenderingContext.render(*args) { to_s }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def br
|
|
135
|
+
return send(:br) if is_a? React::Component
|
|
136
|
+
React::RenderingContext.render(:span) do
|
|
137
|
+
React::RenderingContext.render(to_s)
|
|
138
|
+
React::RenderingContext.render(:br)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
data/lib/react/state.rb
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
module React
|
|
2
|
+
class StateWrapper < BasicObject
|
|
3
|
+
def initialize(native, from)
|
|
4
|
+
@state_hash = Hash.new(`#{native}.state`)
|
|
5
|
+
@from = from
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def [](state)
|
|
9
|
+
@state_hash[state]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def []=(state, new_value)
|
|
13
|
+
@state_hash[state] = new_value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def method_missing(method, *args)
|
|
17
|
+
if match = method.match(/^(.+)\!$/)
|
|
18
|
+
if args.count > 0
|
|
19
|
+
current_value = State.get_state(@from, match[1])
|
|
20
|
+
State.set_state(@from, $1, args[0])
|
|
21
|
+
current_value
|
|
22
|
+
else
|
|
23
|
+
current_state = State.get_state(@from, match[1])
|
|
24
|
+
State.set_state(@from, $1, current_state)
|
|
25
|
+
Observable.new(current_state) do |update|
|
|
26
|
+
State.set_state(@from, $1, update)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
State.get_state(@from, method)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class State
|
|
36
|
+
|
|
37
|
+
@rendering_level = 0
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
attr_reader :current_observer
|
|
41
|
+
|
|
42
|
+
def has_observers?(object, name)
|
|
43
|
+
!observers_by_name[object][name].empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def bulk_update
|
|
47
|
+
saved_bulk_update_flag = @bulk_update_flag
|
|
48
|
+
@bulk_update_flag = true
|
|
49
|
+
yield
|
|
50
|
+
ensure
|
|
51
|
+
@bulk_update_flag = saved_bulk_update_flag
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def set_state2(object, name, value, updates, exclusions = nil)
|
|
55
|
+
# set object's name state to value, tell all observers it has changed.
|
|
56
|
+
# Observers must implement update_react_js_state
|
|
57
|
+
object_needs_notification = object.respond_to? :update_react_js_state
|
|
58
|
+
observers_by_name[object][name].dup.each do |observer|
|
|
59
|
+
next if exclusions && exclusions.include?(observer)
|
|
60
|
+
updates[observer] += [object, name, value]
|
|
61
|
+
object_needs_notification = false if object == observer
|
|
62
|
+
end
|
|
63
|
+
updates[object] += [nil, name, value] if object_needs_notification
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def initialize_states(object, initial_values) # initialize objects' name/value pairs
|
|
67
|
+
states[object].merge!(initial_values || {})
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def get_state(object, name, current_observer = @current_observer)
|
|
71
|
+
# get current value of name for object, remember that the current object depends on this state,
|
|
72
|
+
# current observer can be overriden with last param
|
|
73
|
+
if current_observer && !new_observers[current_observer][object].include?(name)
|
|
74
|
+
new_observers[current_observer][object] << name
|
|
75
|
+
end
|
|
76
|
+
if @delayed_updates && @delayed_updates[object][name]
|
|
77
|
+
@delayed_updates[object][name][1] << current_observer
|
|
78
|
+
end
|
|
79
|
+
states[object][name]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def set_state(object, name, value, delay=nil)
|
|
83
|
+
states[object][name] = value
|
|
84
|
+
if delay || @bulk_update_flag
|
|
85
|
+
@delayed_updates ||= Hash.new { |h, k| h[k] = {} }
|
|
86
|
+
@delayed_updates[object][name] = [value, Set.new]
|
|
87
|
+
@delayed_updater ||= after(0.001) do
|
|
88
|
+
delayed_updates = @delayed_updates
|
|
89
|
+
@delayed_updates = Hash.new { |h, k| h[k] = {} } # could this be nil???
|
|
90
|
+
@delayed_updater = nil
|
|
91
|
+
updates = Hash.new { |hash, key| hash[key] = Array.new }
|
|
92
|
+
delayed_updates.each do |object, name_hash|
|
|
93
|
+
name_hash.each do |name, value_and_set|
|
|
94
|
+
set_state2(object, name, value_and_set[0], updates, value_and_set[1])
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
updates.each { |observer, args| observer.update_react_js_state(*args) }
|
|
98
|
+
end
|
|
99
|
+
elsif @rendering_level == 0
|
|
100
|
+
updates = Hash.new { |hash, key| hash[key] = Array.new }
|
|
101
|
+
set_state2(object, name, value, updates)
|
|
102
|
+
updates.each { |observer, args| observer.update_react_js_state(*args) }
|
|
103
|
+
end
|
|
104
|
+
value
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def notify_observers(object, name, value)
|
|
108
|
+
object_needs_notification = object.respond_to? :update_react_js_state
|
|
109
|
+
observers_by_name[object][name].dup.each do |observer|
|
|
110
|
+
observer.update_react_js_state(object, name, value)
|
|
111
|
+
object_needs_notification = false if object == observer
|
|
112
|
+
end
|
|
113
|
+
object.update_react_js_state(nil, name, value) if object_needs_notification
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def notify_observers_after_thread_completes(object, name, value)
|
|
117
|
+
(@delayed_updates ||= []) << [object, name, value]
|
|
118
|
+
@delayed_updater ||= after(0) do
|
|
119
|
+
delayed_updates = @delayed_updates
|
|
120
|
+
@delayed_updates = []
|
|
121
|
+
@delayed_updater = nil
|
|
122
|
+
delayed_updates.each { |args| notify_observers(*args) }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def will_be_observing?(object, name, current_observer)
|
|
127
|
+
current_observer && new_observers[current_observer][object].include?(name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def is_observing?(object, name, current_observer)
|
|
131
|
+
current_observer && observers_by_name[object][name].include?(current_observer)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def update_states_to_observe(current_observer = @current_observer) # should be called after the last after_render callback, currently called after components render method
|
|
135
|
+
raise "update_states_to_observer called outside of watch block" unless current_observer
|
|
136
|
+
current_observers[current_observer].each do |object, names|
|
|
137
|
+
names.each do |name|
|
|
138
|
+
observers_by_name[object][name].delete(current_observer)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
observers = current_observers[current_observer] = new_observers[current_observer]
|
|
142
|
+
new_observers.delete(current_observer)
|
|
143
|
+
observers.each do |object, names|
|
|
144
|
+
names.each do |name|
|
|
145
|
+
observers_by_name[object][name] << current_observer
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def remove # call after component is unmounted
|
|
151
|
+
raise "remove called outside of watch block" unless @current_observer
|
|
152
|
+
current_observers[@current_observer].each do |object, names|
|
|
153
|
+
names.each do |name|
|
|
154
|
+
observers_by_name[object][name].delete(@current_observer)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
current_observers.delete(@current_observer)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def set_state_context_to(observer, rendering = nil) # wrap all execution that may set or get states in a block so we know which observer is executing
|
|
161
|
+
if `typeof window.reactive_ruby_timing !== 'undefined'`
|
|
162
|
+
@nesting_level = (@nesting_level || 0) + 1
|
|
163
|
+
start_time = Time.now.to_f
|
|
164
|
+
observer_name = (observer.class.respond_to?(:name) ? observer.class.name : observer.to_s) rescue "object:#{observer.object_id}"
|
|
165
|
+
end
|
|
166
|
+
saved_current_observer = @current_observer
|
|
167
|
+
@current_observer = observer
|
|
168
|
+
@rendering_level += 1 if rendering
|
|
169
|
+
return_value = yield
|
|
170
|
+
return_value
|
|
171
|
+
ensure
|
|
172
|
+
@current_observer = saved_current_observer
|
|
173
|
+
@rendering_level -= 1 if rendering
|
|
174
|
+
@nesting_level = [0, @nesting_level - 1].max if `typeof window.reactive_ruby_timing !== 'undefined'`
|
|
175
|
+
return_value
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def states
|
|
179
|
+
@states ||= Hash.new { |h, k| h[k] = {} }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
[:new_observers, :current_observers, :observers_by_name].each do |method_name|
|
|
183
|
+
define_method(method_name) do
|
|
184
|
+
instance_variable_get("@#{method_name}") ||
|
|
185
|
+
instance_variable_set("@#{method_name}", Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = [] } })
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|