volt 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -2
  3. data/Readme.md +97 -56
  4. data/VERSION +1 -1
  5. data/app/volt/assets/js/sockjs-0.3.4.min.js +27 -0
  6. data/app/volt/assets/js/vertxbus.js +216 -0
  7. data/app/volt/tasks/live_query/live_query.rb +5 -5
  8. data/app/volt/tasks/live_query/live_query_pool.rb +1 -1
  9. data/app/volt/tasks/query_tasks.rb +5 -0
  10. data/app/volt/tasks/store_tasks.rb +44 -18
  11. data/docs/WHY.md +10 -0
  12. data/lib/volt/cli.rb +18 -7
  13. data/lib/volt/controllers/model_controller.rb +30 -8
  14. data/lib/volt/extra_core/inflections.rb +63 -0
  15. data/lib/volt/extra_core/inflector/inflections.rb +203 -0
  16. data/lib/volt/extra_core/inflector/methods.rb +63 -0
  17. data/lib/volt/extra_core/inflector.rb +4 -0
  18. data/lib/volt/extra_core/object.rb +9 -0
  19. data/lib/volt/extra_core/string.rb +10 -14
  20. data/lib/volt/models/array_model.rb +45 -27
  21. data/lib/volt/models/cursor.rb +6 -0
  22. data/lib/volt/models/model.rb +127 -12
  23. data/lib/volt/models/model_hash_behaviour.rb +8 -5
  24. data/lib/volt/models/model_helpers.rb +4 -4
  25. data/lib/volt/models/model_state.rb +22 -0
  26. data/lib/volt/models/persistors/array_store.rb +49 -35
  27. data/lib/volt/models/persistors/base.rb +3 -3
  28. data/lib/volt/models/persistors/model_store.rb +17 -6
  29. data/lib/volt/models/persistors/query/query_listener.rb +0 -2
  30. data/lib/volt/models/persistors/store.rb +0 -4
  31. data/lib/volt/models/persistors/store_state.rb +27 -0
  32. data/lib/volt/models/url.rb +2 -2
  33. data/lib/volt/models/validations/errors.rb +0 -0
  34. data/lib/volt/models/validations/length.rb +13 -0
  35. data/lib/volt/models/validations/validations.rb +82 -0
  36. data/lib/volt/models.rb +1 -1
  37. data/lib/volt/page/bindings/attribute_binding.rb +29 -14
  38. data/lib/volt/page/bindings/base_binding.rb +2 -2
  39. data/lib/volt/page/bindings/component_binding.rb +29 -25
  40. data/lib/volt/page/bindings/content_binding.rb +1 -0
  41. data/lib/volt/page/bindings/each_binding.rb +25 -33
  42. data/lib/volt/page/bindings/event_binding.rb +0 -1
  43. data/lib/volt/page/bindings/if_binding.rb +3 -1
  44. data/lib/volt/page/bindings/template_binding.rb +61 -28
  45. data/lib/volt/page/document_events.rb +3 -1
  46. data/lib/volt/page/draw_cycle.rb +22 -0
  47. data/lib/volt/page/page.rb +10 -1
  48. data/lib/volt/page/reactive_template.rb +23 -16
  49. data/lib/volt/page/sub_context.rb +1 -1
  50. data/lib/volt/page/targets/attribute_section.rb +3 -2
  51. data/lib/volt/page/targets/attribute_target.rb +0 -4
  52. data/lib/volt/page/targets/base_section.rb +25 -0
  53. data/lib/volt/page/targets/binding_document/component_node.rb +13 -14
  54. data/lib/volt/page/targets/binding_document/html_node.rb +4 -0
  55. data/lib/volt/page/targets/dom_section.rb +16 -67
  56. data/lib/volt/page/targets/dom_template.rb +99 -0
  57. data/lib/volt/page/targets/helpers/comment_searchers.rb +29 -0
  58. data/lib/volt/page/template_renderer.rb +2 -14
  59. data/lib/volt/reactive/array_extensions.rb +0 -1
  60. data/lib/volt/reactive/event_chain.rb +9 -2
  61. data/lib/volt/reactive/events.rb +44 -37
  62. data/lib/volt/reactive/object_tracking.rb +1 -1
  63. data/lib/volt/reactive/reactive_array.rb +18 -0
  64. data/lib/volt/reactive/reactive_count.rb +108 -0
  65. data/lib/volt/reactive/reactive_generator.rb +44 -0
  66. data/lib/volt/reactive/reactive_value.rb +73 -73
  67. data/lib/volt/reactive/string_extensions.rb +1 -1
  68. data/lib/volt/router/routes.rb +205 -88
  69. data/lib/volt/server/component_handler.rb +3 -1
  70. data/lib/volt/server/html_parser/view_parser.rb +20 -4
  71. data/lib/volt/server/rack/component_paths.rb +13 -10
  72. data/lib/volt/server/rack/index_files.rb +4 -4
  73. data/lib/volt/server/socket_connection_handler.rb +5 -1
  74. data/lib/volt/server.rb +10 -3
  75. data/spec/apps/kitchen_sink/.gitignore +8 -0
  76. data/spec/apps/kitchen_sink/Gemfile +32 -0
  77. data/spec/apps/kitchen_sink/app/home/views/index/index.html +3 -5
  78. data/spec/apps/kitchen_sink/config.ru +4 -0
  79. data/spec/apps/kitchen_sink/public/index.html +2 -2
  80. data/spec/extra_core/inflector_spec.rb +8 -0
  81. data/spec/models/event_chain_spec.rb +18 -0
  82. data/spec/models/model_buffers_spec.rb +9 -0
  83. data/spec/models/model_spec.rb +22 -9
  84. data/spec/models/reactive_array_spec.rb +26 -1
  85. data/spec/models/reactive_call_times_spec.rb +28 -0
  86. data/spec/models/reactive_value_spec.rb +19 -0
  87. data/spec/models/validations_spec.rb +39 -0
  88. data/spec/page/bindings/content_binding_spec.rb +1 -0
  89. data/spec/{templates → page/bindings}/template_binding_spec.rb +54 -0
  90. data/spec/router/routes_spec.rb +156 -8
  91. data/spec/server/html_parser/sandlebars_parser_spec.rb +55 -47
  92. data/spec/server/html_parser/view_parser_spec.rb +3 -0
  93. data/spec/server/rack/asset_files_spec.rb +1 -1
  94. data/spec/spec_helper.rb +25 -11
  95. data/spec/templates/targets/binding_document/component_node_spec.rb +12 -0
  96. data/templates/project/Gemfile.tt +11 -0
  97. data/templates/project/app/home/config/routes.rb +1 -1
  98. data/templates/project/app/home/controllers/index_controller.rb +5 -5
  99. data/templates/project/app/home/views/index/index.html +6 -6
  100. data/volt.gemspec +5 -6
  101. metadata +34 -76
  102. data/app/volt/assets/js/sockjs-0.2.1.min.js +0 -27
  103. data/lib/volt/reactive/object_tracker.rb +0 -107
