volt 0.7.1 → 0.7.2

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.
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