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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +36 -0
  4. data/.rubocop.yml +1159 -0
  5. data/.travis.yml +29 -0
  6. data/Appraisals +20 -0
  7. data/CHANGELOG.md +93 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE +19 -0
  10. data/README.md +121 -0
  11. data/Rakefile +33 -0
  12. data/UPGRADING.md +24 -0
  13. data/component-name-lookup.md +145 -0
  14. data/config.ru +25 -0
  15. data/gemfiles/opal_0.8_react_13.gemfile +13 -0
  16. data/gemfiles/opal_0.8_react_14.gemfile +13 -0
  17. data/gemfiles/opal_0.8_react_15.gemfile +13 -0
  18. data/gemfiles/opal_0.9_react_13.gemfile +13 -0
  19. data/gemfiles/opal_0.9_react_14.gemfile +13 -0
  20. data/gemfiles/opal_0.9_react_15.gemfile +13 -0
  21. data/hyper-react.gemspec +43 -0
  22. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
  23. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  24. data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  25. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  26. data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  27. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  28. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  29. data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  30. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +109 -0
  31. data/lib/hyper-react.rb +52 -0
  32. data/lib/rails-helpers/top_level_rails_component.rb +54 -0
  33. data/lib/react-sources/react-server.js +2 -0
  34. data/lib/react/api.rb +162 -0
  35. data/lib/react/callbacks.rb +42 -0
  36. data/lib/react/children.rb +30 -0
  37. data/lib/react/component.rb +139 -0
  38. data/lib/react/component/api.rb +50 -0
  39. data/lib/react/component/base.rb +9 -0
  40. data/lib/react/component/class_methods.rb +214 -0
  41. data/lib/react/component/dsl_instance_methods.rb +27 -0
  42. data/lib/react/component/params.rb +6 -0
  43. data/lib/react/component/props_wrapper.rb +83 -0
  44. data/lib/react/component/should_component_update.rb +98 -0
  45. data/lib/react/component/tags.rb +144 -0
  46. data/lib/react/element.rb +168 -0
  47. data/lib/react/event.rb +76 -0
  48. data/lib/react/ext/hash.rb +9 -0
  49. data/lib/react/ext/string.rb +8 -0
  50. data/lib/react/hash.rb +13 -0
  51. data/lib/react/native_library.rb +92 -0
  52. data/lib/react/object.rb +15 -0
  53. data/lib/react/observable.rb +29 -0
  54. data/lib/react/react-source.rb +9 -0
  55. data/lib/react/rendering_context.rb +142 -0
  56. data/lib/react/state.rb +190 -0
  57. data/lib/react/test.rb +16 -0
  58. data/lib/react/test/dsl.rb +17 -0
  59. data/lib/react/test/matchers/render_html_matcher.rb +49 -0
  60. data/lib/react/test/rspec.rb +15 -0
  61. data/lib/react/test/session.rb +46 -0
  62. data/lib/react/top_level.rb +132 -0
  63. data/lib/react/validator.rb +136 -0
  64. data/lib/reactive-ruby/component_loader.rb +49 -0
  65. data/lib/reactive-ruby/isomorphic_helpers.rb +197 -0
  66. data/lib/reactive-ruby/rails.rb +7 -0
  67. data/lib/reactive-ruby/rails/component_mount.rb +46 -0
  68. data/lib/reactive-ruby/rails/controller_helper.rb +15 -0
  69. data/lib/reactive-ruby/rails/railtie.rb +14 -0
  70. data/lib/reactive-ruby/serializers.rb +15 -0
  71. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
  72. data/lib/reactive-ruby/version.rb +3 -0
  73. data/lib/reactrb/auto-import.rb +32 -0
  74. data/lib/reactrb/deep-compare.rb +24 -0
  75. data/lib/reactrb/new-event-name-convention.rb +11 -0
  76. data/lib/sources/react-latest.js +21169 -0
  77. data/lib/sources/react-v13.js +21645 -0
  78. data/lib/sources/react-v14.js +20821 -0
  79. data/lib/sources/react-v15.js +21170 -0
  80. data/logo1.png +0 -0
  81. data/logo2.png +0 -0
  82. data/logo3.png +0 -0
  83. data/path_release_steps.md +9 -0
  84. data/spec/controller_helper_spec.rb +34 -0
  85. data/spec/index.html.erb +10 -0
  86. data/spec/react/callbacks_spec.rb +106 -0
  87. data/spec/react/children_spec.rb +76 -0
  88. data/spec/react/component/base_spec.rb +32 -0
  89. data/spec/react/component_spec.rb +872 -0
  90. data/spec/react/dsl_spec.rb +296 -0
  91. data/spec/react/element_spec.rb +136 -0
  92. data/spec/react/event_spec.rb +24 -0
  93. data/spec/react/native_library_spec.rb +344 -0
  94. data/spec/react/observable_spec.rb +7 -0
  95. data/spec/react/opal_jquery_extensions_spec.rb +66 -0
  96. data/spec/react/param_declaration_spec.rb +258 -0
  97. data/spec/react/react_spec.rb +209 -0
  98. data/spec/react/state_spec.rb +55 -0
  99. data/spec/react/test/dsl_spec.rb +43 -0
  100. data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
  101. data/spec/react/test/rspec_spec.rb +62 -0
  102. data/spec/react/test/session_spec.rb +100 -0
  103. data/spec/react/test/utils_spec.rb +45 -0
  104. data/spec/react/top_level_component_spec.rb +96 -0
  105. data/spec/react/tutorial/tutorial_spec.rb +36 -0
  106. data/spec/react/validator_spec.rb +124 -0
  107. data/spec/reactive-ruby/component_loader_spec.rb +71 -0
  108. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
  109. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +10 -0
  110. data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
  111. data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
  112. data/spec/spec_helper.rb +115 -0
  113. data/spec/support/react/spec_helpers.rb +64 -0
  114. data/spec/vendor/es5-shim.min.js +6 -0
  115. data/spec/vendor/jquery-2.2.4.min.js +4 -0
  116. metadata +387 -0
@@ -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
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def shallow_to_n
3
+ hash = `{}`
4
+ self.map do |key, value|
5
+ `hash[#{key}] = #{value}`
6
+ end
7
+ hash
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ class String
2
+ def event_camelize
3
+ `#{self}.replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
4
+ var capitalize = true;
5
+ return capitalize ? word.substr(0,1).toUpperCase()+word.substr(1) : word;
6
+ })`
7
+ end
8
+ end
@@ -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
@@ -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
@@ -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