volt 0.7.23 → 0.8.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -1
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile +8 -0
  5. data/Guardfile +2 -2
  6. data/Readme.md +139 -136
  7. data/VERSION +1 -1
  8. data/app/volt/assets/js/setImmediate.js +175 -0
  9. data/app/volt/tasks/live_query/data_store.rb +0 -2
  10. data/app/volt/tasks/live_query/live_query.rb +4 -4
  11. data/docs/GETTING_STARTED.md +24 -3
  12. data/docs/WHY.md +1 -22
  13. data/lib/volt.rb +20 -1
  14. data/lib/volt/console.rb +20 -0
  15. data/lib/volt/controllers/model_controller.rb +25 -11
  16. data/lib/volt/extra_core/object.rb +2 -14
  17. data/lib/volt/extra_core/string.rb +4 -0
  18. data/lib/volt/models.rb +0 -1
  19. data/lib/volt/models/array_model.rb +8 -16
  20. data/lib/volt/models/cursor.rb +1 -1
  21. data/lib/volt/models/model.rb +40 -60
  22. data/lib/volt/models/model_hash_behaviour.rb +10 -24
  23. data/lib/volt/models/model_helpers.rb +2 -2
  24. data/lib/volt/models/model_state.rb +1 -1
  25. data/lib/volt/models/model_wrapper.rb +4 -4
  26. data/lib/volt/models/persistors/array_store.rb +44 -28
  27. data/lib/volt/models/persistors/base.rb +1 -1
  28. data/lib/volt/models/persistors/model_store.rb +1 -1
  29. data/lib/volt/models/persistors/params.rb +5 -1
  30. data/lib/volt/models/persistors/query/query_listener.rb +2 -0
  31. data/lib/volt/models/persistors/store.rb +3 -2
  32. data/lib/volt/models/persistors/store_state.rb +7 -2
  33. data/lib/volt/models/url.rb +35 -29
  34. data/lib/volt/models/validations.rb +7 -17
  35. data/lib/volt/page/bindings/attribute_binding.rb +57 -39
  36. data/lib/volt/page/bindings/base_binding.rb +0 -14
  37. data/lib/volt/page/bindings/content_binding.rb +15 -18
  38. data/lib/volt/page/bindings/each_binding.rb +67 -34
  39. data/lib/volt/page/bindings/if_binding.rb +15 -12
  40. data/lib/volt/page/bindings/template_binding.rb +77 -59
  41. data/lib/volt/page/bindings/template_binding/grouped_controllers.rb +19 -4
  42. data/lib/volt/page/channel.rb +22 -38
  43. data/lib/volt/page/channel_stub.rb +3 -6
  44. data/lib/volt/page/page.rb +24 -26
  45. data/lib/volt/page/string_template_renderer.rb +46 -0
  46. data/lib/volt/page/sub_context.rb +7 -1
  47. data/lib/volt/page/targets/binding_document/component_node.rb +11 -9
  48. data/lib/volt/page/tasks.rb +3 -2
  49. data/lib/volt/page/url_tracker.rb +4 -3
  50. data/lib/volt/reactive/computation.rb +131 -0
  51. data/lib/volt/reactive/dependency.rb +71 -0
  52. data/lib/volt/reactive/eventable.rb +82 -0
  53. data/lib/volt/reactive/hash_dependency.rb +36 -0
  54. data/lib/volt/{controllers → reactive}/reactive_accessors.rb +8 -11
  55. data/lib/volt/reactive/reactive_array.rb +100 -193
  56. data/lib/volt/reactive/reactive_hash.rb +49 -0
  57. data/lib/volt/server/html_parser/attribute_scope.rb +24 -4
  58. data/lib/volt/server/html_parser/if_view_scope.rb +15 -15
  59. data/lib/volt/server/html_parser/view_scope.rb +31 -1
  60. data/spec/apps/kitchen_sink/Gemfile +4 -8
  61. data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +8 -0
  62. data/spec/apps/kitchen_sink/app/main/config/routes.rb +8 -1
  63. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +8 -0
  64. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +73 -0
  65. data/spec/apps/kitchen_sink/app/main/views/main/index.html +6 -1
  66. data/spec/apps/kitchen_sink/app/main/views/main/main.html +26 -6
  67. data/spec/apps/kitchen_sink/app/main/views/main/store.html +6 -0
  68. data/spec/controllers/reactive_accessors_spec.rb +13 -15
  69. data/spec/integration/bindings_spec.rb +159 -0
  70. data/spec/integration/templates_spec.rb +15 -0
  71. data/spec/models/model_spec.rb +130 -228
  72. data/spec/reactive/computation_spec.rb +63 -0
  73. data/spec/reactive/dependency_spec.rb +5 -0
  74. data/spec/reactive/eventable_spec.rb +48 -0
  75. data/spec/reactive/reactive_array_spec.rb +97 -0
  76. data/spec/router/routes_spec.rb +26 -27
  77. data/spec/server/html_parser/view_parser_spec.rb +3 -21
  78. data/spec/server/rack/asset_files_spec.rb +1 -1
  79. data/templates/project/app/main/views/main/main.html +2 -2
  80. metadata +29 -41
  81. data/lib/volt/extra_core/time.rb +0 -16
  82. data/lib/volt/page/draw_cycle.rb +0 -31
  83. data/lib/volt/page/memory_test.rb +0 -26
  84. data/lib/volt/page/reactive_template.rb +0 -32
  85. data/lib/volt/reactive/array_extensions.rb +0 -12
  86. data/lib/volt/reactive/destructive_methods.rb +0 -19
  87. data/lib/volt/reactive/event_chain.rb +0 -125
  88. data/lib/volt/reactive/events.rb +0 -216
  89. data/lib/volt/reactive/object_tracking.rb +0 -14
  90. data/lib/volt/reactive/reactive_block.rb +0 -88
  91. data/lib/volt/reactive/reactive_generator.rb +0 -44
  92. data/lib/volt/reactive/reactive_tags.rb +0 -71
  93. data/lib/volt/reactive/reactive_value.rb +0 -427
  94. data/lib/volt/reactive/string_extensions.rb +0 -31
  95. data/spec/integration/test_integration_spec.rb +0 -14
  96. data/spec/models/event_chain_spec.rb +0 -150
  97. data/spec/models/model_buffers_spec.rb +0 -9
  98. data/spec/models/old_model_spec.rb +0 -67
  99. data/spec/models/reactive_array_spec.rb +0 -364
  100. data/spec/models/reactive_block_spec.rb +0 -13
  101. data/spec/models/reactive_call_times_spec.rb +0 -28
  102. data/spec/models/reactive_generator_spec.rb +0 -58
  103. data/spec/models/reactive_tags_spec.rb +0 -35
  104. data/spec/models/reactive_value_spec.rb +0 -370
  105. data/spec/models/store_spec.rb +0 -16
  106. data/spec/models/string_extensions_spec.rb +0 -57
