hyper-react 0.10.0

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