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.
- checksums.yaml +4 -4
- data/.travis.yml +8 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile +8 -0
- data/Guardfile +2 -2
- data/Readme.md +139 -136
- data/VERSION +1 -1
- data/app/volt/assets/js/setImmediate.js +175 -0
- data/app/volt/tasks/live_query/data_store.rb +0 -2
- data/app/volt/tasks/live_query/live_query.rb +4 -4
- data/docs/GETTING_STARTED.md +24 -3
- data/docs/WHY.md +1 -22
- data/lib/volt.rb +20 -1
- data/lib/volt/console.rb +20 -0
- data/lib/volt/controllers/model_controller.rb +25 -11
- data/lib/volt/extra_core/object.rb +2 -14
- data/lib/volt/extra_core/string.rb +4 -0
- data/lib/volt/models.rb +0 -1
- data/lib/volt/models/array_model.rb +8 -16
- data/lib/volt/models/cursor.rb +1 -1
- data/lib/volt/models/model.rb +40 -60
- data/lib/volt/models/model_hash_behaviour.rb +10 -24
- data/lib/volt/models/model_helpers.rb +2 -2
- data/lib/volt/models/model_state.rb +1 -1
- data/lib/volt/models/model_wrapper.rb +4 -4
- data/lib/volt/models/persistors/array_store.rb +44 -28
- data/lib/volt/models/persistors/base.rb +1 -1
- data/lib/volt/models/persistors/model_store.rb +1 -1
- data/lib/volt/models/persistors/params.rb +5 -1
- data/lib/volt/models/persistors/query/query_listener.rb +2 -0
- data/lib/volt/models/persistors/store.rb +3 -2
- data/lib/volt/models/persistors/store_state.rb +7 -2
- data/lib/volt/models/url.rb +35 -29
- data/lib/volt/models/validations.rb +7 -17
- data/lib/volt/page/bindings/attribute_binding.rb +57 -39
- data/lib/volt/page/bindings/base_binding.rb +0 -14
- data/lib/volt/page/bindings/content_binding.rb +15 -18
- data/lib/volt/page/bindings/each_binding.rb +67 -34
- data/lib/volt/page/bindings/if_binding.rb +15 -12
- data/lib/volt/page/bindings/template_binding.rb +77 -59
- data/lib/volt/page/bindings/template_binding/grouped_controllers.rb +19 -4
- data/lib/volt/page/channel.rb +22 -38
- data/lib/volt/page/channel_stub.rb +3 -6
- data/lib/volt/page/page.rb +24 -26
- data/lib/volt/page/string_template_renderer.rb +46 -0
- data/lib/volt/page/sub_context.rb +7 -1
- data/lib/volt/page/targets/binding_document/component_node.rb +11 -9
- data/lib/volt/page/tasks.rb +3 -2
- data/lib/volt/page/url_tracker.rb +4 -3
- data/lib/volt/reactive/computation.rb +131 -0
- data/lib/volt/reactive/dependency.rb +71 -0
- data/lib/volt/reactive/eventable.rb +82 -0
- data/lib/volt/reactive/hash_dependency.rb +36 -0
- data/lib/volt/{controllers → reactive}/reactive_accessors.rb +8 -11
- data/lib/volt/reactive/reactive_array.rb +100 -193
- data/lib/volt/reactive/reactive_hash.rb +49 -0
- data/lib/volt/server/html_parser/attribute_scope.rb +24 -4
- data/lib/volt/server/html_parser/if_view_scope.rb +15 -15
- data/lib/volt/server/html_parser/view_scope.rb +31 -1
- data/spec/apps/kitchen_sink/Gemfile +4 -8
- data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +8 -0
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +8 -1
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +8 -0
- data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +73 -0
- data/spec/apps/kitchen_sink/app/main/views/main/index.html +6 -1
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +26 -6
- data/spec/apps/kitchen_sink/app/main/views/main/store.html +6 -0
- data/spec/controllers/reactive_accessors_spec.rb +13 -15
- data/spec/integration/bindings_spec.rb +159 -0
- data/spec/integration/templates_spec.rb +15 -0
- data/spec/models/model_spec.rb +130 -228
- data/spec/reactive/computation_spec.rb +63 -0
- data/spec/reactive/dependency_spec.rb +5 -0
- data/spec/reactive/eventable_spec.rb +48 -0
- data/spec/reactive/reactive_array_spec.rb +97 -0
- data/spec/router/routes_spec.rb +26 -27
- data/spec/server/html_parser/view_parser_spec.rb +3 -21
- data/spec/server/rack/asset_files_spec.rb +1 -1
- data/templates/project/app/main/views/main/main.html +2 -2
- metadata +29 -41
- data/lib/volt/extra_core/time.rb +0 -16
- data/lib/volt/page/draw_cycle.rb +0 -31
- data/lib/volt/page/memory_test.rb +0 -26
- data/lib/volt/page/reactive_template.rb +0 -32
- data/lib/volt/reactive/array_extensions.rb +0 -12
- data/lib/volt/reactive/destructive_methods.rb +0 -19
- data/lib/volt/reactive/event_chain.rb +0 -125
- data/lib/volt/reactive/events.rb +0 -216
- data/lib/volt/reactive/object_tracking.rb +0 -14
- data/lib/volt/reactive/reactive_block.rb +0 -88
- data/lib/volt/reactive/reactive_generator.rb +0 -44
- data/lib/volt/reactive/reactive_tags.rb +0 -71
- data/lib/volt/reactive/reactive_value.rb +0 -427
- data/lib/volt/reactive/string_extensions.rb +0 -31
- data/spec/integration/test_integration_spec.rb +0 -14
- data/spec/models/event_chain_spec.rb +0 -150
- data/spec/models/model_buffers_spec.rb +0 -9
- data/spec/models/old_model_spec.rb +0 -67
- data/spec/models/reactive_array_spec.rb +0 -364
- data/spec/models/reactive_block_spec.rb +0 -13
- data/spec/models/reactive_call_times_spec.rb +0 -28
- data/spec/models/reactive_generator_spec.rb +0 -58
- data/spec/models/reactive_tags_spec.rb +0 -35
- data/spec/models/reactive_value_spec.rb +0 -370
- data/spec/models/store_spec.rb +0 -16
- data/spec/models/string_extensions_spec.rb +0 -57
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Provides methods for objects that store reactive value's to trigger
|
|
2
|
-
module ObjectTracking
|
|
3
|
-
def __setup_tracking(key, value)
|
|
4
|
-
if value.reactive?
|
|
5
|
-
# TODO: We should build this in so it fires just for the current index.
|
|
6
|
-
# Currently this is a big performance hit.
|
|
7
|
-
chain_listener = event_chain.add_object(value.reactive_manager) do |event, filter, *args|
|
|
8
|
-
yield(event, key, args)
|
|
9
|
-
end
|
|
10
|
-
@reactive_element_listeners ||= {}
|
|
11
|
-
@reactive_element_listeners[key] = chain_listener
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
class ReactiveBlock
|
|
2
|
-
include ReactiveTags
|
|
3
|
-
|
|
4
|
-
def reactive?
|
|
5
|
-
true
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def initialize(source, check_block, run_block)
|
|
9
|
-
@source = ReactiveValue.new(source)
|
|
10
|
-
@check_block = check_block
|
|
11
|
-
@run_block = run_block
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def cur
|
|
15
|
-
val = @run_block.call(@source)
|
|
16
|
-
|
|
17
|
-
return val
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def setup_listeners
|
|
21
|
-
@cell_trackers = []
|
|
22
|
-
@setup = false
|
|
23
|
-
@added_tracker = @source.on('added') do |_, index|
|
|
24
|
-
change_cell_count(@source.size.cur)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
@removed_tracker = @source.on('removed') do |_, index|
|
|
28
|
-
change_cell_count(@source.size.cur)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
@setup = true
|
|
32
|
-
|
|
33
|
-
# Initial cell tracking
|
|
34
|
-
change_cell_count(@source.size.cur)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# We need to make sure we're listening on the result from each cell,
|
|
38
|
-
# that way we can trigger when the value changes.
|
|
39
|
-
def change_cell_count(size)
|
|
40
|
-
current_size = @cell_trackers.size
|
|
41
|
-
|
|
42
|
-
if current_size < size
|
|
43
|
-
# Add trackers
|
|
44
|
-
|
|
45
|
-
current_size.upto(size-1) do |index|
|
|
46
|
-
# Get the reactive value for the index
|
|
47
|
-
val = @source[index]
|
|
48
|
-
|
|
49
|
-
result = @check_block.call(val)
|
|
50
|
-
|
|
51
|
-
@cell_trackers << result.on('changed') do
|
|
52
|
-
puts "RESULT CHANGED: #{index} - #{self.object_id}"
|
|
53
|
-
trigger!('changed')
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
elsif current_size > size
|
|
57
|
-
(current_size-1).downto(size) do |index|
|
|
58
|
-
@cell_trackers[index].remove
|
|
59
|
-
@cell_trackers.delete_at(index)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def teardown_listeners
|
|
66
|
-
@added_tracker.remove
|
|
67
|
-
@added_tracker = nil
|
|
68
|
-
|
|
69
|
-
@removed_tracker.remove
|
|
70
|
-
@removed_tracker = nil
|
|
71
|
-
|
|
72
|
-
change_cell_count(0)
|
|
73
|
-
|
|
74
|
-
@cell_trackers = nil
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def event_added(event, scope_provider, first, first_for_event)
|
|
78
|
-
setup_listeners if first
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def event_removed(event, last, last_for_event)
|
|
82
|
-
teardown_listeners if last
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def inspect
|
|
86
|
-
"@#{cur}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
class ReactiveGenerator
|
|
2
|
-
# Takes a hash and returns a ReactiveValue that depends on
|
|
3
|
-
# any ReactiveValue's inside of the hash (or children).
|
|
4
|
-
def self.from_hash(hash, skip_if_no_reactives=false)
|
|
5
|
-
reactives = find_reactives(hash)
|
|
6
|
-
|
|
7
|
-
if skip_if_no_reactives && reactives.size == 0
|
|
8
|
-
# There weren't any reactives, we can just use the hash
|
|
9
|
-
return hash
|
|
10
|
-
else
|
|
11
|
-
# Create a new reactive value that listens on all of its
|
|
12
|
-
# child reactive values.
|
|
13
|
-
value = ReactiveValue.new(hash)
|
|
14
|
-
|
|
15
|
-
reactives.each do |child|
|
|
16
|
-
value.reactive_manager.add_parent!(child)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
return value
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Recursively loop through the data, returning a list of all
|
|
24
|
-
# reactive values in the hash, array, etc..
|
|
25
|
-
def self.find_reactives(object)
|
|
26
|
-
found = []
|
|
27
|
-
if object.reactive?
|
|
28
|
-
found << object
|
|
29
|
-
|
|
30
|
-
found += find_reactives(object.cur)
|
|
31
|
-
elsif object.is_a?(Array)
|
|
32
|
-
object.each do |item|
|
|
33
|
-
found += find_reactives(item)
|
|
34
|
-
end
|
|
35
|
-
elsif object.is_a?(Hash)
|
|
36
|
-
object.each_pair do |key, value|
|
|
37
|
-
found += find_reactives(key)
|
|
38
|
-
found += find_reactives(value)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
return found.flatten
|
|
43
|
-
end
|
|
44
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
require 'volt/reactive/destructive_methods'
|
|
2
|
-
|
|
3
|
-
# ReactiveTags provide an easy way to specify how a class deals with
|
|
4
|
-
# reactive events and method calls.als
|
|
5
|
-
module ReactiveTags
|
|
6
|
-
class MethodTags
|
|
7
|
-
attr_accessor :destructive, :pass_reactive, :reacts_with
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
class MethodTagger
|
|
11
|
-
attr_reader :method_tags
|
|
12
|
-
|
|
13
|
-
def initialize
|
|
14
|
-
@method_tags = MethodTags.new
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def destructive!(&block)
|
|
18
|
-
@method_tags.destructive = block || true
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def pass_reactive!
|
|
22
|
-
@method_tags.pass_reactive = true
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
module ClassMethods
|
|
27
|
-
def tag_method(method_name, &block)
|
|
28
|
-
tagger = MethodTagger.new
|
|
29
|
-
|
|
30
|
-
tagger.instance_eval(&block)
|
|
31
|
-
|
|
32
|
-
@reactive_method_tags ||= {}
|
|
33
|
-
@reactive_method_tags[method_name.to_sym] = tagger.method_tags
|
|
34
|
-
|
|
35
|
-
# Track a destructive method
|
|
36
|
-
if tagger.method_tags.destructive
|
|
37
|
-
DestructiveMethods.add_method(method_name)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def tag_all_methods(&block)
|
|
42
|
-
tagger = MethodTagger.new
|
|
43
|
-
|
|
44
|
-
tagger.instance_eval(&block)
|
|
45
|
-
|
|
46
|
-
@reactive_method_tags ||= {}
|
|
47
|
-
@reactive_method_tags[:__all_methods] = tagger.method_tags
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Returns a reference to the tags on a method
|
|
52
|
-
def reactive_method_tag(method_name, tag_name, klass=self.class)
|
|
53
|
-
# Check to make sure we haven't gone above a class that has included
|
|
54
|
-
# ReactiveTags
|
|
55
|
-
return nil if !klass || !klass.method_defined?(:reactive_method_tag)
|
|
56
|
-
|
|
57
|
-
tags = klass.instance_variable_get('@reactive_method_tags')
|
|
58
|
-
|
|
59
|
-
if tags && (tag = tags[method_name.to_sym]) && (tag = tag.send(tag_name))
|
|
60
|
-
return tag
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
return self.reactive_method_tag(method_name, tag_name, klass.superclass)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def self.included(base)
|
|
68
|
-
base.send(:extend, ClassMethods)
|
|
69
|
-
base.send(:include, Events)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
require 'volt/reactive/events'
|
|
2
|
-
require 'volt/reactive/reactive_tags'
|
|
3
|
-
require 'volt/reactive/string_extensions'
|
|
4
|
-
require 'volt/reactive/array_extensions'
|
|
5
|
-
require 'volt/reactive/reactive_array'
|
|
6
|
-
require 'volt/reactive/destructive_methods'
|
|
7
|
-
require 'volt/reactive/reactive_generator'
|
|
8
|
-
|
|
9
|
-
class Object
|
|
10
|
-
def cur
|
|
11
|
-
self
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def reactive?
|
|
15
|
-
false
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
class ReactiveValue < BasicObject
|
|
20
|
-
# methods on ReactiveValues:
|
|
21
|
-
# reactive?, cur, with, on, data, trigger!
|
|
22
|
-
# - everything else is forwarded to the ReactiveManager
|
|
23
|
-
|
|
24
|
-
# Methods we should skip wrapping the results in
|
|
25
|
-
# We skip .hash because in uniq it has .to_int called on it, which needs to
|
|
26
|
-
# return a Fixnum instance.
|
|
27
|
-
# :hash - needs something where .to_int can be called on it and it will
|
|
28
|
-
# return an int
|
|
29
|
-
# :methods- needs to return a straight up array to work with irb tab completion
|
|
30
|
-
# :eql? - needed for .uniq to work correctly
|
|
31
|
-
# :to_ary - in some places ruby expects to get an array back from this method
|
|
32
|
-
SKIP_METHODS = [:object_id, :hash, :methods, :eql?, :respond_to?, :respond_to_missing?, :to_ary, :to_int]#, :instance_of?, :kind_of?, :to_s, :to_str]
|
|
33
|
-
|
|
34
|
-
def initialize(getter, setter=nil, scope=nil)
|
|
35
|
-
@reactive_manager = ::ReactiveManager.new(self, getter, setter, scope)
|
|
36
|
-
# @reactive_cache = {}
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def reactive?
|
|
40
|
-
true
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Proxy methods to the ReactiveManager. We want to have as few
|
|
44
|
-
# as possible methods on reactive values, so all other methods
|
|
45
|
-
# are forwarded to the object the reactive value points to.
|
|
46
|
-
[:cur, :cur=, :deep_cur, :on, :trigger!, :trigger_by_scope!, :with].each do |method_name|
|
|
47
|
-
define_method(method_name) do |*args, &block|
|
|
48
|
-
@reactive_manager.send(method_name, *args, &block)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def reactive_manager
|
|
53
|
-
@reactive_manager
|
|
54
|
-
end
|
|
55
|
-
alias_method :rm, :reactive_manager
|
|
56
|
-
|
|
57
|
-
def puts(*args)
|
|
58
|
-
::Object.send(:puts, *args)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def __is_destructive?(method_name)
|
|
62
|
-
last_char = method_name[-1]
|
|
63
|
-
if last_char == '=' && method_name[-2] != '='
|
|
64
|
-
# Method is an assignment (and not a comparator ==)
|
|
65
|
-
return true
|
|
66
|
-
elsif method_name.size > 1 && last_char == '!' || last_char == '<'
|
|
67
|
-
# Method is tagged as destructive, or is a push ( << )
|
|
68
|
-
return true
|
|
69
|
-
elsif ::DestructiveMethods.might_be_destructive?(method_name)
|
|
70
|
-
# Method may be destructive, check if it actually is on the current value
|
|
71
|
-
# TODO: involves a call to cur
|
|
72
|
-
return reactive_manager.check_tag(method_name, :destructive, self.cur)
|
|
73
|
-
else
|
|
74
|
-
return false
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def method_missing(method_name, *args, &block)
|
|
79
|
-
# Unroll send into a direct call
|
|
80
|
-
if method_name == :send
|
|
81
|
-
method_name, *args = args
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# result = @reactive_cache[[method_name, args.map(&:object_id)]]
|
|
85
|
-
# return result if result
|
|
86
|
-
|
|
87
|
-
# For some methods, we pass directly to the current object. This
|
|
88
|
-
# helps ReactiveValue's be well behaved ruby citizens.
|
|
89
|
-
# Also skip if this is a destructive method
|
|
90
|
-
if SKIP_METHODS.include?(method_name) || __is_destructive?(method_name)
|
|
91
|
-
current_obj = self.cur
|
|
92
|
-
|
|
93
|
-
# Unwrap arguments if the method doesn't want reactive values
|
|
94
|
-
pass_args = reactive_manager.unwrap_if_pass_reactive(args, method_name, current_obj)
|
|
95
|
-
|
|
96
|
-
return current_obj.__send__(method_name, *pass_args, &block)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
result = @reactive_manager.with_and_options(args) do |val, in_args|
|
|
100
|
-
# Unwrap arguments if the method doesn't want reactive values
|
|
101
|
-
# TODO: Should cache the lookup on pass_reactive
|
|
102
|
-
pass_args = reactive_manager.unwrap_if_pass_reactive(in_args, method_name, val)
|
|
103
|
-
|
|
104
|
-
val.__send__(method_name, *pass_args, &block)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
manager = result.reactive_manager
|
|
108
|
-
|
|
109
|
-
setup_setter(manager, method_name, args)
|
|
110
|
-
|
|
111
|
-
manager.set_scope!([method_name, *args, block])
|
|
112
|
-
|
|
113
|
-
# result = result.with(block_reactives) if block
|
|
114
|
-
|
|
115
|
-
# if args.size == 0 || method_name == :[]
|
|
116
|
-
# @reactive_cache[[method_name, args.map(&:object_id)]] = result
|
|
117
|
-
# end
|
|
118
|
-
|
|
119
|
-
return result
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def setup_setter(manager, method_name, args)
|
|
123
|
-
# See if we can automatically create a setter. If we are fetching a
|
|
124
|
-
# value via a read, we can probably reassign it with .name=
|
|
125
|
-
if args.size == 0
|
|
126
|
-
# TODO: At the moment we are defining a setter on all "reads", this
|
|
127
|
-
# probably has some performance implications
|
|
128
|
-
manager.setter! do |val|
|
|
129
|
-
# Call setter
|
|
130
|
-
self.cur.send(:"#{method_name}=", val)
|
|
131
|
-
end
|
|
132
|
-
elsif args.size == 1 && method_name == :[]
|
|
133
|
-
manager.setter! do |val|
|
|
134
|
-
# Call an array setter
|
|
135
|
-
self.cur.send(:"#{method_name}=", args[0], val)
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def respond_to_missing?(name, include_private=false)
|
|
141
|
-
cur.respond_to?(name)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def inspect
|
|
145
|
-
"@#{cur.inspect}"
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def pretty_inspect
|
|
149
|
-
inspect
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Not 100% sure why, but we need to define this directly, it doesn't call
|
|
153
|
-
# on method missing
|
|
154
|
-
def ==(val)
|
|
155
|
-
method_missing(:==, val)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# TODO: this is broke in opal
|
|
159
|
-
def !
|
|
160
|
-
method_missing(:!)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def to_s
|
|
164
|
-
cur.to_s
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def coerce(other)
|
|
168
|
-
if other.reactive?
|
|
169
|
-
return [other, self]
|
|
170
|
-
else
|
|
171
|
-
wrapped_object = ::ReactiveValue.new(other, [])
|
|
172
|
-
return [wrapped_object, self]
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# Return a new reactive value that listens for changes on any
|
|
177
|
-
# ReactiveValues inside of its children (hash values, array items, etc..)
|
|
178
|
-
# This is useful if someone is passing in a set of options, but the main
|
|
179
|
-
# hash isn't a ReactiveValue, but you want to listen for changes inside
|
|
180
|
-
# of the hash.
|
|
181
|
-
#
|
|
182
|
-
# skip_if_no_reactives lets you get back a non-reactive value in the event
|
|
183
|
-
# that there are no child reactive values.
|
|
184
|
-
def self.from_hash(hash, skip_if_no_reactives=false)
|
|
185
|
-
::ReactiveGenerator.from_hash(hash)
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
class ReactiveManager
|
|
190
|
-
include ::Events
|
|
191
|
-
|
|
192
|
-
attr_reader :scope, :parents
|
|
193
|
-
|
|
194
|
-
# When created, ReactiveValue's get a getter (a proc)
|
|
195
|
-
def initialize(reactive_value, getter, setter=nil, scope=nil)
|
|
196
|
-
@reactive_value = reactive_value
|
|
197
|
-
@getter = getter
|
|
198
|
-
@setter = setter
|
|
199
|
-
@scope = scope
|
|
200
|
-
|
|
201
|
-
@parents = []
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def reactive_value
|
|
205
|
-
@reactive_value
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def reactive?
|
|
209
|
-
true
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def inspect
|
|
213
|
-
"@<#{self.class.to_s}:#{object_id} #{cur.inspect}>"
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def reactive_object_id
|
|
217
|
-
@reactive_object_id ||= rand(100000)
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def event_added(event, scope, first, first_for_event)
|
|
222
|
-
# When the first event is registered, we need to start listening on our current object
|
|
223
|
-
# for it to publish events.
|
|
224
|
-
|
|
225
|
-
update_followers if first
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def event_removed(event, last, last_for_event)
|
|
229
|
-
# If no one is listening on the reactive value, then we don't need to listen on our
|
|
230
|
-
# current object for events, because no one cares.
|
|
231
|
-
|
|
232
|
-
remove_followers if last
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# Fetch the current value
|
|
237
|
-
def cur(shallow=false, ignore_cache=false)
|
|
238
|
-
# Use cache if it is cached
|
|
239
|
-
if @cur_cache && !shallow && !ignore_cache
|
|
240
|
-
# We might be caching another reactive value, so we just set
|
|
241
|
-
# it as the result and let it get unwrapped.
|
|
242
|
-
result = @cur_cache
|
|
243
|
-
else
|
|
244
|
-
if @getter.class == ::Proc
|
|
245
|
-
# Get the current value, capture any errors
|
|
246
|
-
begin
|
|
247
|
-
result = @getter.call
|
|
248
|
-
rescue => e
|
|
249
|
-
result = e
|
|
250
|
-
end
|
|
251
|
-
else
|
|
252
|
-
# getter is just an object, return it
|
|
253
|
-
result = @getter
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
if !shallow && result.reactive?
|
|
258
|
-
# Unwrap any stored reactive values
|
|
259
|
-
result = result.cur
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
return result
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def update_followers
|
|
267
|
-
return if @setting_up
|
|
268
|
-
if has_listeners?
|
|
269
|
-
current_obj = cur(true, true)
|
|
270
|
-
should_attach = current_obj.respond_to?(:on)
|
|
271
|
-
|
|
272
|
-
if should_attach
|
|
273
|
-
if !@cur_cache || current_obj.object_id != @cur_cache.object_id
|
|
274
|
-
remove_followers
|
|
275
|
-
|
|
276
|
-
@setting_up = true
|
|
277
|
-
@cur_cache_chain_listener = self.event_chain.add_object(current_obj)
|
|
278
|
-
@setting_up = nil
|
|
279
|
-
end
|
|
280
|
-
else
|
|
281
|
-
remove_followers
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
# Store current if we have listeners
|
|
285
|
-
@cur_cache = current_obj
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
def remove_followers
|
|
291
|
-
# Remove from previous
|
|
292
|
-
if @cur_cache
|
|
293
|
-
@cur_cache = nil
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
if @cur_cache_chain_listener
|
|
297
|
-
@cur_cache_chain_listener.remove
|
|
298
|
-
@cur_cache_chain_listener = nil
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
def cur=(val)
|
|
303
|
-
if @setter
|
|
304
|
-
@setter.call(val)
|
|
305
|
-
# update_followers
|
|
306
|
-
elsif @scope == nil
|
|
307
|
-
@getter = val
|
|
308
|
-
@setter = nil
|
|
309
|
-
|
|
310
|
-
# update_followers
|
|
311
|
-
trigger!('changed')
|
|
312
|
-
else
|
|
313
|
-
raise "Value can not be updated"
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
# Returns a copy of the object with where all ReactiveValue's are replaced
|
|
319
|
-
# with their current value.
|
|
320
|
-
# NOTE: Classes need to implement their own deep_cur method for this to work,
|
|
321
|
-
# it works out of the box with arrays and hashes.
|
|
322
|
-
def deep_cur
|
|
323
|
-
self.cur.deep_cur
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# Method calls can be tagged so the reactive value knows
|
|
327
|
-
# how to handle them. This lets you check the state of
|
|
328
|
-
# the tags.
|
|
329
|
-
def check_tag(method_name, tag_name, current_obj)
|
|
330
|
-
if current_obj.respond_to?(:reactive_method_tag)
|
|
331
|
-
tag = current_obj.reactive_method_tag(method_name, tag_name)
|
|
332
|
-
|
|
333
|
-
unless tag
|
|
334
|
-
# Get the tag from the all methods if its not directly specified
|
|
335
|
-
tag = current_obj.reactive_method_tag(:__all_methods, tag_name)
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# Evaluate now if its a proc
|
|
339
|
-
tag = tag.call(method_name) if tag.class == ::Proc
|
|
340
|
-
|
|
341
|
-
return tag
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
return nil
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
def unwrap_if_pass_reactive(args, method_name, current_obj)
|
|
348
|
-
# Check to see if the method we're calling wants to receive reactive values.
|
|
349
|
-
pass_reactive = check_tag(method_name, :pass_reactive, current_obj)
|
|
350
|
-
|
|
351
|
-
# Unwrap arguments if the method doesn't want reactive values
|
|
352
|
-
return pass_reactive ? args : args.map{|v| v.cur }
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
# With returns a new reactive value dependent on any arguments passed in.
|
|
356
|
-
# If a block is passed in, the getter is the block its self, which will
|
|
357
|
-
# be passed the .cur and the .cur of any reactive arguments.
|
|
358
|
-
def with(*args, &block)
|
|
359
|
-
return with_and_options(args, &block)
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
def with_and_options(args, &block)
|
|
363
|
-
getter = @getter
|
|
364
|
-
setter = @setter
|
|
365
|
-
scope = @scope
|
|
366
|
-
|
|
367
|
-
if block
|
|
368
|
-
# If a block was passed in, the getter now becomes a proc that calls
|
|
369
|
-
# the passed in block with the right arguments.
|
|
370
|
-
getter = ::Proc.new do
|
|
371
|
-
# TODO: Calling cur every time
|
|
372
|
-
current_val = self.cur
|
|
373
|
-
|
|
374
|
-
if current_val.is_a?(Exception)
|
|
375
|
-
current_val
|
|
376
|
-
else
|
|
377
|
-
block.call(current_val, args)
|
|
378
|
-
end
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
# TODO: Make this work with custom setters
|
|
382
|
-
setter = nil
|
|
383
|
-
|
|
384
|
-
# Scope also gets set to nil, because now we should always retrigger this
|
|
385
|
-
# method because we don't know enough about what methods its calling.
|
|
386
|
-
scope = nil
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
new_val = ReactiveValue.new(getter, setter, scope)
|
|
390
|
-
|
|
391
|
-
# Add the ReactiveValue we're building from
|
|
392
|
-
new_val.reactive_manager.add_parent!(self)
|
|
393
|
-
|
|
394
|
-
# Add any reactive arguments as parents
|
|
395
|
-
args.select(&:reactive?).each do |arg|
|
|
396
|
-
new_val.reactive_manager.add_parent!(arg.reactive_manager)
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
return new_val
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
def add_parent!(parent)
|
|
403
|
-
@parents << parent
|
|
404
|
-
event_chain.add_object(parent)
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
def remove_parent!(parent)
|
|
408
|
-
@parents.delete(parent)
|
|
409
|
-
event_chain.remove_object(parent)
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
def set_scope!(new_scope)
|
|
414
|
-
@scope = new_scope
|
|
415
|
-
|
|
416
|
-
self
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
def set_scope(new_scope)
|
|
420
|
-
dup.scope!(new_scope)
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
# Sets the setter
|
|
424
|
-
def setter!(setter=nil, &block)
|
|
425
|
-
@setter = setter || block
|
|
426
|
-
end
|
|
427
|
-
end
|