@@ -0,0 +1,131 @@
1
+ class Computation
2
+ @@current = nil
3
+ @@flush_queue = []
4
+
5
+
6
+ def self.current=(val)
7
+ @@current = val
8
+ end
9
+
10
+ def self.current
11
+ @@current
12
+ end
13
+
14
+ def initialize(computation)
15
+ @computation = computation
16
+ @invalidations = []
17
+ end
18
+
19
+ # Runs the computation
20
+ def compute!
21
+ @invalidated = false
22
+
23
+ unless @stopped
24
+
25
+ @computing = true
26
+ run_in do
27
+ @computation.call
28
+ end
29
+ @computing = false
30
+ end
31
+ end
32
+
33
+ def on_invalidate(&callback)
34
+ if @invalidated
35
+ # Call invalidate now, since its already invalidated
36
+ Computation.run_without_tracking do
37
+ callback.call
38
+ end
39
+ else
40
+ # Store the invalidation
41
+ @invalidations << callback
42
+ end
43
+ end
44
+
45
+ # Calling invalidate removes the computation from all of
46
+ # its dependencies. This keeps its dependencies from
47
+ # invalidating it again.
48
+ def invalidate!
49
+ unless @invalidated
50
+ @invalidated = true
51
+
52
+ if !@stopped && !@computing
53
+ @@flush_queue << self
54
+
55
+ # If we are in the browser, we queue a flush for the next tick
56
+ if Volt.in_browser?
57
+ self.class.queue_flush!
58
+ end
59
+ end
60
+
61
+ invalidations = @invalidations
62
+ @invalidations = []
63
+
64
+ invalidations.each do |invalidation|
65
+ invalidation.call
66
+ end
67
+ end
68
+ end
69
+
70
+ # Stop re-run of the computations
71
+ def stop
72
+ unless @stopped
73
+ @stopped = true
74
+ invalidate!
75
+ end
76
+ end
77
+
78
+ # Runs in this computation as the current computation, returns the computation
79
+ def run_in
80
+ previous = Computation.current
81
+ Computation.current = self
82
+ yield
83
+ Computation.current = previous
84
+
85
+ return self
86
+ end
87
+
88
+ def self.run_without_tracking
89
+ previous = Computation.current
90
+ Computation.current = nil
91
+ return_value = yield
92
+ Computation.current = previous
93
+
94
+ return return_value
95
+ end
96
+
97
+
98
+ def self.flush!
99
+ raise "Can't flush while in a flush" if @flushing
100
+
101
+ @flushing = true
102
+ # clear any timers
103
+ @timer = nil
104
+
105
+ computations = @@flush_queue
106
+ @@flush_queue = []
107
+
108
+ computations.each do |computation|
109
+ computation.compute!
110
+ end
111
+
112
+ @flushing = false
113
+ end
114
+
115
+ def self.queue_flush!
116
+ if !@timer
117
+ # Flush once everything else has finished running
118
+ @timer = `setImmediate(function() { self['$flush!'](); });`
119
+ end
120
+ end
121
+ end
122
+
123
+
124
+ class Proc
125
+ def watch!
126
+ return Computation.new(self).run_in do
127
+ # run self
128
+ self.call
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,71 @@
1
+ # Temp until https://github.com/opal/opal/pull/596
2
+ require 'set'
3
+
4
+ class Set
5
+ def delete(o)
6
+ @hash.delete(o)
7
+ end
8
+
9
+ def delete?(o)
10
+ if include?(o)
11
+ delete(o)
12
+ else
13
+ nil
14
+ end
15
+ end
16
+
17
+ def delete_if
18
+ block_given? or return enum_for(__method__)
19
+ # @hash.delete_if should be faster, but using it breaks the order
20
+ # of enumeration in subclasses.
21
+ select { |o| yield o }.each { |o| @hash.delete(o) }
22
+ self
23
+ end
24
+
25
+ def to_a
26
+ @hash.keys
27
+ end
28
+ end
29
+
30
+ class Dependency
31
+ def initialize
32
+ @dependencies = Set.new
33
+ end
34
+
35
+ def depend
36
+ # If there is no @dependencies, don't depend because it has been removed
37
+ return unless @dependencies
38
+
39
+ current = Computation.current
40
+ if current
41
+ added = @dependencies.add?(current)
42
+
43
+ if added
44
+ # puts "Added #{self.inspect} to #{current.inspect}"
45
+ current.on_invalidate do
46
+ # If @dependencies is nil, this Dependency has been removed
47
+ @dependencies.delete(current) if @dependencies
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def changed!
54
+ deps = @dependencies
55
+
56
+ # If no deps, dependency has been removed
57
+ return unless deps
58
+
59
+ @dependencies = Set.new
60
+
61
+ deps.each do |dep|
62
+ dep.invalidate!
63
+ end
64
+ end
65
+
66
+ # Called when a dependency is no longer needed
67
+ def remove
68
+ changed!
69
+ @dependencies = nil
70
+ end
71
+ end
@@ -0,0 +1,82 @@
1
+ class Listener
2
+ def initialize(klass, event, callback)
3
+ @klass = klass
4
+ @event = event
5
+ @callback = callback
6
+ end
7
+
8
+ def call(*args)
9
+ @callback.call(*args) unless @removed
10
+ end
11
+
12
+ def remove
13
+ @removed = true
14
+
15
+ @klass.remove_listener(@event, self)
16
+
17
+ # Make things easier on the GC
18
+ @klass = nil
19
+ @callback = nil
20
+ end
21
+
22
+ def inspect
23
+ "<Listener:#{object_id} event=#{@event}>"
24
+ end
25
+ end
26
+
27
+ module Eventable
28
+ def on(event, &callback)
29
+ event = event.to_sym
30
+ listener = Listener.new(self, event, callback)
31
+ @listeners ||= {}
32
+ @listeners[event] ||= []
33
+ @listeners[event] << listener
34
+
35
+ first_for_event = @listeners[event].size == 1
36
+ first = first_for_event && @listeners.size == 1
37
+
38
+ # Let the included class know that an event was registered. (if it cares)
39
+ if self.respond_to?(:event_added)
40
+ # call event added passing the event, the scope, and a boolean if it
41
+ # is the first time this event has been added.
42
+ self.event_added(event, first, first_for_event)
43
+ end
44
+
45
+ return listener
46
+ end
47
+
48
+ def trigger!(event, *args)
49
+ event = event.to_sym
50
+
51
+ return unless @listeners && @listeners[event]
52
+
53
+ # TODO: We have to dup here because one trigger might remove another
54
+ @listeners[event].dup.each do |listener|
55
+ # Call the event on each listener
56
+ listener.call(*args)
57
+ end
58
+ end
59
+
60
+ def remove_listener(event, listener)
61
+ event = event.to_sym
62
+
63
+ raise "Unable to delete #{event} from #{self.inspect}" unless @listeners && @listeners[event]
64
+
65
+ @listeners[event].delete(listener)
66
+
67
+ last_for_event = @listeners[event].size == 0
68
+
69
+ if last_for_event
70
+ # No registered listeners now on this event
71
+ @listeners.delete(event)
72
+ end
73
+
74
+ last = last_for_event && @listeners.size == 0
75
+
76
+ # Let the class we're included on know that we removed a listener (if it cares)
77
+ if self.respond_to?(:event_removed)
78
+ # Pass in the event and a boolean indicating if it is the last event
79
+ self.event_removed(event, last, last_for_event)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,36 @@
1
+ class HashDependency
2
+ def initialize
3
+ @hash_depedencies = {}
4
+ end
5
+
6
+ def depend(key)
7
+ ensure_key(key).depend
8
+ end
9
+
10
+ def changed!(key)
11
+ ensure_key(key).changed!
12
+ end
13
+
14
+ def delete(key)
15
+ # TODORW: should this .remove
16
+ dep = @hash_depedencies[key]
17
+
18
+ if dep
19
+ dep.changed!
20
+ dep.remove
21
+ end
22
+
23
+ @hash_depedencies.delete(key)
24
+ end
25
+
26
+ def changed_all!
27
+ @hash_depedencies.each_pair do |key,value|
28
+ value.changed!
29
+ end
30
+ end
31
+
32
+ private
33
+ def ensure_key(key)
34
+ @hash_depedencies[key] ||= Dependency.new
35
+ end
36
+ end
@@ -4,17 +4,18 @@ module ReactiveAccessors
4
4
  # Create a method to read a reactive value from an instance value. If it