@@ -15,34 +15,30 @@ class EachBinding < BaseBinding
15
15
  @templates = []
16
16
 
17
17
  # Run the initial render
18
- update
18
+ # update
19
+ reload
19
20
 
20
21
  @added_listener = @value.on('added') { |_, position, item| item_added(position) }
21
22
  @changed_listener = @value.on('changed') { reload }
22
23
  @removed_listener = @value.on('removed') { |_, position| item_removed(position) }
23
24
  end
24
25
 
25
- # When a change event comes through, its most likely upstream, so the whole
26
- # array might have changed. In this case, just reload the whole thing
27
- # TODO: Track to make sure the changed event isn't being called too often (it is currently)
26
+ # When a changed event happens, we update to the new size.
28
27
  def reload
29
- # ObjectTracker.enable_cache
30
- # Remove all of the current templates
31
- if @templates
32
- @templates.each do |template|
33
- template.remove_anchors
34
-
35
- # TODO: Make sure this is being removed since we already removed the anchors
36
- template.remove
28
+ # Adjust to the new size
29
+ values = current_values
30
+ templates_size = @templates.size
31
+ values_size = values.size
32
+
33
+ if templates_size < values_size
34
+ (templates_size).upto(values_size-1) do |index|
35
+ item_added(index)
36
+ end
37
+ elsif templates_size > values_size
38
+ (templates_size-1).downto(values_size) do |index|
39
+ item_removed(index)
37
40
  end
