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
@@ -1,16 +0,0 @@
1
- class Temp1
2
- include Events
3
-
4
- attr_accessor :seconds
5
- def initialize
6
- @seconds = ReactiveValue.new(nil)
7
- end
8
-
9
- def seconds=(val)
10
- @seconds.cur = val
11
- end
12
-
13
- def live_seconds
14
- @seconds
15
- end
16
- end
@@ -1,31 +0,0 @@
1
- # The draw cycle is responsible for queueing redraws until all of the events have
2
- # fired. Once that is done, everything will be redrawn. This prevents bindings
3
- # from being drawn multiple times before all events have propigated.
4
- class DrawCycle
5
- def initialize
6
- @queue = {}
7
- @timer = nil
8
- end
9
-
10
- def queue(binding)
11
- @queue[binding] = true
12
-
13
- unless @timer
14
- # Flush once everything else has finished running
15
- @timer = `setTimeout(function() { self.$flush(); }, 0);`
16
- end
17
- end
18
-
19
- def flush
20
- @timer = nil
21
-
22
- work_queue = @queue
23
- @queue = {}
24
-
25
- work_queue.each_pair do |binding,_|
26
- # Call the update if queued
27
- binding.update
28
- end
29
-
30
- end
31
- end
@@ -1,26 +0,0 @@
1
- require 'opal'
2
- require 'volt/models'
3
-
4
- class Test
5
- def self.test1
6
- a = ReactiveValue.new(1)
7
- listener = a.on('changed') { puts "CHANGED" }
8
- a.cur = 5
9
- listener.remove
10
-
11
- ObjectTracker.process_queue
12
- end
13
-
14
- def self.test
15
- a = ReactiveValue.new(Model.new)
16
- a._cool = [1,2,3]
17
-
18
- listener = a._cool.on('added') { puts "ADDED" }
19
- a._cool << 4
20
- puts a._cool[3]
21
-
22
- listener.remove
23
-
24
- ObjectTracker.process_queue
25
- end
26
- end
@@ -1,32 +0,0 @@
1
- class ReactiveTemplate
2
- include Events
3
-
4
- def initialize(page, context, template_path)
5
- @template_path = template_path
6
- @target = AttributeTarget.new(nil, nil, self)
7
- @template = TemplateRenderer.new(page, @target, context, "main", template_path)
8
-
9
- end
10
-
11
- def reactive?
12
- true
13
- end
14
-
15
- # Render the template and get the current value
16
- def cur
17
- @target.to_html
18
- end
19
-
20
- def update
21
- trigger!('changed')
22
- end
23
-
24
- def remove
25
- @template.remove
26
-
27
- @template = nil
28
- @target = nil
29
- @template_path = nil
30
- end
31
-
32
- end
@@ -1,12 +0,0 @@
1
- class Array
2
- alias :__old_plus :+
3
-
4
- def +(val)
5
- result = __old_plus(val.cur)
6
- if val.reactive? && !result.reactive?
7
- result = ReactiveValue.new(result)
8
- end
9
-
10
- return result
11
- end
12
- end
@@ -1,19 +0,0 @@
1
- # DestructiveMethods tracks the names of all methods that are marked as
2
- # destructive. This lets us do an optimization where we don't need to
3
- # check any methods with names that aren't here, we can be sure that they
4
- # are not destructive. If the method is tracked here, we need to check
5
- # it on its current class.
6
- class DestructiveMethods
7
- @@method_names = {}
8
-
9
- def self.add_method(method_name)
10
- @@method_names[method_name] = true
11
- end
12
-
13
- # Check to see if a method might be destructive. If this returns
14
- # false, then we can guarentee that it won't be destructive and
15
- # we can skip a destructive check.
16
- def self.might_be_destructive?(method_name)
17
- return @@method_names[method_name]
18
- end
19
- end
@@ -1,125 +0,0 @@
1
- CHAIN_DEBUG = false
2
-
3
- class ChainListener
4
- attr_reader :object, :callback
5
-
6
- def initialize(event_chain, object, callback)
7
- @event_chain = event_chain
8
- @object = object
9
- @callback = callback
10
-
11
- if RUBY_PLATFORM == 'opal' && CHAIN_DEBUG
12
- `window.chain_listeners = window.chain_listeners || 0;`
13
- `window.chain_listeners += 1;`
14
- `console.log('chain listeners: ', window.chain_listeners)`
15
- end
16
- end
17
-
18
- def remove
19
- # raise "event chain already removed" if @removed
20
- if @removed
21
- puts "event chain already removed"
22
- return
23
- end
24
-
25
- @removed = true
26
- @event_chain.remove_object(self)
27
-
28
- # We need to clear these to free memory
29
- @event_chain = nil
30
- @object = nil
31
- @callback = nil
32
-
33
- if RUBY_PLATFORM == 'opal' && CHAIN_DEBUG
34
- `window.chain_listeners -= 1;`
35
- `console.log('del chain listeners: ', window.chain_listeners)`
36
- end
37
- end
38
- end
39
-
40
- class EventChain
41
- def initialize(main_object)
42
- @event_chain = {}
43
- @main_object = main_object
44
- @event_counts = {}
45
- end
46
-
47
- # Register an event listener that chains from object to self
48
- def setup_listener(event, chain_listener)
49
- return chain_listener.object.on(event, @main_object) do |filter, *args|
50
- if (callback = chain_listener.callback)
51
- callback.call(event, filter, *args)
52
- else
53
- # Trigger on this value, when it happens on the parent
54
-
55
- # Only pass the filter from non-reactive to reactive? This
56
- # lets us scope the calls on a proxied object.
57
- # Filters limit which listeners are triggered, passing them to
58
- # the ReactiveValue's from non-reactive lets us filter at the
59
- # reactive level as well.
60
- filter = nil unless !chain_listener.object.reactive? && @main_object.reactive?
61
-
62
- @main_object.trigger!(event, filter, *args)
63
- end
64
- end
65
- end
66
-
67
- # We can chain our events to any other object that includes
68
- # Events
69
- def add_object(object, &block)
70
- chain_listener = ChainListener.new(self, object, block)
71
-
72
- listeners = {}
73
-
74
- @main_object.listeners.keys.each do |event|
75
- # Create a listener for each event
76
- listeners[event] = setup_listener(event, chain_listener)
77
- end
78
-
79
- @event_chain[chain_listener] = listeners
80
-
81
- return chain_listener
82
- end
83
-
84
-
85
- def remove_object(chain_listener)
86
- @event_chain[chain_listener].each_pair do |event,listener|
87
- # Unbind each listener
88
- # TODO: The if shouldn't be needed, but sometimes we get nil for some reason?
89
- listener.remove if listener
90
- end
91
-
92
- @event_chain.delete(chain_listener)
93
- end
94
-
95
- def add_event(event)
96
- unless @event_counts[event]
97
- @event_chain.each_pair do |chain_listener,listeners|
98
- # Only add if we haven't already chained this event
99
- unless listeners[event]
100
- listeners[event] = setup_listener(event, chain_listener)
101
- end
102
- end
103
- end
104
-
105
- @event_counts[event] ||= 0
106
- @event_counts[event] += 1
107
- end
108
-
109
- # Removes the event from all events in all objects
110
- def remove_event(event)
111
- if @event_counts[event]
112
- count = @event_counts[event] -= 1
113
-
114
- if count == 0
115
- @event_chain.each_pair do |chain_listener,listeners|
116
- listeners[event].remove# if listeners[event]
117
- listeners.delete(event)
118
- end
119
-
120
- # Also remove the event count
121
- @event_counts.delete(event)
122
- end
123
- end
124
- end
125
- end
@@ -1,216 +0,0 @@
1
- require 'volt/reactive/event_chain'
2
-
3
- DEBUG = false
4
-
5
- # A listener gets returned when adding an 'on' event listener. It can be
6
- # used to clear the event listener.
7
- class Listener
8
- attr_reader :scope_provider, :klass
9
-
10
- def initialize(klass, event, scope_provider, callback)
11
- @klass = klass
12
- @event = event
13
- @scope_provider = scope_provider
14
- @callback = callback
15
-
16
- if DEBUG && RUBY_PLATFORM == 'opal'
17
- # puts "e: #{event} on #{klass.inspect}"
18
- @@all_events ||= []
19
- @@all_events << self
20
-
21
- # counts = {}
22
- # @@all_events.each do |ev|
23
- # scope = (ev.scope_provider && ev.scope_provider.scope) || nil
24
- #
25
- # # puts `typeof(scope)`
26
- # if `typeof(scope) !== 'undefined'`
27
- # counts[scope] ||= 0
28
- # counts[scope] += 1
29
- # end
30
- # end
31
- #
32
- # puts counts.inspect
33
-
34
- `window.total_listeners = window.total_listeners || 0;`
35
- `window.total_listeners += 1;`
36
- `console.log(window.total_listeners);`
37
- end
38
- end
39
-
40
- def internal?
41
- @internal
42
- end
43
-
44
- def scope
45
- @scope_provider && scope_provider.respond_to?(:scope) && @scope_provider.scope
46
- end
47
-
48
- def call(*args)
49
- if @removed
50
- # puts "Triggered on a removed event: #{@event}"
51
- return
52
- end
53
-
54
- if @klass.reactive?
55
- # Update the reactive value's current value to let it know it is being
56
- # followed.
57
- @klass.update_followers if @klass.respond_to?(:update_followers)
58
- end
59
-
60
- @callback.call(*args)
61
- end
62
-
63
- # Removes the listener from where ever it was created.
64
- def remove
65
- if @removed
66
- # raise "event #{@event} already removed"
67
- puts "event #{@event} already removed"
68
- return
69
- end
70
-
71
- if DEBUG && RUBY_PLATFORM == 'opal'
72
- @@all_events.delete(self) if @@all_events
73
-
74
- `window.total_listeners -= 1;`
75
- `console.log("Rem", window.total_listeners);`
76
- end
77
-
78
-
79
- @removed = true
80
- @klass.remove_listener(@event, self)
81
-
82
- # We need to clear these references to free the memory
83
- @scope_provider = nil
84
- @callback = nil
85
- # @klass2 = @klass
86
- @klass = nil
87
- # @event = nil
88
-
89
- end
90
-
91
- def inspect
92
- "<Listener:#{object_id} event=#{@event} scope=#{scope.inspect}#{' internal' if internal?}>"
93
- end
94
- end
95
-
96
- module Events
97
- # Add a listener for an event
98
- def on(event, scope_provider=nil, &block)
99
-
100
- event = event.to_sym
101
-
102
- @has_listeners = true
103
-
104
- new_listener = Listener.new(self, event, scope_provider, block)
105
-
106
- @listeners ||= {}
107
- @listeners[event] ||= []
108
- @listeners[event] << new_listener
109
-
110
- first_for_event = @listeners[event].size == 1
111
- first = first_for_event && @listeners.size == 1
112
-
113
- # When events get added, we need to notify event chains so they
114
- # can update and chain any new events.
115
- event_chain.add_event(event) if first_for_event
116
-
117
- # Let the included class know that an event was registered. (if it cares)
118
- if self.respond_to?(:event_added)
119
- # call event added passing the event, the scope, and a boolean if it
120
- # is the first time this event has been added.
121
- self.event_added(event, scope_provider, first, first_for_event)
122
- end
123
-
124
- return new_listener
125
- end
126
-
127
- def event_chain
128
- @event_chain ||= EventChain.new(self)
129
- end
130
-
131
- def listeners
132
- @listeners || {}
133
- end
134
-
135
- def has_listeners?
136
- @has_listeners
137
- end
138
-
139
- # Typically you would call .remove on the listener returned from the .on
140
- # method. However, here you can also pass in the original proc to remove
141
- # a listener
142
- def remove_listener(event, listener)
143
- event = event.to_sym
144
-
145
- raise "Unable to delete #{event} from #{self.inspect}" unless @listeners && @listeners[event]
146
-
147
- @listeners[event].delete(listener)
148
-
149
- last_for_event = @listeners[event].size == 0
150
-
151
- if last_for_event
152
- # When events are removed, we need to notify any relevent chains so they
153
- # can remove any chained events.
154
- event_chain.remove_event(event)
155
-
156
- # No registered listeners now on this event
157
- @listeners.delete(event)
158
- end
159
-
160
- last = last_for_event && @listeners.size == 0
161
-
162
- # Let the class we're included on know that we removed a listener (if it cares)
163
- if self.respond_to?(:event_removed)
164
- # Pass in the event and a boolean indicating if it is the last event
165
- self.event_removed(event, last, last_for_event)
166
- end
167
-
168
- if last
169
- @has_listeners = nil
170
- end
171
- end
172
-
173
- def trigger!(event, filter=nil, *args)
174
- # puts "TRIGGER: #{event} on #{self.inspect}" if event == :added
175
- are_reactive = reactive?
176
-
177
- event = event.to_sym
178
-
179
- if @listeners && @listeners[event]
180
- # TODO: We have to dup here because one trigger might remove another
181
- @listeners[event].dup.each do |listener|
182
- # Call the event on each listener
183
- # If there is no listener, that means another event trigger removed it.
184
- # If there is no filter, call
185
- # if we aren't reactive, we should pass to all of our reactive listeners, since they
186
- # just proxy us.
187
- # If the filter exists, check it
188
- if (!filter || (!are_reactive && listener.scope_provider.reactive?) || filter.call(listener.scope))
189
- listener.call(filter, *args)
190
- end
191
- end
192
- end
193
-
194
- nil
195
- end
196
-
197
- # Takes a block, which passes in
198
- def trigger_by_scope!(event, *args, &block)
199
- trigger!(event, block, *args)
200
- end
201
-
202
- # Takes an event and a list of method names, and triggers the event for each listener
203
- # coming off of those methods.
204
- def trigger_for_methods!(event, *method_names)
205
- trigger_by_scope!(event, [], nil) do |scope|
206
- if scope
207
- method_name = scope.first
208
-
209
- method_names.include?(method_name)
210
- else
211
- false
212
- end
213
- end
214
- end
215
-
216
- end