5
5
  # is not setup, create it so it can be updated through the reactive value
6
6
  # at a later point.
7
+ def __reactive_dependency_get(var_name)
8
+ value_dep = instance_variable_get(:"@__#{var_name}_dependency")
9
+ value_dep ||= instance_variable_set(:"@__#{var_name}_dependency", Dependency.new)
10
+ end
11
+
7
12
  def reactive_reader(*names)
8
13
  names.each do |name|
9
14
  var_name = :"@#{name}"
10
15
  define_method(name.to_sym) do
11
16
  value = instance_variable_get(var_name)
12
17
 
13
- unless value
14
- value = ReactiveValue.new(nil)
15
-
16
- instance_variable_set(var_name, value)
17
- end
18
+ self.class.__reactive_dependency_get(name).depend
18
19
 
19
20
  value
20
21
  end
@@ -25,13 +26,9 @@ module ReactiveAccessors
25
26
  names.each do |name|
26
27
  var_name = :"@#{name}"
27
28
  define_method(:"#{name}=") do |new_value|
28
- value = instance_variable_get(var_name)
29
+ instance_variable_set(var_name, new_value)
29
30
 
30
- if value
31
- value.cur = new_value
32
- else
33
- instance_variable_set(var_name, ReactiveValue.new(value))
34
- end
31
+ self.class.__reactive_dependency_get(name).changed!
35
32
  end