38
41
  end
39
-
40
- @templates = []
41
-
42
- # Run update again to rebuild
43
- update
44
-
45
- # ObjectTracker.disable_cache
46
42
  end
47
43
 
48
44
  def item_removed(position)
@@ -77,7 +73,7 @@ class EachBinding < BaseBinding
77
73
  item_template = TemplateRenderer.new(@page, @target, item_context, binding_name, @template_name)
78
74
  @templates.insert(position, item_template)
79
75
 
80
- update_indexes_after(position)
76
+ # update_indexes_after(position)
81
77
  end
82
78
 
83
79
  # When items are added or removed in the middle of the list, we need
@@ -85,27 +81,23 @@ class EachBinding < BaseBinding
85
81
  def update_indexes_after(start_index)
86
82
  size = @templates.size
87
83
  if size > 0
88
- start_index.upto(@templates.size-1) do |index|
84
+ start_index.upto(size-1) do |index|
85
+ puts "UP INDEX: #{index}"
89
86
  @templates[index].context.locals[:index].cur = index
90
87
  end
91
88
  end
92
89
  end
93
90
 
94
- def update(item=nil)
95
- if item
96
- values = [item]
97
- else
98
- values = @value.cur
99
- return if values.is_a?(Model) || values.is_a?(Exception)
100
- values = values.attributes
101
- end
91
+ def current_values
92
+ values = @value.cur
102
93
 
103
- # TODO: Switch to #each?
104
- values.each_with_index do |value,index|
105
- item_added(index)
106
- end
94
+ return [] if values.is_a?(Model) || values.is_a?(Exception)
95
+ values = values.attributes
96
+
97
+ return values
107
98
  end
108
99
 
100
+
109
101
  # When this each_binding is removed, cleanup.
110
102
  def remove
111
103
  # puts "Remove Each"
@@ -12,7 +12,6 @@ class JSEvent
12
12
  end
13
13
 
14
14
  def stop
15
- # puts "STOPPING"
16
15
  # `this.js_event.stopPropagation();`
17
16
  `this.js_event.preventDefault();`
18
17
  end
@@ -39,8 +39,10 @@ class IfBinding < BaseBinding
39
39
  @branches.each do |branch|
40
40
  value, template_name = branch
41
41
 
42
+ current_value = value.cur
43
+
42
44
  # TODO: A bug in opal requires us to check == true
43
- if value.cur.true? == true
45
+ if current_value.true? == true && !current_value.is_a?(Exception)
44
46
  # This branch is currently true
45
47
  true_template = template_name
46
48
  break
@@ -11,8 +11,6 @@ class TemplateBinding < BaseBinding
11
11
 
12
12
  @current_template = nil
13
13
 
14
- # puts "GETTER: #{value_from_getter(getter).inspect}"
15
-
16
14
  # Find the source for the getter binding
17
15
  @path, section = value_from_getter(getter)
18
16
 
@@ -20,15 +18,15 @@ class TemplateBinding < BaseBinding
20
18
  # Render this as a section
21
19
  @section = section
22
20
  else
23
- # Use the value passed in as the default model
24
- @model = section
21
+ # Use the value passed in as the default arguments
22
+ @arguments = section
25
23
  end
26
24
 
27
25
  # Run the initial render
28
26
  update
29
27
 
