volt 0.8.27.beta3 → 0.8.27.beta4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +3 -2
- data/{Readme.md → README.md} +9 -12
- data/Rakefile +2 -9
- data/VERSION +1 -1
- data/app/volt/models/user.rb +8 -0
- data/app/volt/tasks/live_query/data_store.rb +13 -5
- data/app/volt/tasks/live_query/live_query.rb +45 -3
- data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
- data/app/volt/tasks/query_tasks.rb +20 -2
- data/app/volt/tasks/store_tasks.rb +37 -20
- data/app/volt/tasks/user_tasks.rb +15 -13
- data/lib/volt/boot.rb +5 -3
- data/lib/volt/cli/console.rb +1 -0
- data/lib/volt/cli/generate.rb +15 -0
- data/lib/volt/cli.rb +19 -12
- data/lib/volt/config.rb +1 -1
- data/lib/volt/controllers/model_controller.rb +13 -3
- data/lib/volt/extra_core/extra_core.rb +1 -0
- data/lib/volt/extra_core/hash.rb +26 -0
- data/lib/volt/extra_core/object.rb +5 -1
- data/lib/volt/models/array_model.rb +86 -35
- data/lib/volt/models/associations.rb +53 -0
- data/lib/volt/models/buffer.rb +22 -10
- data/lib/volt/models/dirty.rb +88 -0
- data/lib/volt/models/errors.rb +21 -0
- data/lib/volt/models/field_helpers.rb +2 -2
- data/lib/volt/models/listener_tracker.rb +17 -0
- data/lib/volt/models/model.rb +213 -69
- data/lib/volt/models/model_helpers.rb +27 -17
- data/lib/volt/models/permissions.rb +246 -0
- data/lib/volt/models/persistors/array_store.rb +149 -81
- data/lib/volt/models/persistors/base.rb +16 -0
- data/lib/volt/models/persistors/cookies.rb +14 -9
- data/lib/volt/models/persistors/flash.rb +3 -0
- data/lib/volt/models/persistors/local_store.rb +0 -16
- data/lib/volt/models/persistors/model_store.rb +1 -2
- data/lib/volt/models/persistors/query/normalizer.rb +51 -0
- data/lib/volt/models/persistors/query/query_listener.rb +21 -5
- data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
- data/lib/volt/models/persistors/store.rb +8 -0
- data/lib/volt/models/persistors/store_state.rb +4 -27
- data/lib/volt/models/state_helpers.rb +11 -0
- data/lib/volt/models/state_manager.rb +43 -0
- data/lib/volt/models/url.rb +5 -5
- data/lib/volt/models/validations.rb +38 -41
- data/lib/volt/models/validators/email_validator.rb +4 -9
- data/lib/volt/models/validators/format_validator.rb +23 -8
- data/lib/volt/models/validators/length_validator.rb +2 -2
- data/lib/volt/models/validators/numericality_validator.rb +7 -3
- data/lib/volt/models/validators/phone_number_validator.rb +4 -9
- data/lib/volt/models/validators/presence_validator.rb +2 -2
- data/lib/volt/models/validators/unique_validator.rb +2 -2
- data/lib/volt/models/validators/user_validation.rb +6 -0
- data/lib/volt/models.rb +8 -3
- data/lib/volt/page/bindings/attribute_binding.rb +10 -4
- data/lib/volt/page/bindings/content_binding.rb +9 -5
- data/lib/volt/page/bindings/if_binding.rb +25 -2
- data/lib/volt/page/bindings/template_binding.rb +19 -1
- data/lib/volt/page/bindings/yield_binding.rb +31 -0
- data/lib/volt/page/page.rb +11 -16
- data/lib/volt/reactive/class_eventable.rb +71 -0
- data/lib/volt/reactive/computation.rb +79 -10
- data/lib/volt/reactive/dependency.rb +27 -8
- data/lib/volt/reactive/eventable.rb +36 -22
- data/lib/volt/reactive/reactive_array.rb +2 -3
- data/lib/volt/reactive/reactive_hash.rb +8 -3
- data/lib/volt/router/routes.rb +2 -1
- data/lib/volt/server/component_templates.rb +0 -2
- data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
- data/lib/volt/server/html_parser/view_handler.rb +3 -0
- data/lib/volt/server/html_parser/view_parser.rb +1 -0
- data/lib/volt/server/html_parser/view_scope.rb +17 -41
- data/lib/volt/server/rack/component_paths.rb +1 -10
- data/lib/volt/server/rack/index_files.rb +9 -4
- data/lib/volt/server/rack/opal_files.rb +22 -14
- data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
- data/lib/volt/server/socket_connection_handler.rb +4 -0
- data/lib/volt/spec/setup.rb +26 -0
- data/lib/volt/tasks/dispatcher.rb +11 -0
- data/lib/volt/utils/event_counter.rb +29 -0
- data/lib/volt/utils/generic_pool.rb +12 -0
- data/lib/volt/utils/modes.rb +40 -0
- data/lib/volt/utils/promise_patch.rb +66 -0
- data/lib/volt/utils/timers.rb +33 -0
- data/lib/volt/volt/users.rb +48 -5
- data/lib/volt.rb +4 -0
- data/spec/apps/kitchen_sink/Gemfile +3 -1
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
- data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
- data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
- data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
- data/spec/extra_core/logger_spec.rb +4 -2
- data/spec/integration/user_spec.rb +42 -42
- data/spec/integration/yield_spec.rb +18 -0
- data/spec/models/associations_spec.rb +37 -0
- data/spec/models/dirty_spec.rb +102 -0
- data/spec/models/model_spec.rb +64 -8
- data/spec/models/model_state_spec.rb +24 -0
- data/spec/models/permissions_spec.rb +96 -0
- data/spec/models/user_spec.rb +8 -5
- data/spec/models/user_validation_spec.rb +24 -0
- data/spec/models/validations_spec.rb +44 -5
- data/spec/models/validators/email_validator_spec.rb +109 -82
- data/spec/models/validators/format_validator_spec.rb +4 -107
- data/spec/models/validators/length_validator_spec.rb +9 -9
- data/spec/models/validators/phone_number_validator_spec.rb +60 -103
- data/spec/models/validators/shared_examples_for_validators.rb +123 -0
- data/spec/reactive/class_eventable_spec.rb +37 -0
- data/spec/reactive/computation_spec.rb +68 -3
- data/spec/reactive/dependency_spec.rb +71 -0
- data/spec/reactive/eventable_spec.rb +21 -0
- data/spec/reactive/reactive_hash_spec.rb +12 -0
- data/spec/router/routes_spec.rb +50 -50
- data/spec/server/html_parser/view_parser_spec.rb +0 -3
- data/spec/server/rack/component_paths_spec.rb +11 -0
- data/spec/server/rack/quite_common_logger_spec.rb +3 -4
- data/spec/spec_helper.rb +7 -3
- data/templates/component/config/dependencies.rb +1 -7
- data/templates/component/config/routes.rb +1 -1
- data/templates/component/controllers/main_controller.rb.tt +20 -0
- data/templates/component/views/{index → main}/index.html.tt +0 -0
- data/templates/newgem/lib/newgem.rb.tt +1 -3
- data/templates/project/app/main/config/routes.rb +3 -3
- data/templates/project/app/main/views/main/main.html.tt +4 -4
- data/templates/project/config/app.rb.tt +6 -0
- data/volt.gemspec +11 -7
- metadata +96 -42
- data/lib/volt/models/model_state.rb +0 -21
- data/templates/component/controllers/main_controller.rb +0 -18
@@ -17,12 +17,14 @@ module Volt
|
|
17
17
|
# Listen for changes
|
18
18
|
@computation = -> do
|
19
19
|
begin
|
20
|
-
|
20
|
+
@context.instance_eval(&@getter)
|
21
21
|
rescue => e
|
22
22
|
Volt.logger.error("AttributeBinding Error: #{e.inspect}")
|
23
|
-
|
23
|
+
''
|
24
24
|
end
|
25
|
-
end.
|
25
|
+
end.watch_and_resolve! do |result|
|
26
|
+
update(result)
|
27
|
+
end
|
26
28
|
|
27
29
|
@is_radio = element.is('[type=radio]')
|
28
30
|
if @is_radio
|
@@ -122,9 +124,13 @@ module Volt
|
|
122
124
|
element.off('change.attrbind', nil)
|
123
125
|
end
|
124
126
|
|
127
|
+
if @computation
|
128
|
+
@computation.stop
|
129
|
+
@computation = nil
|
130
|
+
end
|
131
|
+
|
125
132
|
@string_template_renderer.remove if @string_template_renderer
|
126
133
|
@string_template_renderer_computation.stop if @string_template_renderer_computation
|
127
|
-
@computation.stop if @computation
|
128
134
|
|
129
135
|
# Clear any references
|
130
136
|
@target = nil
|
@@ -9,12 +9,14 @@ module Volt
|
|
9
9
|
# Listen for changes
|
10
10
|
@computation = -> do
|
11
11
|
begin
|
12
|
-
|
12
|
+
res = @context.instance_eval(&getter)
|
13
13
|
rescue => e
|
14
14
|
Volt.logger.error("ContentBinding Error: #{e.inspect}")
|
15
|
-
|
15
|
+
''
|
16
16
|
end
|
17
|
-
end.
|
17
|
+
end.watch_and_resolve! do |result|
|
18
|
+
update(result)
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
def update(value)
|
@@ -29,8 +31,10 @@ module Volt
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def remove
|
32
|
-
|
33
|
-
|
34
|
+
if @computation
|
35
|
+
@computation.stop
|
36
|
+
@computation = nil
|
37
|
+
end
|
34
38
|
|
35
39
|
super
|
36
40
|
end
|
@@ -24,7 +24,15 @@ module Volt
|
|
24
24
|
@branches << [value, template_name]
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
# The promise dependency can be invalidated when we need to rerun the update
|
28
|
+
# manually because a promise resolved.
|
29
|
+
@promise_dependency = Dependency.new
|
30
|
+
|
31
|
+
@computation = -> do
|
32
|
+
@promise_dependency.depend
|
33
|
+
|
34
|
+
update
|
35
|
+
end.watch!
|
28
36
|
end
|
29
37
|
|
30
38
|
def update
|
@@ -36,6 +44,22 @@ module Volt
|
|
36
44
|
if value.is_a?(Proc)
|
37
45
|
begin
|
38
46
|
current_value = @context.instance_eval(&value)
|
47
|
+
|
48
|
+
if current_value.is_a?(Promise)
|
49
|
+
# If we got a promise, use its value if resolved.
|
50
|
+
if current_value.resolved?
|
51
|
+
current_value = current_value.value
|
52
|
+
else
|
53
|
+
# if its not, resolve it and try again.
|
54
|
+
# TODO: we maybe could cache this so we don't have to run a full update again
|
55
|
+
current_value.then do |val|
|
56
|
+
# Run update again
|
57
|
+
@promise_dependency.changed!
|
58
|
+
end
|
59
|
+
|
60
|
+
current_value = nil
|
61
|
+
end
|
62
|
+
end
|
39
63
|
rescue => e
|
40
64
|
Volt.logger.error("IfBinding:#{object_id} error: #{e.inspect}\n" + `value.toString()`)
|
41
65
|
current_value = false
|
@@ -44,7 +68,6 @@ module Volt
|
|
44
68
|
current_value = value
|
45
69
|
end
|
46
70
|
|
47
|
-
# TODO: A bug in opal requires us to check == true
|
48
71
|
if current_value && !current_value.nil? && !current_value.is_a?(Exception)
|
49
72
|
# This branch is currently true
|
50
73
|
true_template = template_name
|
@@ -3,11 +3,19 @@ require 'volt/page/template_renderer'
|
|
3
3
|
require 'volt/page/bindings/template_binding/grouped_controllers'
|
4
4
|
require 'volt/page/bindings/template_binding/view_lookup_for_path'
|
5
5
|
|
6
|
+
|
6
7
|
module Volt
|
7
8
|
class TemplateBinding < BaseBinding
|
8
|
-
|
9
|
+
|
10
|
+
# @param [String] binding_in_path is the path this binding was rendered from. Used to
|
11
|
+
# lookup paths in ViewLookupForPath
|
12
|
+
# @param [String|nil] content_template_path is the path to the template for the content
|
13
|
+
# provided in the tag.
|
14
|
+
def initialize(page, target, context, binding_name, binding_in_path, getter, content_template_path=nil)
|
9
15
|
super(page, target, context, binding_name)
|
10
16
|
|
17
|
+
@content_template_path = content_template_path
|
18
|
+
|
11
19
|
# Setup the view lookup helper
|
12
20
|
@view_lookup = Volt::ViewLookupForPath.new(page, binding_in_path)
|
13
21
|
|
@@ -47,6 +55,13 @@ module Volt
|
|
47
55
|
else
|
48
56
|
# Use the value passed in as the default arguments
|
49
57
|
@arguments = section_or_arguments
|
58
|
+
|
59
|
+
# Include content_template_path in attrs
|
60
|
+
if @content_template_path
|
61
|
+
@arguments ||= {}
|
62
|
+
@arguments[:content_template_path] = @content_template_path
|
63
|
+
@arguments[:content_controller] = @context
|
64
|
+
end
|
50
65
|
end
|
51
66
|
|
52
67
|
# Sometimes we want multiple template bindings to share the same controller (usually
|
@@ -140,6 +155,9 @@ module Volt
|
|
140
155
|
end
|
141
156
|
|
142
157
|
def remove
|
158
|
+
@computation.stop
|
159
|
+
@computation = nil
|
160
|
+
|
143
161
|
controller_send(:"before_#{@action}_remove") if @controller && @action
|
144
162
|
|
145
163
|
clear_grouped_controller
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# The yield binding renders the content of a tag which passes in
|
2
|
+
|
3
|
+
require 'volt/page/bindings/base_binding'
|
4
|
+
require 'volt/page/template_renderer'
|
5
|
+
|
6
|
+
module Volt
|
7
|
+
class YieldBinding < BaseBinding
|
8
|
+
def initialize(page, target, context, binding_name)
|
9
|
+
super(page, target, context, binding_name)
|
10
|
+
|
11
|
+
# Get the path to the template to yield
|
12
|
+
full_path = @context.attrs.content_template_path
|
13
|
+
|
14
|
+
# Grab the controller for the content
|
15
|
+
controller = @context.attrs.content_controller
|
16
|
+
|
17
|
+
@current_template = TemplateRenderer.new(@page, @target, controller, @binding_name, full_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove
|
21
|
+
if @current_template
|
22
|
+
# Remove the template if one has been rendered, when the template binding is
|
23
|
+
# removed.
|
24
|
+
@current_template.remove
|
25
|
+
end
|
26
|
+
|
27
|
+
super
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/volt/page/page.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
+
require 'opal'
|
1
2
|
if RUBY_PLATFORM == 'opal'
|
2
|
-
require 'opal'
|
3
3
|
require 'opal-jquery'
|
4
4
|
end
|
5
5
|
require 'volt/models'
|
@@ -10,6 +10,7 @@ require 'volt/page/bindings/content_binding'
|
|
10
10
|
require 'volt/page/bindings/each_binding'
|
11
11
|
require 'volt/page/bindings/if_binding'
|
12
12
|
require 'volt/page/bindings/template_binding'
|
13
|
+
require 'volt/page/bindings/yield_binding'
|
13
14
|
require 'volt/page/bindings/component_binding'
|
14
15
|
require 'volt/page/bindings/event_binding'
|
15
16
|
require 'volt/page/template_renderer'
|
@@ -31,11 +32,9 @@ require 'volt/page/tasks'
|
|
31
32
|
|
32
33
|
module Volt
|
33
34
|
class Page
|
34
|
-
attr_reader :url, :params, :page, :templates, :routes, :events
|
35
|
+
attr_reader :url, :params, :page, :templates, :routes, :events
|
35
36
|
|
36
37
|
def initialize
|
37
|
-
@model_classes = {}
|
38
|
-
|
39
38
|
# Run the code to setup the page
|
40
39
|
@page = Model.new
|
41
40
|
|
@@ -139,14 +138,6 @@ module Volt
|
|
139
138
|
|
140
139
|
attr_reader :events
|
141
140
|
|
142
|
-
def add_model(model_name)
|
143
|
-
model_name = model_name.camelize.to_sym
|
144
|
-
@model_classes[model_name] = Object.const_get(model_name)
|
145
|
-
rescue NameError => e
|
146
|
-
# Handle if the model is user (Volt's provided user model is scoped under Volt::)
|
147
|
-
raise unless model_name == :User
|
148
|
-
end
|
149
|
-
|
150
141
|
def add_template(name, template, bindings)
|
151
142
|
@templates ||= {}
|
152
143
|
|
@@ -159,7 +150,6 @@ module Volt
|
|
159
150
|
unless @templates[name]
|
160
151
|
@templates[name] = { 'html' => template, 'bindings' => bindings }
|
161
152
|
end
|
162
|
-
# puts "Add Template: #{name}"
|
163
153
|
end
|
164
154
|
|
165
155
|
def add_routes(&block)
|
@@ -210,7 +200,7 @@ module Volt
|
|
210
200
|
end
|
211
201
|
end
|
212
202
|
rescue => e
|
213
|
-
|
203
|
+
Volt.logger.error("Unable to restore: #{e.inspect}")
|
214
204
|
end
|
215
205
|
end
|
216
206
|
|
@@ -218,8 +208,13 @@ module Volt
|
|
218
208
|
$page = Page.new
|
219
209
|
|
220
210
|
# Call start once the page is loaded
|
221
|
-
Document.ready? do
|
211
|
+
# Document.ready? do
|
212
|
+
# $page.start
|
213
|
+
# end
|
214
|
+
|
215
|
+
# For some reason Document.ready? (using opal-jquery) quit working.
|
216
|
+
`$(document).ready(function() {`
|
222
217
|
$page.start
|
223
|
-
|
218
|
+
`});`
|
224
219
|
end
|
225
220
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'volt/reactive/eventable'
|
2
|
+
|
3
|
+
module Volt
|
4
|
+
# ClassEventable behaves like Eventable, except events can be bound with a class #on method.
|
5
|
+
# When triggered on an instance, the self in the block will be the instance it was triggered
|
6
|
+
# on. This allows classes to easy setup listeners.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# class Post
|
10
|
+
# on(:create) do
|
11
|
+
# deny if owner?
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
module ClassEventable
|
15
|
+
module ClassMethods
|
16
|
+
# Eventable also provides a static version of on, which allows you to setup on
|
17
|
+
# events at the class level. When the event triggers, self will be set to the
|
18
|
+
# instance it was triggered on.
|
19
|
+
def on(*events, &callback)
|
20
|
+
raise '.on requires an event' if events.size == 0
|
21
|
+
|
22
|
+
listener = Listener.new(self, events, callback)
|
23
|
+
|
24
|
+
self.__listeners__ ||= {}
|
25
|
+
|
26
|
+
events.each do |event|
|
27
|
+
listeners = self.__listeners__
|
28
|
+
listeners[event] ||= []
|
29
|
+
listeners[event] << listener
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove_listener(event, listener)
|
34
|
+
listeners = self.__listeners__
|
35
|
+
if listeners
|
36
|
+
listeners[event].delete(listener)
|
37
|
+
|
38
|
+
if listeners[event].size == 0
|
39
|
+
# No registered listeners now on this event
|
40
|
+
listeners.delete(event)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
# Extend trigger! to also trigger class listeners
|
48
|
+
def trigger!(event, *args)
|
49
|
+
event = event.to_sym
|
50
|
+
|
51
|
+
super
|
52
|
+
|
53
|
+
if (klass_listeners = self.class.__listeners__)
|
54
|
+
klass_listeners[event].dup.each do |listener|
|
55
|
+
# Call each class listener with self set to the current instance
|
56
|
+
listener.instance_call(self, *args)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.included(base)
|
63
|
+
base.class_attribute :__listeners__
|
64
|
+
|
65
|
+
# Include the base eventable so the class can be triggered on
|
66
|
+
base.send :include, Volt::Eventable
|
67
|
+
base.send :extend, ClassMethods
|
68
|
+
base.send :include, InstanceMethods
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -11,12 +11,14 @@ module Volt
|
|
11
11
|
@@current
|
12
12
|
end
|
13
13
|
|
14
|
+
# @param [Proc] the code to run when the computation needs to compute
|
14
15
|
def initialize(computation)
|
15
16
|
@computation = computation
|
16
17
|
@invalidations = []
|
17
18
|
end
|
18
19
|
|
19
|
-
# Runs the computation
|
20
|
+
# Runs the computation, called on initial run and
|
21
|
+
# when changed!
|
20
22
|
def compute!
|
21
23
|
@invalidated = false
|
22
24
|
|
@@ -24,7 +26,12 @@ module Volt
|
|
24
26
|
|
25
27
|
@computing = true
|
26
28
|
run_in do
|
27
|
-
@computation.
|
29
|
+
if @computation.arity > 0
|
30
|
+
# Pass in the Computation so it can be canceled from within
|
31
|
+
@computation.call(self)
|
32
|
+
else
|
33
|
+
@computation.call
|
34
|
+
end
|
28
35
|
end
|
29
36
|
@computing = false
|
30
37
|
end
|
@@ -49,7 +56,7 @@ module Volt
|
|
49
56
|
unless @invalidated
|
50
57
|
@invalidated = true
|
51
58
|
|
52
|
-
|
59
|
+
unless @stopped || @computing
|
53
60
|
@@flush_queue << self
|
54
61
|
|
55
62
|
# If we are in the browser, we queue a flush for the next tick
|
@@ -79,16 +86,24 @@ module Volt
|
|
79
86
|
def run_in
|
80
87
|
previous = Computation.current
|
81
88
|
Computation.current = self
|
82
|
-
|
83
|
-
|
89
|
+
begin
|
90
|
+
yield
|
91
|
+
ensure
|
92
|
+
Computation.current = previous
|
93
|
+
end
|
94
|
+
|
84
95
|
self
|
85
96
|
end
|
86
97
|
|
98
|
+
# Run a block without tracking any dependencies
|
87
99
|
def self.run_without_tracking
|
88
100
|
previous = Computation.current
|
89
101
|
Computation.current = nil
|
90
|
-
|
91
|
-
|
102
|
+
begin
|
103
|
+
return_value = yield
|
104
|
+
ensure
|
105
|
+
Computation.current = previous
|
106
|
+
end
|
92
107
|
return_value
|
93
108
|
end
|
94
109
|
|
@@ -118,9 +133,63 @@ end
|
|
118
133
|
|
119
134
|
class Proc
|
120
135
|
def watch!
|
121
|
-
Volt::Computation.new(self)
|
122
|
-
|
123
|
-
|
136
|
+
computation = Volt::Computation.new(self)
|
137
|
+
|
138
|
+
# Initial run
|
139
|
+
computation.compute!
|
140
|
+
|
141
|
+
# return the computation
|
142
|
+
computation
|
143
|
+
end
|
144
|
+
|
145
|
+
# Watches a proc until the value returned equals the passed
|
146
|
+
# in value. When the value matches, the block is called.
|
147
|
+
#
|
148
|
+
# @param the value to match
|
149
|
+
# @return [Volt::Computation] the initial computation is returned.
|
150
|
+
def watch_until!(value, &block)
|
151
|
+
computation = -> do
|
152
|
+
# First fetch the value
|
153
|
+
result = self.call
|
154
|
+
|
155
|
+
if result == value
|
156
|
+
# Values match
|
157
|
+
|
158
|
+
# call the block
|
159
|
+
block.call
|
160
|
+
|
161
|
+
# stop the computation
|
162
|
+
computation.stop
|
163
|
+
end
|
164
|
+
end.watch!
|
165
|
+
|
166
|
+
computation
|
167
|
+
end
|
168
|
+
|
169
|
+
# Does an watch and if the result is a promise, resolves the promise.
|
170
|
+
# #watch_and_resolve! takes a block that will be passed the resolved results
|
171
|
+
# of the proc.
|
172
|
+
#
|
173
|
+
# Example:
|
174
|
+
# -> { }
|
175
|
+
def watch_and_resolve!
|
176
|
+
unless block_given?
|
177
|
+
raise "watch_and_resolve! requires a block to call when the value is resolved or another value other than a promise is returned in the watch."
|
124
178
|
end
|
179
|
+
|
180
|
+
computation = Proc.new do
|
181
|
+
result = self.call
|
182
|
+
|
183
|
+
if result.is_a?(Promise)
|
184
|
+
result.then do |final|
|
185
|
+
yield(final)
|
186
|
+
end
|
187
|
+
else
|
188
|
+
yield(result)
|
189
|
+
end
|
190
|
+
end.watch!
|
191
|
+
|
192
|
+
# Return the computation
|
193
|
+
computation
|
125
194
|
end
|
126
195
|
end
|
@@ -3,12 +3,9 @@ require 'set'
|
|
3
3
|
|
4
4
|
class Set
|
5
5
|
def delete(o)
|
6
|
-
@hash.delete(o)
|
7
|
-
end
|
8
|
-
|
9
|
-
def delete?(o)
|
10
6
|
if include?(o)
|
11
|
-
delete(o)
|
7
|
+
@hash.delete(o)
|
8
|
+
true
|
12
9
|
else
|
13
10
|
nil
|
14
11
|
end
|
@@ -28,9 +25,21 @@ class Set
|
|
28
25
|
end
|
29
26
|
|
30
27
|
module Volt
|
28
|
+
# Dependencies are used to track the current computation so it can be re-run
|
29
|
+
# at a later point if this dependency changes.
|
30
|
+
#
|
31
|
+
# You can also pass an on_dep and on_stop_dep proc's to #initialize.
|
31
32
|
class Dependency
|
32
|
-
|
33
|
+
# Setup a new dependency.
|
34
|
+
#
|
35
|
+
# @param on_dep [Proc] a proc to be called the first time a computation depends
|
36
|
+
# on this dependency.
|
37
|
+
# @param on_stop_dep [Proc] a proc to be called when no computations are depending
|
38
|
+
# on this dependency anymore.
|
39
|
+
def initialize(on_dep=nil, on_stop_dep=nil)
|
33
40
|
@dependencies = Set.new
|
41
|
+
@on_dep = on_dep
|
42
|
+
@on_stop_dep = on_stop_dep
|
34
43
|
end
|
35
44
|
|
36
45
|
def depend
|
@@ -42,10 +51,18 @@ module Volt
|
|
42
51
|
added = @dependencies.add?(current)
|
43
52
|
|
44
53
|
if added
|
45
|
-
#
|
54
|
+
# The first time the dependency is depended on by this computation, we call on_dep
|
55
|
+
@on_dep.call if @on_dep && @dependencies.size == 1
|
56
|
+
|
46
57
|
current.on_invalidate do
|
47
58
|
# If @dependencies is nil, this Dependency has been removed
|
48
|
-
|
59
|
+
if @dependencies
|
60
|
+
# For set, .delete returns a boolean if it was deleted
|
61
|
+
deleted = @dependencies.delete(current)
|
62
|
+
|
63
|
+
# Call on stop dep if no more deps
|
64
|
+
@on_stop_dep.call if @on_stop_dep && deleted && @dependencies.size == 0
|
65
|
+
end
|
49
66
|
end
|
50
67
|
end
|
51
68
|
end
|
@@ -60,6 +77,8 @@ module Volt
|
|
60
77
|
@dependencies = Set.new
|
61
78
|
|
62
79
|
deps.each(&:invalidate!)
|
80
|
+
|
81
|
+
@on_stop_dep.call if @on_stop_dep
|
63
82
|
end
|
64
83
|
|
65
84
|
# Called when a dependency is no longer needed
|
@@ -2,9 +2,9 @@ module Volt
|
|
2
2
|
# Listeners are returned from #on on a class with Eventable included.
|
3
3
|
# Listeners can be stopped by calling #remove
|
4
4
|
class Listener
|
5
|
-
def initialize(klass,
|
5
|
+
def initialize(klass, events, callback)
|
6
6
|
@klass = klass
|
7
|
-
@
|
7
|
+
@events = events
|
8
8
|
@callback = callback
|
9
9
|
end
|
10
10
|
|
@@ -12,10 +12,17 @@ module Volt
|
|
12
12
|
@callback.call(*args) unless @removed
|
13
13
|
end
|
14
14
|
|
15
|
+
# Call the callback with self set to instance
|
16
|
+
def instance_call(instance, *args)
|
17
|
+
instance.instance_exec(*args, &@callback)
|
18
|
+
end
|
19
|
+
|
15
20
|
def remove
|
16
21
|
@removed = true
|
17
22
|
|
18
|
-
@
|
23
|
+
@events.each do |event|
|
24
|
+
@klass.remove_listener(event, self)
|
25
|
+
end
|
19
26
|
|
20
27
|
# Make things easier on the GC
|
21
28
|
@klass = nil
|
@@ -23,7 +30,7 @@ module Volt
|
|
23
30
|
end
|
24
31
|
|
25
32
|
def inspect
|
26
|
-
"<Listener:#{object_id}
|
33
|
+
"<Listener:#{object_id} events=#{@events}>"
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
@@ -35,37 +42,44 @@ module Volt
|
|
35
42
|
# the class, it will trigger any listener with the same event name.
|
36
43
|
#
|
37
44
|
# returns: a listener that has a #remove method to stop the listener.
|
38
|
-
def on(
|
39
|
-
event
|
40
|
-
|
45
|
+
def on(*events, &callback)
|
46
|
+
raise '.on requires an event' if events.size == 0
|
47
|
+
|
48
|
+
listener = Listener.new(self, events, callback)
|
49
|
+
|
41
50
|
@listeners ||= {}
|
42
|
-
@listeners[event] ||= []
|
43
|
-
@listeners[event] << listener
|
44
51
|
|
45
|
-
|
46
|
-
|
52
|
+
events.each do |event|
|
53
|
+
event = event.to_sym
|
54
|
+
@listeners[event] ||= []
|
55
|
+
@listeners[event] << listener
|
56
|
+
|
57
|
+
first_for_event = @listeners[event].size == 1
|
58
|
+
first = first_for_event && @listeners.size == 1
|
47
59
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
60
|
+
# Let the included class know that an event was registered. (if it cares)
|
61
|
+
if self.respond_to?(:event_added)
|
62
|
+
# call event added passing the event, the scope, and a boolean if it
|
63
|
+
# is the first time this event has been added.
|
64
|
+
event_added(event, first, first_for_event)
|
65
|
+
end
|
53
66
|
end
|
54
67
|
|
55
68
|
listener
|
56
69
|
end
|
57
70
|
|
71
|
+
|
58
72
|
# Triggers event on the class the module was includeded. Any .on listeners
|
59
73
|
# will have their block called passing in *args.
|
60
74
|
def trigger!(event, *args)
|
61
75
|
event = event.to_sym
|
62
76
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
77
|
+
if @listeners && @listeners[event]
|
78
|
+
# TODO: We have to dup here because one trigger might remove another
|
79
|
+
@listeners[event].dup.each do |listener|
|
80
|
+
# Call the event on each listener
|
81
|
+
listener.call(*args)
|
82
|
+
end
|
69
83
|
end
|
70
84
|
end
|
71
85
|
|
@@ -91,7 +91,7 @@ module Volt
|
|
91
91
|
|
92
92
|
# TODO: Handle a range
|
93
93
|
def [](index)
|
94
|
-
# Handle a negative index
|
94
|
+
# Handle a negative index, depend on size
|
95
95
|
index = size + index if index < 0
|
96
96
|
|
97
97
|
# Get or create the dependency
|
@@ -125,7 +125,7 @@ module Volt
|
|
125
125
|
# Handle a negative index
|
126
126
|
index = size + index if index < 0
|
127
127
|
|
128
|
-
model
|
128
|
+
model = @array.delete_at(index)
|
129
129
|
|
130
130
|
# Remove the dependency for that cell, and #remove it
|
131
131
|
index_deps = @array_deps.delete_at(index)
|
@@ -244,7 +244,6 @@ module Volt
|
|
244
244
|
def trigger_for_index!(index)
|
245
245
|
# Trigger a change for the cell
|
246
246
|
dep = @array_deps[index]
|
247
|
-
|
248
247
|
dep.changed! if dep
|
249
248
|
end
|
250
249
|
|
@@ -13,10 +13,13 @@ module Volt
|
|
13
13
|
@hash == val
|
14
14
|
end
|
15
15
|
|
16
|
+
def blank?
|
17
|
+
@hash.blank?
|
18
|
+
end
|
19
|
+
|
16
20
|
# TODO: We should finish off this class for reactivity
|
17
21
|
def method_missing(method_name, *args, &block)
|
18
22
|
@all_deps.depend
|
19
|
-
|
20
23
|
@hash.send(method_name, *args, &block)
|
21
24
|
end
|
22
25
|
|
@@ -40,7 +43,9 @@ module Volt
|
|
40
43
|
end
|
41
44
|
|
42
45
|
def clear
|
43
|
-
|
46
|
+
# Don't use .each_key so we get a clone here since we are
|
47
|
+
# deleting as we go.
|
48
|
+
@hash.keys.each do |key|
|
44
49
|
delete(key)
|
45
50
|
end
|
46
51
|
|
@@ -62,7 +67,7 @@ module Volt
|
|
62
67
|
|
63
68
|
def inspect
|
64
69
|
@all_deps.depend
|
65
|
-
"
|
70
|
+
"#<#{self.class.name} #{@hash.inspect}>"
|
66
71
|
end
|
67
72
|
end
|
68
73
|
end
|