36
33
  end
37
34
  end
@@ -1,12 +1,13 @@
1
- require 'volt/reactive/object_tracking'
2
- require 'volt/reactive/reactive_block'
1
+ require 'volt/reactive/eventable'
3
2
 
4
3
  class ReactiveArray# < Array
5
- include ReactiveTags
6
- include ObjectTracking
4
+ include Eventable
7
5
 
8
6
  def initialize(array=[])
9
7
  @array = array
8
+ @array_deps = []
9
+ @size_dep = Dependency.new
10
+ @old_size = 0
10
11
  end
11
12
 
12
13
  # Forward any missing methods to the array
@@ -18,64 +19,74 @@ class ReactiveArray# < Array
18
19
  @array.==(*args)
19
20
  end
20
21
 
21
- tag_method(:each) do
22
- destructive!
23
- end
24
22
  # At the moment, each just passes through.
25
23
  def each(&block)
26
24
  @array.each(&block)
27
25
  end
28
26
 
29
- tag_method(:[]=) do
30
- pass_reactive!
27
+ def count(&block)
28
+ if block
29
+ count = 0
30
+ size.times do |index|
31
+ if block.call(self[index]).true?
32
+ count += 1
33
+ end
34
+ end
35
+
36
+ return count
37
+ else
38
+ return size
39
+ end
31
40
  end