30
- @path_changed_listener = @path.on('changed') { update } if @path.reactive?
31
- @section_changed_listener = @section.on('changed') { update } if @section && @section.reactive?
28
+ @path_changed_listener = @path.on('changed') { queue_update } if @path.reactive?
29
+ @section_changed_listener = @section.on('changed') { queue_update } if @section && @section.reactive?
32
30
  end
33
31
 
34
32
  def setup_path(binding_in_path)
@@ -44,7 +42,7 @@ class TemplateBinding < BaseBinding
44
42
  end
45
43
 
46
44
  # Takes in a lookup path and returns the full path for the matching
47
- # template. Also returns the controller name if applicable.
45
+ # template. Also returns the controller and action name if applicable.
48
46
  #
49
47
  # Looking up a path is fairly simple. There are 4 parts needed to find
50
48
  # the html to be rendered. File paths look like this:
@@ -79,7 +77,6 @@ class TemplateBinding < BaseBinding
79
77
 
80
78
  full_path = [@collection_name, @controller_name, @page_name, nil]
81
79
 
82
- offset = 0
83
80
  start_at = full_path.size - parts_size - path_position
84
81
 
85
82
  full_path.size.times do |index|
@@ -95,9 +92,12 @@ class TemplateBinding < BaseBinding
95
92
  path = full_path.join('/')
96
93
  if check_for_template?(path)
97
94
  controller = nil
98
- if path_position > 1
95
+
96
+ # Don't return a controller if we are just getting another section
97
+ # from the same controller
98
+ if path_position >= 1
99
99
  # Lookup the controller
100
- controller = [full_path[0], full_path[1] + '_controller']
100
+ controller = [full_path[0], full_path[1] + '_controller', full_path[2]]
101
101
  end
102
102
  return path, controller
103
103
  end
@@ -107,45 +107,76 @@ class TemplateBinding < BaseBinding
107
107
  end
108
108
 
109
109
  def update
110
- full_path, controller_name = path_for_template(@path.cur, @section.cur)
110
+ full_path, controller_path = path_for_template(@path.cur, @section.cur)
111
+ # puts "UPDATE: #{@path.inspect} - #{full_path.inspect}"
111
112
 
112
113
  @current_template.remove if @current_template
113
114
 
114
- if @model
115
+ if @arguments
115
116
  # Load in any procs
116
- @model.each_pair do |key,value|
117
+ @arguments.each_pair do |key,value|
117
118
  if value.class == Proc
118
- @model[key.gsub('-', '_')] = value.call
119
+ @arguments[key.gsub('-', '_')] = value.call
119
120
  end
120
121
  end
121
122
  end
122
123
 
123
- render_template(full_path, controller_name)
124
+ render_template(full_path, controller_path)
124
125
  end
125
126
 
126
127
  # The context for templates can be either a controller, or the original context.
127
- def render_template(full_path, controller_name)
128
+ def render_template(full_path, controller_path)
129
+ args = @arguments ? [@arguments] : []
130
+
128
131
  # TODO: at the moment a :body section and a :title will both initialize different
129
132
  # controllers. Maybe we should have a way to tie them together?
130
- controller_class = get_controller(controller_name)
131
- if controller_class
132
- args = []
133
- args << SubContext.new(@model) if @model
133
+ controller_class, action = get_controller(controller_path)
134
134
 
135
+ if controller_class
135
136
  # Setup the controller
136
137
  current_context = controller_class.new(*args)
137
- @controller = current_context
138
138
  else
139
- # Pass the context directly
140
- current_context = @context
141
- @controller = nil
139
+ current_context = ModelController.new(*args)
142
140
  end
141
+ @controller = current_context
142
+
143
+ # Trigger the action
144
+ @controller.send(action) if @controller.respond_to?(action)
143
145
 
144
146
  @current_template = TemplateRenderer.new(@page, @target, current_context, @binding_name, full_path)
145
147
 
146
148
  call_ready
147
149
  end
148
150
 
