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,74 @@
|
|
1
|
+
require 'volt/templates/base_binding'
|
2
|
+
|
3
|
+
class IfBinding < BaseBinding
|
4
|
+
def initialize(target, context, binding_name, branches)
|
5
|
+
getter, template_name = branches[0]
|
6
|
+
# puts "New If Binding: #{binding_name}, #{getter.inspect}"
|
7
|
+
|
8
|
+
|
9
|
+
super(target, context, binding_name)
|
10
|
+
|
11
|
+
@branches = []
|
12
|
+
@listeners = []
|
13
|
+
|
14
|
+
branches.each do |branch|
|
15
|
+
getter, template_name = branch
|
16
|
+
|
17
|
+
if getter.present?
|
18
|
+
# Lookup the value
|
19
|
+
value = value_from_getter(getter)
|
20
|
+
|
21
|
+
if value.reactive?
|
22
|
+
# Trigger change when value changes
|
23
|
+
@listeners << value.on('changed') { update }
|
24
|
+
end
|
25
|
+
else
|
26
|
+
# A nil value means this is an unconditional else branch, it
|
27
|
+
# should always be true
|
28
|
+
value = true
|
29
|
+
end
|
30
|
+
|
31
|
+
@branches << [value, template_name]
|
32
|
+
end
|
33
|
+
|
34
|
+
update
|
35
|
+
end
|
36
|
+
|
37
|
+
def update
|
38
|
+
# Find the true branch
|
39
|
+
true_template = nil
|
40
|
+
@branches.each do |branch|
|
41
|
+
value, template_name = branch
|
42
|
+
|
43
|
+
# TODO: A bug in opal requires us to check == true
|
44
|
+
if value.cur.true? == true
|
45
|
+
# This branch is currently true
|
46
|
+
true_template = template_name
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Change out the template only if the true branch has changed.
|
52
|
+
if @last_true_template != true_template
|
53
|
+
@last_true_template = true_template
|
54
|
+
|
55
|
+
if @template
|
56
|
+
@template.remove
|
57
|
+
@template = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
if true_template
|
61
|
+
@template = TemplateRenderer.new(@target, @context, binding_name, true_template)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def remove
|
67
|
+
# Remove all listeners on any reactive values
|
68
|
+
@listeners.each(&:remove)
|
69
|
+
|
70
|
+
@template.remove if @template
|
71
|
+
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'opal'
|
2
|
+
require 'volt/models'
|
3
|
+
|
4
|
+
class Test
|
5
|
+
def self.test1
|
6
|
+
a = ReactiveValue.new(1)
|
7
|
+
listener = a.on('changed') { puts "CHANGED" }
|
8
|
+
a.cur = 5
|
9
|
+
listener.remove
|
10
|
+
|
11
|
+
ObjectTracker.process_queue
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.test
|
15
|
+
a = ReactiveValue.new(Model.new)
|
16
|
+
a._cool = [1,2,3]
|
17
|
+
|
18
|
+
listener = a._cool.on('added') { puts "ADDED" }
|
19
|
+
a._cool << 4
|
20
|
+
puts a._cool[3]
|
21
|
+
|
22
|
+
listener.remove
|
23
|
+
|
24
|
+
ObjectTracker.process_queue
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'opal'
|
2
|
+
|
3
|
+
ENV['CLIENT'] = true
|
4
|
+
|
5
|
+
require 'opal-jquery'
|
6
|
+
require 'volt/models'
|
7
|
+
require 'volt/models/params'
|
8
|
+
require 'volt/controllers/model_controller'
|
9
|
+
require 'volt/templates/attribute_binding'
|
10
|
+
require 'volt/templates/content_binding'
|
11
|
+
require 'volt/templates/each_binding'
|
12
|
+
require 'volt/templates/if_binding'
|
13
|
+
require 'volt/templates/template_binding'
|
14
|
+
require 'volt/templates/template_renderer'
|
15
|
+
require 'volt/templates/reactive_template'
|
16
|
+
require 'volt/templates/event_binding'
|
17
|
+
require 'volt/templates/document_events'
|
18
|
+
require 'volt/templates/sub_context'
|
19
|
+
require 'volt/templates/targets/dom_target'
|
20
|
+
require 'volt/templates/channel'
|
21
|
+
require 'volt/router/routes'
|
22
|
+
require 'volt/models/url'
|
23
|
+
require 'volt/page/url_tracker'
|
24
|
+
require 'volt'
|
25
|
+
require 'volt/benchmark/benchmark'
|
26
|
+
require 'volt/templates/render_queue'
|
27
|
+
|
28
|
+
class Page
|
29
|
+
attr_reader :url, :params, :page, :store, :templates, :routes, :render_queue
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
|
33
|
+
# debugger
|
34
|
+
puts "------ Page Loaded -------"
|
35
|
+
@model_classes = {}
|
36
|
+
|
37
|
+
# Run the code to setup the page
|
38
|
+
@page = ReactiveValue.new(Model.new)#({}, nil, 'page', @model_classes))
|
39
|
+
@store = ReactiveValue.new(Model.new)#({}, nil, 'store', @model_classes))
|
40
|
+
|
41
|
+
@url = ReactiveValue.new(URL.new)
|
42
|
+
@params = @url.params
|
43
|
+
@url_tracker = UrlTracker.new(self)
|
44
|
+
|
45
|
+
@events = DocumentEvents.new
|
46
|
+
@render_queue = RenderQueue.new
|
47
|
+
|
48
|
+
# Add event for link clicks to handle all a onclick
|
49
|
+
# EventBinding.new(self, )
|
50
|
+
|
51
|
+
# Setup escape binding for console
|
52
|
+
%x{
|
53
|
+
$(document).keyup(function(e) {
|
54
|
+
if (e.keyCode == 27) {
|
55
|
+
Opal.gvars.page.$launch_console();
|
56
|
+
}
|
57
|
+
});
|
58
|
+
|
59
|
+
$(document).on('click', 'a', function(event) {
|
60
|
+
Opal.gvars.page.$link_clicked($(this).attr('href'));
|
61
|
+
event.stopPropagation();
|
62
|
+
|
63
|
+
return false;
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
# channel
|
68
|
+
# channel.on('message') do |message|
|
69
|
+
# # puts "GOT: #{message}"
|
70
|
+
# # `console.log('got: ', message);`
|
71
|
+
# end
|
72
|
+
end
|
73
|
+
|
74
|
+
def link_clicked(url)
|
75
|
+
puts "Link Clicked to: #{url}"
|
76
|
+
# Skip when href == ''
|
77
|
+
return if url.blank?
|
78
|
+
|
79
|
+
# Normalize url
|
80
|
+
# Benchmark.bm(1) do
|
81
|
+
@url.parse("http://localhost:3000" + url)
|
82
|
+
# end
|
83
|
+
end
|
84
|
+
|
85
|
+
# We provide a binding_name, so we can bind events on the document
|
86
|
+
def binding_name
|
87
|
+
'page'
|
88
|
+
end
|
89
|
+
|
90
|
+
def launch_console
|
91
|
+
puts "Launch Console"
|
92
|
+
end
|
93
|
+
|
94
|
+
def channel
|
95
|
+
@channel ||= Channel.new
|
96
|
+
end
|
97
|
+
|
98
|
+
def events
|
99
|
+
@events
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_model(model_name)
|
103
|
+
# puts "ADD MODEL: #{model_name.inspect} - #{model_name.camelize.inspect}"
|
104
|
+
|
105
|
+
@model_classes[["*", "_#{model_name}"]] = Object.const_get(model_name.camelize)
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_template(name, template, bindings)
|
109
|
+
# puts "Add Template: #{name}\n#{template.inspect}\n#{bindings.inspect}"
|
110
|
+
@templates ||= {}
|
111
|
+
@templates[name] = {'html' => template, 'bindings' => bindings}
|
112
|
+
# puts "Add Template: #{name}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_routes(&block)
|
116
|
+
@routes = Routes.new.define(&block)
|
117
|
+
@url.cur.router = @routes
|
118
|
+
end
|
119
|
+
|
120
|
+
def start
|
121
|
+
# Setup to render template
|
122
|
+
Element.find('body').html = "<!-- $CONTENT --><!-- $/CONTENT -->"
|
123
|
+
|
124
|
+
main_controller = IndexController.new
|
125
|
+
|
126
|
+
# Setup main page template
|
127
|
+
TemplateRenderer.new(DomTarget.new, main_controller, 'CONTENT', 'home/index/index/body')
|
128
|
+
|
129
|
+
# Setup title listener template
|
130
|
+
title_target = AttributeTarget.new
|
131
|
+
title_target.on('changed') do
|
132
|
+
title = title_target.to_html
|
133
|
+
`document.title = title;`
|
134
|
+
end
|
135
|
+
TemplateRenderer.new(title_target, main_controller, "main", "home/index/index/title")
|
136
|
+
|
137
|
+
@url_tracker.url_updated(true)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
$page = Page.new
|
142
|
+
|
143
|
+
# Call start once the page is loaded
|
144
|
+
Document.ready? do
|
145
|
+
$page.start
|
146
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class ReactiveTemplate
|
2
|
+
include Events
|
3
|
+
|
4
|
+
def initialize(context, template_path)
|
5
|
+
@template_path = template_path
|
6
|
+
@target = AttributeTarget.new
|
7
|
+
@template = TemplateRenderer.new(@target, context, "main", template_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def event_added(event, scope_provider, first)
|
11
|
+
if first && !@template_listener
|
12
|
+
@template_listener = @target.on('changed') { update }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def event_removed(event, last)
|
17
|
+
if last && @template_listener
|
18
|
+
@template_listener.remove
|
19
|
+
@template_listener = nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Render the template and get the current value
|
24
|
+
def cur
|
25
|
+
@target.to_html
|
26
|
+
end
|
27
|
+
|
28
|
+
# TODO: improve
|
29
|
+
def skip_current_queue_flush
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def update
|
35
|
+
trigger!('changed')
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# A sub context takes in a hash of local variables that should be available
|
2
|
+
# in front of the current context. It basically proxies the local variables
|
3
|
+
# first, then failing those proxies the context.
|
4
|
+
|
5
|
+
class SubContext
|
6
|
+
attr_reader :locals
|
7
|
+
|
8
|
+
def initialize(locals, context=nil)
|
9
|
+
@locals = locals.stringify_keys
|
10
|
+
@context = context
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method_name, *args, &block)
|
14
|
+
method_name = method_name.to_s
|
15
|
+
if @locals[method_name]
|
16
|
+
return @locals[method_name]
|
17
|
+
elsif @context
|
18
|
+
return @context.send(method_name, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
raise NoMethodError.new("undefined method `#{method_name}' for \"#{self.inspect}\":#{self.class.to_s}")
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# AttributeSection provides a place to render templates that
|
2
|
+
# will be placed as text into an attribute.
|
3
|
+
|
4
|
+
require 'volt/templates/targets/base_section'
|
5
|
+
|
6
|
+
class AttributeSection
|
7
|
+
def initialize(target, binding_name)
|
8
|
+
@target = target
|
9
|
+
@binding_name = binding_name
|
10
|
+
# puts "init attr section on #{binding_name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def text=(text)
|
14
|
+
set_content_and_rezero_bindings(text, {})
|
15
|
+
end
|
16
|
+
|
17
|
+
# Takes in our html and bindings, and rezero's the comment names, and the
|
18
|
+
# bindings. Returns an updated bindings hash
|
19
|
+
def set_content_and_rezero_bindings(html, bindings)
|
20
|
+
if @binding_name == 'main'
|
21
|
+
@target.html = html
|
22
|
+
else
|
23
|
+
@target.find_by_binding_id(@binding_name).html = html
|
24
|
+
end
|
25
|
+
|
26
|
+
return bindings
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove
|
30
|
+
node = @target.find_by_binding_id(@binding_name)
|
31
|
+
node.remove
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'volt/templates/targets/base_section'
|
2
|
+
require 'volt/templates/targets/attribute_section'
|
3
|
+
require 'volt/templates/targets/binding_document/component_node'
|
4
|
+
require 'volt/templates/targets/binding_document/html_node'
|
5
|
+
|
6
|
+
# AttributeTarget's provide an interface that can render bindings into
|
7
|
+
# a string that can then be used to update a attribute binding.
|
8
|
+
|
9
|
+
class AttributeTarget < ComponentNode
|
10
|
+
# TODO: improve
|
11
|
+
def skip_current_queue_flush
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def section(*args)
|
16
|
+
return AttributeSection.new(self, *args)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'volt/templates/targets/binding_document/html_node'
|
2
|
+
require 'volt/reactive/events'
|
3
|
+
|
4
|
+
# Component nodes contain an array of both HtmlNodes and ComponentNodes.
|
5
|
+
# Instead of providing a full DOM API, component nodes are the branch
|
6
|
+
# nodes and html nodes are the leafs. This is all we need to produce
|
7
|
+
# the html from templates outside of a normal dom.
|
8
|
+
class ComponentNode < BaseNode
|
9
|
+
include Events
|
10
|
+
|
11
|
+
attr_accessor :parent, :binding_id, :nodes
|
12
|
+
def initialize(binding_id=nil, parent=nil)
|
13
|
+
@nodes = []
|
14
|
+
@binding_id = binding_id
|
15
|
+
@parent = parent
|
16
|
+
|
17
|
+
@change_listener = on('changed') do
|
18
|
+
if @parent
|
19
|
+
@parent.trigger!('changed')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# TODO: improve
|
25
|
+
def skip_current_queue_flush
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def text=(text)
|
30
|
+
self.html = text
|
31
|
+
end
|
32
|
+
|
33
|
+
def html=(html)
|
34
|
+
parts = html.split(/(\<\!\-\- \$\/?[0-9]+ \-\-\>)/).reject {|v| v == '' }
|
35
|
+
|
36
|
+
# Clear current nodes
|
37
|
+
@nodes = []
|
38
|
+
|
39
|
+
current_node = self
|
40
|
+
|
41
|
+
parts.each do |part|
|
42
|
+
case part
|
43
|
+
when /\<\!\-\- \$[0-9]+ \-\-\>/
|
44
|
+
# Open
|
45
|
+
binding_id = part.match(/\<\!\-\- \$([0-9]+) \-\-\>/)[1].to_i
|
46
|
+
|
47
|
+
sub_node = ComponentNode.new(binding_id, current_node)
|
48
|
+
current_node << sub_node
|
49
|
+
current_node = sub_node
|
50
|
+
when /\<\!\-\- \$\/[0-9]+ \-\-\>/
|
51
|
+
# Close
|
52
|
+
# binding_id = part.match(/\<\!\-\- \$\/([0-9]+) \-\-\>/)[1].to_i
|
53
|
+
|
54
|
+
current_node = current_node.parent
|
55
|
+
else
|
56
|
+
# html string
|
57
|
+
current_node << HtmlNode.new(part)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
trigger!('changed')
|
62
|
+
end
|
63
|
+
|
64
|
+
def <<(node)
|
65
|
+
@nodes << node
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_html
|
69
|
+
str = []
|
70
|
+
@nodes.each do |node|
|
71
|
+
str << node.to_html
|
72
|
+
end
|
73
|
+
|
74
|
+
return str.join('')
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_by_binding_id(binding_id)
|
78
|
+
if @binding_id == binding_id
|
79
|
+
return self
|
80
|
+
end
|
81
|
+
|
82
|
+
@nodes.each do |node|
|
83
|
+
if node.cur.is_a?(ComponentNode)
|
84
|
+
val = node.find_by_binding_id(binding_id)
|
85
|
+
return val if val
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def remove
|
93
|
+
@nodes = []
|
94
|
+
|
95
|
+
trigger!('changed')
|
96
|
+
end
|
97
|
+
|
98
|
+
def remove_anchors
|
99
|
+
raise "not implemented"
|
100
|
+
|
101
|
+
@parent.nodes.delete(self)
|
102
|
+
|
103
|
+
@change_listener.remove
|
104
|
+
@change_listener = nil
|
105
|
+
|
106
|
+
trigger!('changed')
|
107
|
+
end
|
108
|
+
|
109
|
+
def inspect
|
110
|
+
"<ComponentNode:#{@binding_id} #{@nodes.inspect}>"
|
111
|
+
end
|
112
|
+
end
|