32
41
 
33
- # alias :__old_assign :[]=
34
- def []=(index, value)
35
- index_val = index.cur
42
+ # TODO: Handle a range
43
+ def [](index)
44
+ # Handle a negative index
45
+ index = size + index if index < 0
36
46
 
37
- if index_val < 0
38
- # Handle a negative index
39
- index_val = size + index_val
40
- end
47
+ # Get or create the dependency
48
+ dep = (@array_deps[index] ||= Dependency.new)
41
49
 
42
- # Clean old value
43
- __clear_element(index)
50
+ # Track the dependency
51
+ dep.depend
44
52
 
45
- @array[index.cur] = value
53
+ # Return the index
54
+ return @array[index]
55
+ end
46
56
 
47
- # Track new value
48
- __track_element(index, value)
57
+ def []=(index, value)
49
58
 
50
- # Also track the index if its reactive
51
- if index.reactive?
52
- # TODO: Need to clean this up when the index changes
53
- event_chain.add_object(index.reactive_manager) do |event, *args|
54
- trigger_for_index!(event, index.cur)
55
- end
56
- end
59
+ # Assign the new value
60
+ @array[index] = value
57
61
 
58
- # Trigger changed
59
- trigger_for_index!('changed', index_val)
62
+ trigger_for_index!(index)
63
+
64
+ trigger_size_change!
60
65
  end
61
66
 
62
- tag_method(:delete_at) do
63
- destructive!
67
+ def size
68
+ @size_dep.depend
69
+
70
+ return @array.size
64
71
  end
65
- # alias :__old_delete_at :delete_at
72
+ alias :length :size
73
+
66
74
  def delete_at(index)
67
- index_val = index.cur
75
+ # Handle a negative index
76
+ index = size + index if index < 0
68
77
 
69
- __clear_element(index)
78
+ model = @array.delete_at(index)
70
79
 
71
- model = @array.delete_at(index_val)
80
+ # Remove the dependency for that cell, and #remove it
81
+ index_deps = @array_deps.delete_at(index)
82
+ index_deps.remove if index_deps
72
83
 
73
- trigger_on_direct_listeners!('removed', index_val)
84
+ trigger_removed!(index)
74
85
 
75
86
  # Trigger a changed event for each element in the zone where the
76
- # lookup would change
87
+ # delete would change
77
88
  index.upto(self.size+1) do |position|
78
- trigger_for_index!('changed', position)
89
+ trigger_for_index!(position)
79
90
  end