151
+
152
+
153
+ # # The context for templates can be either a controller, or the original context.
154
+ # def render_template(full_path, controller_path)
155
+ # # TODO: at the moment a :body section and a :title will both initialize different
156
+ # # controllers. Maybe we should have a way to tie them together?
157
+ # controller_class, action = get_controller(controller_path)
158
+ # if controller_class
159
+ # args = []
160
+ # puts "MODEL: #{@arguments.inspect}"
161
+ # args << SubContext.new(@arguments) if @arguments
162
+ #
163
+ # # Setup the controller
164
+ # current_context = controller_class.new(*args)
165
+ # @controller = current_context
166
+ #
167
+ # # Trigger the action
168
+ # @controller.send(action) if @controller.respond_to?(action)
169
+ # else
170
+ # # Pass the context directly
171
+ # current_context = @context
172
+ # @controller = nil
173
+ # end
174
+ #
175
+ # @current_template = TemplateRenderer.new(@page, @target, current_context, @binding_name, full_path)
176
+ #
177
+ # call_ready
178
+ # end
179
+
149
180
  def call_ready
150
181
  if @controller
151
182
  if @controller.respond_to?(:section=)
@@ -190,11 +221,13 @@ class TemplateBinding < BaseBinding
190
221
  private
191
222
 
192
223
  # Fetch the controller class
193
- def get_controller(controller_name)
194
- return nil unless controller_name && controller_name.size > 0
224
+ def get_controller(controller_path)
225
+ return nil, nil unless controller_path && controller_path.size > 0
226
+
227
+ action = controller_path[-1]
195
228
 
196
229
  # Get the constant parts
197
- parts = controller_name.map {|v| v.gsub('-', '_').camelize }
230
+ parts = controller_path[0..-2].map {|v| v.gsub('-', '_').camelize }
198
231
 
199
232
  # Home doesn't get namespaced
200
233
  if parts.first == 'Home'
@@ -212,7 +245,7 @@ class TemplateBinding < BaseBinding
212
245
  end
213
246
  end
214
247
 
215
- return obj
248
+ return obj, action
216
249
  end
217
250
 
218
251
  end
@@ -15,7 +15,7 @@ class DocumentEvents
15
15
 
16
16
  %x{
17
17
  $('body').on(event, function(e) {
18
- that.$handle(event, e, e.originalEvent.target);
18
+ that.$handle(event, e, e.target || e.originalEvent.target);
19
19
  });
20
20
  }
21
21
 
@@ -37,6 +37,8 @@ class DocumentEvents
37
37
  # TODO: Sometimes the event doesn't exist, but we still get
38
38
  # an event.
39
39
  handlers = @events[event_name]
40
+ # puts "EVENT: #{event_name} - #{handlers.inspect} for #{element.id}"
41
+ # `console.log('target: ', target);`
40
42
  handlers = handlers[element.id] if handlers
41
43
 
42
44
  if handlers
@@ -1,9 +1,31 @@
1
+ # The draw cycle is responsible for queueing redraws until all of the events have
2
+ # fired. Once that is done, everything will be redrawn. This prevents bindings
3
+ # from being drawn multiple times before all events have propigated.
1
4
  class DrawCycle
2
5
  def initialize
3
6
  @queue = {}
7
+ @timer = nil
4
8
  end
5
9
 
6
10
  def queue(binding)
11
+ @queue[binding] = true
12
+
13
+ unless @timer
14
+ # Flush once everything else has finished running
15
+ @timer = `setTimeout(function() { self.$flush(); }, 0);`
16
+ end
17
+ end
18
+
19
+ def flush
20
+ @timer = nil
21
+
22
+ work_queue = @queue
23
+ @queue = {}
24
+
25
+ work_queue.each_pair do |binding,_|
26
+ # Call the update if queued
27
+ binding.update
28
+ end
7
29
 
8
30
  end
9
31
  end
@@ -1,6 +1,5 @@
1
1
  if RUBY_PLATFORM == 'opal'
