volt 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +37 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +23 -0
- data/Readme.md +34 -0
- data/VERSION +1 -0
- data/bin/volt +4 -0
- data/docs/GETTING_STARTED.md +7 -0
- data/docs/GUIDE.md +33 -0
- data/lib/volt.rb +15 -0
- data/lib/volt/benchmark/benchmark.rb +25 -0
- data/lib/volt/cli.rb +34 -0
- data/lib/volt/console.rb +19 -0
- data/lib/volt/controllers/model_controller.rb +29 -0
- data/lib/volt/extra_core/array.rb +10 -0
- data/lib/volt/extra_core/blank.rb +88 -0
- data/lib/volt/extra_core/extra_core.rb +7 -0
- data/lib/volt/extra_core/numeric.rb +9 -0
- data/lib/volt/extra_core/object.rb +36 -0
- data/lib/volt/extra_core/string.rb +29 -0
- data/lib/volt/extra_core/stringify_keys.rb +7 -0
- data/lib/volt/extra_core/true_false.rb +44 -0
- data/lib/volt/extra_core/try.rb +31 -0
- data/lib/volt/models.rb +5 -0
- data/lib/volt/models/array_model.rb +37 -0
- data/lib/volt/models/model.rb +210 -0
- data/lib/volt/models/model_wrapper.rb +23 -0
- data/lib/volt/models/params.rb +67 -0
- data/lib/volt/models/url.rb +192 -0
- data/lib/volt/page/url_tracker.rb +36 -0
- data/lib/volt/reactive/array_extensions.rb +13 -0
- data/lib/volt/reactive/event_chain.rb +126 -0
- data/lib/volt/reactive/events.rb +283 -0
- data/lib/volt/reactive/object_tracker.rb +99 -0
- data/lib/volt/reactive/object_tracking.rb +15 -0
- data/lib/volt/reactive/reactive_array.rb +222 -0
- data/lib/volt/reactive/reactive_tags.rb +64 -0
- data/lib/volt/reactive/reactive_value.rb +368 -0
- data/lib/volt/reactive/string_extensions.rb +34 -0
- data/lib/volt/router/routes.rb +83 -0
- data/lib/volt/server.rb +121 -0
- data/lib/volt/server/binding_setup.rb +2 -0
- data/lib/volt/server/channel_handler.rb +31 -0
- data/lib/volt/server/component_handler.rb +88 -0
- data/lib/volt/server/if_binding_setup.rb +29 -0
- data/lib/volt/server/request_handler.rb +16 -0
- data/lib/volt/server/scope.rb +43 -0
- data/lib/volt/server/source_map_server.rb +31 -0
- data/lib/volt/server/template_parser.rb +452 -0
- data/lib/volt/store/mongo.rb +5 -0
- data/lib/volt/templates/attribute_binding.rb +110 -0
- data/lib/volt/templates/base_binding.rb +37 -0
- data/lib/volt/templates/channel.rb +48 -0
- data/lib/volt/templates/content_binding.rb +35 -0
- data/lib/volt/templates/document_events.rb +80 -0
- data/lib/volt/templates/each_binding.rb +115 -0
- data/lib/volt/templates/event_binding.rb +51 -0
- data/lib/volt/templates/if_binding.rb +74 -0
- data/lib/volt/templates/memory_test.rb +26 -0
- data/lib/volt/templates/page.rb +146 -0
- data/lib/volt/templates/reactive_template.rb +38 -0
- data/lib/volt/templates/render_queue.rb +5 -0
- data/lib/volt/templates/sub_context.rb +23 -0
- data/lib/volt/templates/targets/attribute_section.rb +33 -0
- data/lib/volt/templates/targets/attribute_target.rb +18 -0
- data/lib/volt/templates/targets/base_section.rb +14 -0
- data/lib/volt/templates/targets/binding_document/base_node.rb +3 -0
- data/lib/volt/templates/targets/binding_document/component_node.rb +112 -0
- data/lib/volt/templates/targets/binding_document/html_node.rb +11 -0
- data/lib/volt/templates/targets/dom_section.rb +147 -0
- data/lib/volt/templates/targets/dom_target.rb +11 -0
- data/lib/volt/templates/template_binding.rb +159 -0
- data/lib/volt/templates/template_renderer.rb +50 -0
- data/spec/models/event_chain_spec.rb +129 -0
- data/spec/models/model_spec.rb +340 -0
- data/spec/models/old_model_spec.rb +109 -0
- data/spec/models/reactive_array_spec.rb +262 -0
- data/spec/models/reactive_tags_spec.rb +35 -0
- data/spec/models/reactive_value_spec.rb +336 -0
- data/spec/models/string_extensions_spec.rb +57 -0
- data/spec/router/routes_spec.rb +24 -0
- data/spec/server/template_parser_spec.rb +50 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/store/mongo_spec.rb +4 -0
- data/spec/templates/targets/binding_document/component_node_spec.rb +18 -0
- data/spec/templates/template_binding_spec.rb +98 -0
- data/templates/.gitignore +12 -0
- data/templates/Gemfile.tt +8 -0
- data/templates/app/.empty_directory +0 -0
- data/templates/app/home/config/routes.rb +1 -0
- data/templates/app/home/controllers/index_controller.rb +5 -0
- data/templates/app/home/css/.empty_directory +0 -0
- data/templates/app/home/models/.empty_directory +0 -0
- data/templates/app/home/views/index/about.html +9 -0
- data/templates/app/home/views/index/home.html +7 -0
- data/templates/app/home/views/index/index.html +28 -0
- data/templates/config.ru +4 -0
- data/templates/public/css/ansi.css +0 -0
- data/templates/public/css/bootstrap-theme.css +459 -0
- data/templates/public/css/bootstrap.css +7098 -0
- data/templates/public/css/jumbotron.css +79 -0
- data/templates/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/templates/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/templates/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/templates/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/templates/public/index.html +25 -0
- data/templates/public/js/bootstrap.js +0 -0
- data/templates/public/js/jquery-2.0.3.js +8829 -0
- data/templates/public/js/sockjs-0.2.1.min.js +27 -0
- data/templates/spec/spec_helper.rb +20 -0
- data/volt.gemspec +41 -0
- metadata +412 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'volt/templates/targets/base_section'
|
2
|
+
|
3
|
+
class DomSection < BaseSection
|
4
|
+
def initialize(binding_name)
|
5
|
+
@start_node = find_by_comment("$#{binding_name}")
|
6
|
+
@end_node = find_by_comment("$/#{binding_name}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_by_comment(text, in_node=`document`)
|
10
|
+
node = nil
|
11
|
+
|
12
|
+
%x{
|
13
|
+
node = document.evaluate("//comment()[. = ' " + text + " ']", in_node, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null).iterateNext();
|
14
|
+
}
|
15
|
+
return node
|
16
|
+
end
|
17
|
+
|
18
|
+
def text=(value)
|
19
|
+
%x{
|
20
|
+
this.$range().deleteContents();
|
21
|
+
this.$range().insertNode(document.createTextNode(#{value}));
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def remove
|
26
|
+
range = self.range()
|
27
|
+
|
28
|
+
%x{
|
29
|
+
range.deleteContents();
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove_anchors
|
34
|
+
%x{
|
35
|
+
this.start_node.parentNode.removeChild(this.start_node);
|
36
|
+
this.end_node.parentNode.removeChild(this.end_node);
|
37
|
+
}
|
38
|
+
@start_node = nil
|
39
|
+
@end_node = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def insert_anchor_before_end(binding_name)
|
43
|
+
Element.find(@end_node).before("<!-- $#{binding_name} --><!-- $/#{binding_name} -->")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Takes in an array of dom nodes and replaces the current content
|
47
|
+
# with the new nodes
|
48
|
+
def nodes=(nodes)
|
49
|
+
range = self.range()
|
50
|
+
|
51
|
+
%x{
|
52
|
+
range.deleteContents();
|
53
|
+
|
54
|
+
for (var i=nodes.length-1; i >= 0; i--) {
|
55
|
+
var node = nodes[i];
|
56
|
+
|
57
|
+
node.parentNode.removeChild(node);
|
58
|
+
range.insertNode(node);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Takes in our html and bindings, and rezero's the comment names, and the
|
64
|
+
# bindings. Returns an updated bindings hash
|
65
|
+
def set_content_and_rezero_bindings(html, bindings)
|
66
|
+
sub_nodes = nil
|
67
|
+
temp_div = nil
|
68
|
+
|
69
|
+
%x{
|
70
|
+
temp_div = document.createElement('div');
|
71
|
+
var doc = jQuery.parseHTML(html);
|
72
|
+
|
73
|
+
for (var i=0;i < doc.length;i++) {
|
74
|
+
temp_div.appendChild(doc[i]);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
new_bindings = {}
|
79
|
+
# Loop through the bindings, and rezero.
|
80
|
+
bindings.each_pair do |name,binding|
|
81
|
+
new_name = @@binding_number
|
82
|
+
|
83
|
+
if name.cur.is_a?(String)
|
84
|
+
if name[0..1] == 'id'
|
85
|
+
# Find by id
|
86
|
+
%x{
|
87
|
+
var node = temp_div.querySelector('#' + name);
|
88
|
+
node.setAttribute('id', 'id' +new_name);
|
89
|
+
}
|
90
|
+
|
91
|
+
new_bindings["id#{new_name}"] = binding
|
92
|
+
else
|
93
|
+
# Assume a fixed id
|
94
|
+
# TODO: We should raise an exception if this id is already on the page
|
95
|
+
new_bindings[name] = binding
|
96
|
+
end
|
97
|
+
else
|
98
|
+
# Change the comment ids
|
99
|
+
start_comment = find_by_comment("$#{name}", temp_div)
|
100
|
+
end_comment = find_by_comment("$/#{name}", temp_div)
|
101
|
+
|
102
|
+
%x{
|
103
|
+
start_comment.textContent = " $" + new_name + " ";
|
104
|
+
end_comment.textContent = " $/" + new_name + " ";
|
105
|
+
}
|
106
|
+
|
107
|
+
new_bindings[new_name] = binding
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
@@binding_number += 1
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
children = nil
|
116
|
+
%x{
|
117
|
+
children = temp_div.childNodes;
|
118
|
+
}
|
119
|
+
|
120
|
+
# Update the nodes
|
121
|
+
self.nodes = children
|
122
|
+
|
123
|
+
%x{
|
124
|
+
temp_div = null;
|
125
|
+
}
|
126
|
+
|
127
|
+
return new_bindings
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def range
|
133
|
+
return @range if @range
|
134
|
+
|
135
|
+
range = nil
|
136
|
+
%x{
|
137
|
+
range = document.createRange();
|
138
|
+
range.setStartAfter(this.start_node);
|
139
|
+
range.setEndBefore(this.end_node);
|
140
|
+
}
|
141
|
+
|
142
|
+
@range = range
|
143
|
+
|
144
|
+
return range
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'volt/templates/targets/base_section'
|
2
|
+
require 'volt/templates/targets/dom_section'
|
3
|
+
|
4
|
+
# DomTarget's provide an interface that can render bindings into
|
5
|
+
# the dom. Currently only one "dom" is supported, but multiple
|
6
|
+
# may be allowed in the future (iframes?)
|
7
|
+
class DomTarget < BaseSection
|
8
|
+
def section(*args)
|
9
|
+
return DomSection.new(*args)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'volt/templates/base_binding'
|
2
|
+
require 'volt/templates/template_renderer'
|
3
|
+
|
4
|
+
class TemplateBinding < BaseBinding
|
5
|
+
def initialize(target, context, binding_name, binding_in_path, getter)
|
6
|
+
# puts "New template binding: #{context.inspect} - #{binding_name.inspect} - #{getter.inspect}"
|
7
|
+
super(target, context, binding_name)
|
8
|
+
|
9
|
+
# Binding in path is the path for the template this binding is in
|
10
|
+
setup_path(binding_in_path)
|
11
|
+
|
12
|
+
@current_template = nil
|
13
|
+
|
14
|
+
# puts "GETTER: #{value_from_getter(getter).inspect}"
|
15
|
+
|
16
|
+
# Find the source for the getter binding
|
17
|
+
@path, section = value_from_getter(getter)
|
18
|
+
|
19
|
+
if section.is_a?(String)
|
20
|
+
# Render this as a section
|
21
|
+
@section = section
|
22
|
+
else
|
23
|
+
# Use the value passed in as the default model
|
24
|
+
@model = section
|
25
|
+
end
|
26
|
+
|
27
|
+
# Run the initial render
|
28
|
+
update
|
29
|
+
|
30
|
+
@path_changed_listener = @path.on('changed') { update } if @path.reactive?
|
31
|
+
@section_changed_listener = @section.on('changed') { update } if @section && @section.reactive?
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_path(binding_in_path)
|
35
|
+
path_parts = binding_in_path.split('/')
|
36
|
+
@collection_name = path_parts[0]
|
37
|
+
@controller_name = path_parts[1]
|
38
|
+
@page_name = path_parts[2]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true if there is a template at the path
|
42
|
+
def check_for_template?(path)
|
43
|
+
$page.templates[path]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Takes in a lookup path and returns the full path for the matching
|
47
|
+
# template. Also returns the controller name if applicable.
|
48
|
+
#
|
49
|
+
# Looking up a path is fairly simple. There are 4 parts needed to find
|
50
|
+
# the html to be rendered. File paths look like this:
|
51
|
+
# app/{component}/views/{controller_name}/{view}.html
|
52
|
+
# Within the html file may be one or more sections.
|
53
|
+
# 1. component (app/{comp})
|
54
|
+
# 2. controller
|
55
|
+
# 3. view
|
56
|
+
# 4. sections
|
57
|
+
#
|
58
|
+
# When searching for a file, the lookup starts at the section, and moves up.
|
59
|
+
# when moving up, default values are provided for the section, then view/section, etc..
|
60
|
+
# until a file is either found or the component level is reached.
|
61
|
+
#
|
62
|
+
# The defaults are as follows:
|
63
|
+
# 1. component - home
|
64
|
+
# 2. controller - index
|
65
|
+
# 3. view - index
|
66
|
+
# 4. section - body
|
67
|
+
def path_for_template(lookup_path, force_section=nil)
|
68
|
+
parts = lookup_path.split('/')
|
69
|
+
parts_size = parts.size
|
70
|
+
|
71
|
+
default_parts = ['home', 'index', 'index', 'body']
|
72
|
+
|
73
|
+
# When forcing a sub template, we can default the sub template section
|
74
|
+
default_parts[-1] = force_section if force_section
|
75
|
+
|
76
|
+
(5 - parts_size).times do |path_position|
|
77
|
+
# If they passed in a force_section, we can skip the first
|
78
|
+
next if force_section && path_position == 0
|
79
|
+
|
80
|
+
full_path = [@collection_name, @controller_name, @page_name, nil]
|
81
|
+
|
82
|
+
offset = 0
|
83
|
+
start_at = full_path.size - parts_size - path_position
|
84
|
+
|
85
|
+
full_path.size.times do |index|
|
86
|
+
if index >= start_at
|
87
|
+
if part = parts[index-start_at]
|
88
|
+
full_path[index] = part
|
89
|
+
else
|
90
|
+
full_path[index] = default_parts[index]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
path = full_path.join('/')
|
96
|
+
if check_for_template?(path)
|
97
|
+
controller = nil
|
98
|
+
if path_position > 1
|
99
|
+
# Lookup the controller
|
100
|
+
controller = full_path[1]
|
101
|
+
end
|
102
|
+
return path, controller
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def update
|
110
|
+
full_path, controller = path_for_template(@path.cur, @section.cur)
|
111
|
+
|
112
|
+
@current_template.remove if @current_template
|
113
|
+
|
114
|
+
current_context = @context
|
115
|
+
|
116
|
+
if @model
|
117
|
+
# Load in any procs
|
118
|
+
@model.each_pair do |key,value|
|
119
|
+
if value.class == Proc
|
120
|
+
@model[key] = value.call
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# TODO: at the moment a :body section and a :title will both initialize different
|
126
|
+
# controllers. Maybe we should have a way to tie them together?
|
127
|
+
if controller
|
128
|
+
args = []
|
129
|
+
args << SubContext.new(@model) if @model
|
130
|
+
# Initialize the new controller
|
131
|
+
current_context = Object.send(:const_get, (controller.camelize + 'Controller').to_sym).new(*args)
|
132
|
+
elsif @model
|
133
|
+
# Passed in attributes, but there is no controller
|
134
|
+
current_context = SubContext.new(@model, current_context)
|
135
|
+
end
|
136
|
+
|
137
|
+
@current_template = TemplateRenderer.new(@target, current_context, @binding_name, full_path)
|
138
|
+
end
|
139
|
+
|
140
|
+
def remove
|
141
|
+
if @path_changed_listener
|
142
|
+
@path_changed_listener.remove
|
143
|
+
@path_changed_listener = nil
|
144
|
+
end
|
145
|
+
|
146
|
+
if @section_changed_listener
|
147
|
+
@section_changed_listener.remove
|
148
|
+
@section_changed_listener = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
if @current_template
|
152
|
+
# Remove the template if one has been rendered, when the template binding is
|
153
|
+
# removed.
|
154
|
+
@current_template.remove
|
155
|
+
end
|
156
|
+
|
157
|
+
super
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'volt/templates/base_binding'
|
2
|
+
|
3
|
+
class TemplateRenderer < BaseBinding
|
4
|
+
attr_reader :context
|
5
|
+
def initialize(target, context, binding_name, template_name)
|
6
|
+
# puts "new template renderer: #{context.inspect} - #{binding_name.inspect}"
|
7
|
+
super(target, context, binding_name)
|
8
|
+
|
9
|
+
# puts "Template Name: #{template_name}"
|
10
|
+
@template = $page.templates[template_name]
|
11
|
+
@sub_bindings = []
|
12
|
+
|
13
|
+
if @template
|
14
|
+
html = @template['html']
|
15
|
+
bindings = @template['bindings']
|
16
|
+
else
|
17
|
+
html = "<div>-- < missing template #{template_name.inspect.gsub('<', '<').gsub('>', '>')} > --</div>"
|
18
|
+
bindings = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
bindings = self.section.set_content_and_rezero_bindings(html, bindings)
|
22
|
+
|
23
|
+
bindings.each_pair do |id,bindings_for_id|
|
24
|
+
bindings_for_id.each do |binding|
|
25
|
+
@sub_bindings << binding.call(target, context, id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove
|
32
|
+
# puts "Remove Template: #{self} - #{@sub_bindings.inspect}"
|
33
|
+
|
34
|
+
# Remove all of the sub-bindings
|
35
|
+
# @sub_bindings.each(&:remove)
|
36
|
+
|
37
|
+
@sub_bindings.each do |binding|
|
38
|
+
# puts "REMOVE: #{binding.inspect}"
|
39
|
+
binding.remove
|
40
|
+
# puts "REMOVED"
|
41
|
+
end
|
42
|
+
|
43
|
+
@sub_bindings = []
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def remove_anchors
|
48
|
+
section.remove_anchors
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'volt/models'
|
2
|
+
|
3
|
+
describe EventChain do
|
4
|
+
before do
|
5
|
+
@a = ReactiveValue.new(1)
|
6
|
+
@b = ReactiveValue.new(2)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should chain events when we use add_object" do
|
10
|
+
|
11
|
+
count = 0
|
12
|
+
@b.on('changed') { count += 1 }
|
13
|
+
@b.reactive_manager.event_chain.add_object(@a)
|
14
|
+
expect(count).to eq(0)
|
15
|
+
|
16
|
+
@a.trigger!('changed')
|
17
|
+
expect(count).to eq(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should chain events after add_object is called" do
|
21
|
+
@b.reactive_manager.event_chain.add_object(@a)
|
22
|
+
|
23
|
+
add_count = 0
|
24
|
+
@b.on('added') { add_count += 1 }
|
25
|
+
expect(add_count).to eq(0)
|
26
|
+
@a.trigger!('added')
|
27
|
+
|
28
|
+
expect(add_count).to eq(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "should remove events" do
|
33
|
+
@b.reactive_manager.event_chain.add_object(@a)
|
34
|
+
|
35
|
+
add_count = 0
|
36
|
+
listener = @b.on('added') { add_count += 1 }
|
37
|
+
expect(add_count).to eq(0)
|
38
|
+
@a.trigger!('added')
|
39
|
+
|
40
|
+
expect(add_count).to eq(1)
|
41
|
+
|
42
|
+
# Make sure the event is registered
|
43
|
+
expect(@a.reactive_manager.listeners.size).to eq(1)
|
44
|
+
expect(@b.reactive_manager.event_chain.instance_variable_get('@event_chain').values[0].keys.include?(:added)).to eq(true)
|
45
|
+
|
46
|
+
listener.remove
|
47
|
+
|
48
|
+
# Make sure its removed
|
49
|
+
expect(@a.reactive_manager.listeners.size).to eq(0)
|
50
|
+
expect(@b.reactive_manager.event_chain.instance_variable_get('@event_chain').values[0].keys.include?(:added)).to eq(false)
|
51
|
+
|
52
|
+
@a.trigger!('added')
|
53
|
+
expect(add_count).to eq(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should unchain directly" do
|
57
|
+
count = 0
|
58
|
+
a = ReactiveValue.new(Model.new)
|
59
|
+
b = a._name
|
60
|
+
listener = b.on('changed') { count += 1 }
|
61
|
+
|
62
|
+
expect(b.reactive_manager.listeners[:changed].size).to eq(1)
|
63
|
+
# TODO: ideally this would only bind 1 to a
|
64
|
+
expect(a.reactive_manager.listeners[:changed].size).to eq(1)
|
65
|
+
|
66
|
+
listener.remove
|
67
|
+
|
68
|
+
expect(b.reactive_manager.listeners[:changed]).to eq(nil)
|
69
|
+
expect(a.reactive_manager.listeners[:changed]).to eq(nil)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should unchain" do
|
73
|
+
count = 0
|
74
|
+
@b.on('changed') { count += 1 }
|
75
|
+
b_object_listener = @b.reactive_manager.event_chain.add_object(@a)
|
76
|
+
expect(count).to eq(0)
|
77
|
+
|
78
|
+
@a.trigger!('changed')
|
79
|
+
expect(count).to eq(1)
|
80
|
+
|
81
|
+
b_object_listener.remove
|
82
|
+
|
83
|
+
@a.trigger!('changed')
|
84
|
+
expect(count).to eq(1)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should unchain up the chain" do
|
88
|
+
count = 0
|
89
|
+
a = ReactiveValue.new(Model.new)
|
90
|
+
|
91
|
+
b = a._list
|
92
|
+
expect(a.reactive_manager.listeners.size).to eq(0)
|
93
|
+
listener = b.on('changed') { count += 1 }
|
94
|
+
|
95
|
+
expect(a.reactive_manager.listeners.size).to eq(1)
|
96
|
+
|
97
|
+
listener.remove
|
98
|
+
|
99
|
+
expect(a.reactive_manager.listeners.size).to eq(0)
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "double add/removes" do
|
103
|
+
it "should unchain" do
|
104
|
+
c = ReactiveValue.new(3)
|
105
|
+
count = 0
|
106
|
+
@b.on('changed') { count += 1 }
|
107
|
+
c.on('changed') { count += 1 }
|
108
|
+
|
109
|
+
|
110
|
+
# Chain b to a
|
111
|
+
b_object_listener = @b.reactive_manager.event_chain.add_object(@a)
|
112
|
+
c_object_listener = c.reactive_manager.event_chain.add_object(@a)
|
113
|
+
expect(count).to eq(0)
|
114
|
+
|
115
|
+
@a.trigger!('changed')
|
116
|
+
expect(count).to eq(2)
|
117
|
+
|
118
|
+
b_object_listener.remove
|
119
|
+
|
120
|
+
@a.trigger!('changed')
|
121
|
+
expect(count).to eq(3)
|
122
|
+
|
123
|
+
c_object_listener.remove
|
124
|
+
|
125
|
+
@a.trigger!('changed')
|
126
|
+
expect(count).to eq(3)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|