volt 0.2.3
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 +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
|