2
2
  require 'opal'
3
-
4
3
  require 'opal-jquery'
5
4
  end
6
5
  require 'volt/models'
@@ -31,6 +30,15 @@ require 'volt/benchmark/benchmark'
31
30
  require 'volt/page/draw_cycle'
32
31
  require 'volt/page/tasks'
33
32
 
33
+ if RUBY_PLATFORM == 'opal'
34
+ require 'promise.rb'
35
+ else
36
+ # Opal doesn't expose its promise library directly
37
+ spec = Gem::Specification.find_by_name("opal")
38
+ require(spec.gem_dir + "/stdlib/promise")
39
+ end
40
+
41
+
34
42
  class Page
35
43
  attr_reader :url, :params, :page, :templates, :routes, :draw_cycle, :events
36
44
 
@@ -164,6 +172,7 @@ class Page
164
172
  title_target = AttributeTarget.new
165
173
  title_target.on('changed') do
166
174
  title = title_target.to_html
175
+ # puts "SET TITLE: #{title.inspect}: #{title_target.inspect}"
167
176
  `document.title = title;`
168
177
  end
169
178
  TemplateRenderer.new(self, title_target, main_controller, "main", "home/index/index/title")
@@ -4,36 +4,43 @@ class ReactiveTemplate
4
4
  def initialize(page, context, template_path)
5
5
  # puts "New Reactive Template: #{context.inspect} - #{template_path.inspect}"
6
6
  @template_path = template_path
7
- @target = AttributeTarget.new
7
+ @target = AttributeTarget.new(nil, nil, self)
8
8
  @template = TemplateRenderer.new(page, @target, context, "main", template_path)
9
- end
10
9
 
11
- def event_added(event, scope_provider, first)
12
- if first && !@template_listener
13
- @template_listener = @target.on('changed') { update }
14
- end
15
10
  end
16
11
 
17
- def event_removed(event, last)
18
- if last && @template_listener
19
- @template_listener.remove
20
- @template_listener = nil
21
- end
12
+ def reactive?
13
+ true
22
14
  end
23
15
 
16
+ # def event_added(event, scope_provider, first, first_for_event)
17
+ # if first && !@template_listener
18
+ # @template_listener = @target.on('changed') { update }
19
+ # end
20
+ # end
21
+ #
22
+ # def event_removed(event, last, last_for_event)
23
+ # if last && @template_listener
24
+ # @template_listener.remove
25
+ # @template_listener = nil
26
+ # end
27
+ # end
28
+
24
29
  # Render the template and get the current value
25
30
  def cur
26
31
  @target.to_html
27
32
  end
28
33
 
29
- # TODO: improve
30
- def skip_current_queue_flush
31
- true
34
+ def update
35
+ trigger!('changed')
32
36
  end
33
37
 
38
+ def remove
39
+ @template.remove
34
40
 
35
- def update
36
- trigger!('changed')
41
+ @template = nil
42
+ @target = nil
43
+ @template_path = nil
37
44
  end
38
45
 
39
46
  end
@@ -16,7 +16,7 @@ class SubContext
16
16
 
17
17
  def method_missing(method_name, *args, &block)
18
18
  method_name = method_name.to_s
19
- if @locals[method_name]
19
+ if @locals.has_key?(method_name)
20
20
  return @locals[method_name]
21
21
  elsif @context
22
22
  return @context.send(method_name, *args, &block)
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'volt/page/targets/base_section'
5
5
 
6
- class AttributeSection
6
+ class AttributeSection < BaseSection
7
7
  def initialize(target, binding_name)
8
8
  @target = target
9
9
  @binding_name = binding_name
@@ -31,7 +31,8 @@ class AttributeSection
31
31
  end
32
32
 
33
33
  def remove
34
+ # TODO: is this getting run for no reason?
34
35
  node = @target.find_by_binding_id(@binding_name)
35
- node.remove
36
+ node.remove if node
36
37
  end
37
38
  end