80
91
 
81
92
  trigger_size_change!
@@ -86,35 +97,45 @@ class ReactiveArray# < Array
86
97
  end
87
98
 
88
99
 
89
- # Delete is implemented as part of delete_at
90
- tag_method(:delete) do
91
- destructive!
92
- end
93
100
  def delete(val)
94
- self.delete_at(@array.index(val))
101
+ index = @array.index(val)
102
+ if index
103
+ self.delete_at(index)
104
+ else
105
+ # Sometimes the model isn't loaded at the right state yet, so we
106
+ # just remove it from the persistor
107
+ @persistor.removed(val) if @persistor
108
+ end
95
109
  end
96
110
 
97
- # Removes all items in the array model.
98
- tag_method(:clear) do
99
- destructive!
100
- end
101
111
  def clear
112
+ old_size = @array.size
113
+
114
+ deps = @array_deps
115
+ @array_deps = []
116
+
117
+ # Trigger remove for each cell
118
+ old_size.times do |index|
119
+ trigger_removed!(old_size - index - 1)
120
+ end
121
+
122
+ # Trigger on each cell since we are clearing out the array
123
+ if deps
124
+ deps.each do |dep|
125
+ dep.changed! if dep
126
+ end
127
+ end
128
+
129
+ # clear the array
102
130
  @array = []
103
- trigger!('changed')
104
131
  end
105
132
 
106
- tag_method(:<<) do
107
- pass_reactive!
108
- end
109
133
  # alias :__old_append :<<
110
134
  def <<(value)
111
135
  result = (@array << value)
112
136
 
113
- # Track new value
114
- __track_element(self.size-1, value)
115
-
116
- trigger_for_index!('changed', self.size-1)
117
- trigger_on_direct_listeners!('added', self.size-1)
137
+ trigger_for_index!(self.size-1)
138
+ trigger_added!(self.size-1)
118
139
  trigger_size_change!
119
140
 
120
141
  return result
@@ -122,6 +143,7 @@ class ReactiveArray# < Array
122
143
 
123
144
 
124
145
  def +(array)
146
+ raise "not implemented yet"
125
147
  old_size = self.size
126
148
 
127
149
  # TODO: += is funky here, might need to make a .plus! method
@@ -129,7 +151,7 @@ class ReactiveArray# < Array
129
151
 
130
152
  old_size.upto(result.size-1) do |index|
131
153
  trigger_for_index!('changed', index)
132
- trigger_on_direct_listeners!('added', old_size + index)
154
+ trigger_added!(old_size + index)
133
155
  end
134
156
 
135
157
  trigger_size_change!
@@ -137,19 +159,16 @@ class ReactiveArray# < Array
137
159
  return result
138
160
  end
139
161
 
140
- tag_method(:insert) do
141
- destructive!
142
- end
143
162
  def insert(index, *objects)
144
163
  result = @array.insert(index, *objects)
145
164
 
146
165
  # All objects from index to the end have "changed"
147
- index.upto(result.size-1) do |idx|
148
- trigger_for_index!('changed', idx)
166
+ index.upto(result.size) do |index|
167
+ trigger_for_index!(index)
149
168
  end
150
169
 
151
170
  objects.size.times do |count|
152
- trigger_on_direct_listeners!('added', index+count)
171
+ trigger_added!(index+count)
153
172
  end
154
173
 
155
174
  trigger_size_change!
@@ -157,150 +176,38 @@ class ReactiveArray# < Array
157
176
  return result
158
177
  end
159
178
 
