volt 0.7.23 → 0.8.0

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