@@ -7,10 +7,6 @@ require 'volt/page/targets/binding_document/html_node'
7
7
  # a string that can then be used to update a attribute binding.
8
8
 
9
9
  class AttributeTarget < ComponentNode
10
- # TODO: improve
11
- def skip_current_queue_flush
12
- true
13
- end
14
10
 
15
11
  def section(*args)
16
12
  return AttributeSection.new(self, *args)
@@ -1,5 +1,9 @@
1
+ require 'volt/page/targets/dom_template'
2
+
1
3
  # Class to describe the interface for sections
2
4
  class BaseSection
5
+ @@template_cache = {}
6
+
3
7
  def remove
4
8
  raise "not implemented"
5
9
  end
@@ -11,4 +15,25 @@ class BaseSection
11
15
  def insert_anchor_before_end
12
16
  raise "not implemented"
13
17
  end
18
+
19
+ def set_content_to_template(page, template_name)
20
+ if self.is_a?(DomSection)
21
+ dom_template = (@@template_cache[template_name] ||= DomTemplate.new(page, template_name))
22
+
23
+ return set_template(dom_template)
24
+ else
25
+ template = page.templates[template_name]
26
+
27
+ if template
28
+ html = template['html']
29
+ bindings = template['bindings']
30
+ else
31
+ html = "<div>-- &lt; missing template #{template_name.inspect.gsub('<', '&lt;').gsub('>', '&gt;')} &gt; --</div>"
32
+ bindings = {}
33
+ end
34
+
35
+ return set_content_and_rezero_bindings(html, bindings)
36
+ end
37
+ end
38
+
14
39
  end
@@ -9,21 +9,19 @@ class ComponentNode < BaseNode
9
9
  include Events
10
10
 
11
11
  attr_accessor :parent, :binding_id, :nodes
12
- def initialize(binding_id=nil, parent=nil)
12
+ def initialize(binding_id=nil, parent=nil, root=nil)
13
13
  @nodes = []
14
14
  @binding_id = binding_id
15
15
  @parent = parent
16
-
17
- @change_listener = on('changed') do
18
- if @parent
19
- @parent.trigger!('changed')
20
- end
21
- end
16
+ @root = root
22
17
  end
23
18
 
24
- # TODO: improve
25
- def skip_current_queue_flush
26
- true
19
+ def trigger!(*args, &block)
20
+ if @root
21
+ @root.trigger!(*args, &block)
22
+ else
23
+ super
24
+ end
27
25
  end
28
26
 
29
27
  def text=(text)
@@ -44,7 +42,7 @@ class ComponentNode < BaseNode
44
42
  # Open
45
43
  binding_id = part.match(/\<\!\-\- \$([0-9]+) \-\-\>/)[1].to_i
46
44
 
47
- sub_node = ComponentNode.new(binding_id, current_node)
45
+ sub_node = ComponentNode.new(binding_id, current_node, @root || self)
48
46
  current_node << sub_node
49
47
  current_node = sub_node
50
48
  when /\<\!\-\- \$\/[0-9]+ \-\-\>/
@@ -93,6 +91,8 @@ class ComponentNode < BaseNode
93
91
  @nodes = []
94
92
 
95
93
  trigger!('changed')
94
+
95
+ # @binding_id = nil
96
96
  end
97
97
 
98
98
  def remove_anchors
@@ -100,10 +100,9 @@ class ComponentNode < BaseNode
100
100
 
101
101
  @parent.nodes.delete(self)
102
102
 
103
- @change_listener.remove
104
- @change_listener = nil
105
-
106
103
  trigger!('changed')
104
+ @parent = nil
105
+ @binding_id = nil
107
106
  end
108
107
 
109
108
  def inspect
@@ -8,4 +8,8 @@ class HtmlNode < BaseNode
8
8
  def to_html
9
9
  @html
10
10
  end
11
+
12
+ def inspect
13
+ "<HtmlNode #{@html.inspect}>"
14
+ end
11
15
  end