160
- def trigger_on_direct_listeners!(event, *args)
161
- trigger_by_scope!(event, *args) do |scope|
162
- # Only if it is bound directly to us. Don't pass
163
- # down the chain
164
- !scope || scope[0] == nil
165
- end
166
-
167
- end
168
-
169
- def trigger_size_change!
170
- trigger_by_scope!('changed') do |scope|
171
- # method_name, *args, block = scope
172
- method_name, args, block = split_scope(scope)
173
-
174
- result = case method_name && method_name.to_sym
175
- when :size, :length
176
- true
177
- else
178
- false
179
- end
180
-
181
- result
182
- end
183
- end
184
-
185
- # TODO: This is an opal work around. Currently there is a bug with destructuring
186
- # method_name, *args, block = scope
187
- def split_scope(scope)
188
- if scope
189
- scope = scope.dup
190
- method_name = scope.shift
191
- block = scope.pop
192
-
193
- return method_name, scope, block
194
- else
195
- return nil,[],nil
196
- end
197
- end
198
-
199
- # Trigger the changed event to any values fetched either through the
200
- # lookup ([]), #last, or any fetched through the array its self. (sum, max, etc...)
201
- # On an array, when an element is added or removed, we need to trigger change
202
- # events on each method that does the following:
203
- # 1. uses the whole array (max, sum, etc...)
204
- # 2. accesses this specific element - array[index]
205
- # 3. accesses an element via a method (first, last)
206
- def trigger_for_index!(event_name, index, *passed_args)
207
- self.trigger_by_scope!(event_name, *passed_args) do |scope|
208
- # method_name, *args, block = scope
209
- method_name, args, block = split_scope(scope)
210
-
211
- result = case method_name
212
- when nil
213
- # no method name means the event was bound directly, we don't
214
- # want to trigger changed on the array its self.
215
- false
216
- when :[]
217
- # Extract the current index if its reactive
218
- arg_index = args[0].cur
219
-
220
- # TODO: we could handle negative indicies better
221
- arg_index == index.cur || arg_index < 0
222
- when :last
223
- index.cur == self.size-1
224
- when :first
225
- index.cur == 0
226
- when :size, :length
227
- # Size does not depend on the contents of the cells
228
- false
229
- else
230
- true
231
- end
232
-
233
- result = false if method_name == :reject
234
-
235
- result
236
- end
237
- end
238
179
 
239
180
  def inspect
240
181
  "#<#{self.class.to_s}:#{object_id} #{@array.inspect}>"
241
182
  end
242
183
 
243
- # tag_method(:count) do
244
- # destructive!
245
- # end
246
- def count(*args, &block)
247
- # puts "GET COUNT"
248
- if block
249
- run_block = Proc.new do |source|
250
- count = 0
251
- source.cur.size.times do |index|
252
- val = source[index]
253
- result = block.call(val).cur
254
- if result == true
255
- count += 1
256
- end
257
- end
258
184
 
259
- count
185
+ private
186
+ # Check to see if the size has changed, trigger a change on size if it has
187
+ def trigger_size_change!
188
+ new_size = @array.size
189
+ if new_size != @old_size
190
+ @old_size = new_size
191
+ @size_dep.changed!
260
192
  end
261
-
262
- return ReactiveBlock.new(self, block, run_block)
263
- else
264
- @array.count(*args)
265
193
  end
266
- end
267
194
 
268
- def reject(*args, &block)
269
- if block
270
- run_block = Proc.new do |source|
271
- puts "RUN REJECT"
272
- new_array = []
273
- source.cur.size.times do |index|
274
- val = source[index]
275
- result = block.call(val).cur
276
- if result != true
277
- new_array << val.cur
278
- end
279
- end
195
+ def trigger_for_index!(index)
196
+ # Trigger a change for the cell
197
+ dep = @array_deps[index]
280
198
 
281
- ReactiveArray.new(new_array)
282
- end
283
-
284
- return ReactiveBlock.new(self, block, run_block)
285
- else
286
- @array.count
199
+ dep.changed! if dep
287
200
  end
288
- end
289
201
 
290
- private
291
202
 
292
- def __clear_element(index)
293
- # Cleanup any tracking on an index
294
- if @reactive_element_listeners && self[index].reactive?
295
- @reactive_element_listeners[index].remove
296
- @reactive_element_listeners.delete(index)
297
- end
203
+ def trigger_added!(index)
204
+ trigger!('added', index)
298
205
  end
299
206
 
300
- def __track_element(index, value)
301
- __setup_tracking(index, value) do |event, index, args|
302
- trigger_for_index!(event, index, *args)
303
- end
207
+ def trigger_removed!(index)
208
+ trigger!('removed', index)
304
209
  end
305
210
 
211
+
212
+
306
213
  end