volt 0.9.3.pre1 → 0.9.3.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +15 -1
  4. data/Gemfile +30 -3
  5. data/README.md +7 -2
  6. data/Rakefile +17 -0
  7. data/app/volt/models/user.rb +1 -1
  8. data/app/volt/tasks/live_query/live_query.rb +0 -1
  9. data/app/volt/tasks/live_query/live_query_pool.rb +8 -2
  10. data/app/volt/tasks/live_query/query_tracker.rb +2 -2
  11. data/app/volt/tasks/query_tasks.rb +10 -27
  12. data/app/volt/tasks/store_tasks.rb +6 -5
  13. data/app/volt/tasks/user_tasks.rb +2 -2
  14. data/docs/UPGRADE_GUIDE.md +14 -0
  15. data/lib/volt/boot.rb +1 -0
  16. data/lib/volt/cli/asset_compile.rb +25 -7
  17. data/lib/volt/cli/console.rb +6 -5
  18. data/lib/volt/cli/generate.rb +2 -2
  19. data/lib/volt/config.rb +2 -1
  20. data/lib/volt/controllers/http_controller.rb +4 -3
  21. data/lib/volt/controllers/model_controller.rb +41 -19
  22. data/lib/volt/controllers/template_helpers.rb +19 -0
  23. data/lib/volt/extra_core/array.rb +6 -0
  24. data/lib/volt/extra_core/hash.rb +8 -26
  25. data/lib/volt/extra_core/string.rb +1 -1
  26. data/lib/volt/models/array_model.rb +12 -4
  27. data/lib/volt/models/associations.rb +11 -13
  28. data/lib/volt/models/buffer.rb +1 -1
  29. data/lib/volt/models/model.rb +22 -13
  30. data/lib/volt/models/model_helpers/model_change_helpers.rb +0 -1
  31. data/lib/volt/models/model_helpers/model_helpers.rb +11 -0
  32. data/lib/volt/models/permissions.rb +9 -12
  33. data/lib/volt/models/persistors/array_store.rb +7 -7
  34. data/lib/volt/models/persistors/base.rb +9 -0
  35. data/lib/volt/models/persistors/cookies.rb +0 -4
  36. data/lib/volt/models/persistors/flash.rb +0 -4
  37. data/lib/volt/models/persistors/local_store.rb +0 -4
  38. data/lib/volt/models/persistors/model_store.rb +13 -21
  39. data/lib/volt/models/persistors/page.rb +22 -0
  40. data/lib/volt/models/persistors/params.rb +0 -4
  41. data/lib/volt/models/persistors/query/query_listener.rb +3 -2
  42. data/lib/volt/models/url.rb +2 -2
  43. data/lib/volt/models/validators/unique_validator.rb +1 -1
  44. data/lib/volt/models.rb +1 -0
  45. data/lib/volt/page/bindings/attribute_binding.rb +2 -2
  46. data/lib/volt/page/bindings/base_binding.rb +7 -3
  47. data/lib/volt/page/bindings/bindings.rb +9 -0
  48. data/lib/volt/page/bindings/content_binding.rb +2 -2
  49. data/lib/volt/page/bindings/each_binding.rb +16 -12
  50. data/lib/volt/page/bindings/event_binding.rb +4 -4
  51. data/lib/volt/page/bindings/if_binding.rb +3 -3
  52. data/lib/volt/page/bindings/view_binding.rb +4 -4
  53. data/lib/volt/page/bindings/yield_binding.rb +3 -3
  54. data/lib/volt/page/channel.rb +6 -0
  55. data/lib/volt/page/channel_stub.rb +1 -1
  56. data/lib/volt/page/page.rb +20 -54
  57. data/lib/volt/page/path_string_renderer.rb +5 -6
  58. data/lib/volt/page/string_template_renderer.rb +2 -2
  59. data/lib/volt/page/targets/attribute_section.rb +47 -0
  60. data/lib/volt/page/targets/base_section.rb +5 -5
  61. data/lib/volt/page/targets/binding_document/component_node.rb +6 -1
  62. data/lib/volt/page/template_renderer.rb +4 -4
  63. data/lib/volt/reactive/computation.rb +32 -3
  64. data/lib/volt/router/routes.rb +5 -5
  65. data/lib/volt/server/component_templates.rb +30 -2
  66. data/lib/volt/server/forking_server.rb +2 -2
  67. data/lib/volt/server/message_bus/base_message_bus.rb +52 -0
  68. data/lib/volt/server/message_bus/message_encoder.rb +64 -0
  69. data/lib/volt/server/message_bus/peer_to_peer/peer_connection.rb +186 -0
  70. data/lib/volt/server/message_bus/peer_to_peer/peer_server.rb +78 -0
  71. data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +57 -0
  72. data/lib/volt/server/message_bus/peer_to_peer/socket_with_timeout.rb +27 -0
  73. data/lib/volt/server/message_bus/peer_to_peer.rb +198 -0
  74. data/lib/volt/server/message_bus/redis.rb +1 -0
  75. data/lib/volt/server/rack/asset_files.rb +2 -2
  76. data/lib/volt/server/rack/component_paths.rb +1 -1
  77. data/lib/volt/server/rack/http_resource.rb +3 -2
  78. data/lib/volt/server/rack/opal_files.rb +6 -9
  79. data/lib/volt/server/websocket/websocket_handler.rb +0 -3
  80. data/lib/volt/server.rb +5 -3
  81. data/lib/volt/spec/setup.rb +11 -12
  82. data/lib/volt/tasks/dispatcher.rb +8 -12
  83. data/lib/volt/tasks/task_handler.rb +3 -2
  84. data/lib/volt/utils/csso_patch.rb +24 -0
  85. data/lib/volt/utils/promise_patch.rb +2 -0
  86. data/lib/volt/version.rb +1 -1
  87. data/lib/volt/volt/app.rb +73 -36
  88. data/lib/volt/volt/server_setup/app.rb +81 -0
  89. data/lib/volt.rb +22 -1
  90. data/spec/apps/kitchen_sink/Gemfile +1 -1
  91. data/spec/controllers/http_controller_spec.rb +5 -3
  92. data/spec/controllers/model_controller_spec.rb +2 -2
  93. data/spec/extra_core/hash_spec.rb +9 -0
  94. data/spec/integration/list_spec.rb +3 -3
  95. data/spec/models/associations_spec.rb +10 -2
  96. data/spec/models/dirty_spec.rb +7 -7
  97. data/spec/models/model_spec.rb +10 -2
  98. data/spec/models/permissions_spec.rb +9 -0
  99. data/spec/models/persistors/store_spec.rb +8 -0
  100. data/spec/page/bindings/content_binding_spec.rb +6 -2
  101. data/spec/page/bindings/each_binding_spec.rb +59 -0
  102. data/spec/page/bindings/if_binding_spec.rb +57 -0
  103. data/spec/page/path_string_renderer_spec.rb +5 -5
  104. data/spec/reactive/computation_spec.rb +65 -1
  105. data/spec/router/routes_spec.rb +1 -1
  106. data/spec/server/html_parser/sandlebars_parser_spec.rb +12 -22
  107. data/spec/server/message_bus/message_encoder_spec.rb +49 -0
  108. data/spec/server/message_bus/peer_to_peer/peer_connection_spec.rb +108 -0
  109. data/spec/server/message_bus/peer_to_peer/peer_server_spec.rb +66 -0
  110. data/spec/server/message_bus/peer_to_peer/socket_with_timeout_spec.rb +11 -0
  111. data/spec/server/message_bus/peer_to_peer_spec.rb +11 -0
  112. data/spec/server/rack/asset_files_spec.rb +1 -1
  113. data/spec/server/rack/http_resource_spec.rb +4 -4
  114. data/spec/spec_helper.rb +16 -3
  115. data/spec/tasks/dispatcher_spec.rb +17 -5
  116. data/spec/tasks/live_query_spec.rb +1 -1
  117. data/spec/tasks/query_tracker_spec.rb +34 -34
  118. data/spec/tasks/user_tasks_spec.rb +4 -2
  119. data/templates/project/Gemfile.tt +14 -3
  120. data/templates/project/config/app.rb.tt +27 -2
  121. data/volt.gemspec +3 -8
  122. metadata +32 -101
  123. data/docs/FAQ.md +0 -7
@@ -0,0 +1,9 @@
1
+ # A file to require all client side bindings
2
+ require 'volt/page/bindings/attribute_binding'
3
+ require 'volt/page/bindings/content_binding'
4
+ require 'volt/page/bindings/each_binding'
5
+ require 'volt/page/bindings/if_binding'
6
+ require 'volt/page/bindings/view_binding'
7
+ require 'volt/page/bindings/yield_binding'
8
+ require 'volt/page/bindings/component_binding'
9
+ require 'volt/page/bindings/event_binding'
@@ -6,8 +6,8 @@ module Volt
6
6
  HTML_ESCAPE_REGEXP = /[&"'><\n]/
7
7
  HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;', "\n" => "<br />\n" }
8
8
 
9
- def initialize(page, target, context, binding_name, getter)
10
- super(page, target, context, binding_name)
9
+ def initialize(volt_app, target, context, binding_name, getter)
10
+ super(volt_app, target, context, binding_name)
11
11
 
12
12
  # Listen for changes
13
13
  @computation = lambda do
@@ -2,8 +2,8 @@ require 'volt/page/bindings/base_binding'
2
2
 
3
3
  module Volt
4
4
  class EachBinding < BaseBinding
5
- def initialize(page, target, context, binding_name, getter, variable_name, index_name, template_name)
6
- super(page, target, context, binding_name)
5
+ def initialize(volt_app, target, context, binding_name, getter, variable_name, index_name, template_name)
6
+ super(volt_app, target, context, binding_name)
7
7
 
8
8
  @item_name = variable_name
9
9
  @index_name = index_name
@@ -14,18 +14,22 @@ module Volt
14
14
  @getter = getter
15
15
 
16
16
  # Listen for changes
17
- @computation = -> { reload }.watch!
18
- end
17
+ @computation = lambda do
18
+ begin
19
+ value = @context.instance_eval(&@getter)
20
+ rescue => e
21
+ Volt.logger.error("EachBinding Error: #{e.inspect}")
22
+ value = []
23
+ end
19
24
 
20
- # When a changed event happens, we update to the new size.
21
- def reload
22
- begin
23
- value = @context.instance_eval(&@getter)
24
- rescue => e
25
- Volt.logger.error("EachBinding Error: #{e.inspect}")
26
- value = []
25
+ value
26
+ end.watch_and_resolve! do |value|
27
+ update(value)
27
28
  end
29
+ end
28
30
 
31
+ # When a changed event happens, we update to the new size.
32
+ def update(value)
29
33
  # Since we're checking things like size, we don't want this to be re-triggered on a
30
34
  # size change, so we run without tracking.
31
35
  Computation.run_without_tracking do
@@ -110,7 +114,7 @@ module Volt
110
114
  end
111
115
  end
112
116
 
113
- item_template = TemplateRenderer.new(@page, @target, item_context, binding_name, @template_name)
117
+ item_template = TemplateRenderer.new(@volt_app, @target, item_context, binding_name, @template_name)
114
118
  @templates.insert(position, item_template)
115
119
 
116
120
  update_indexes_after(position)
@@ -29,8 +29,8 @@ module Volt
29
29
  class EventBinding < BaseBinding
30
30
  attr_accessor :context, :binding_name
31
31
 
32
- def initialize(page, target, context, binding_name, event_name, call_proc)
33
- super(page, target, context, binding_name)
32
+ def initialize(volt_app, target, context, binding_name, event_name, call_proc)
33
+ super(volt_app, target, context, binding_name)
34
34
  @event_name = event_name
35
35
 
36
36
  handler = proc do |js_event|
@@ -42,12 +42,12 @@ module Volt
42
42
  result = @context.instance_exec(event, &call_proc)
43
43
  end
44
44
 
45
- @listener = @page.events.add(event_name, self, handler)
45
+ @listener = page.events.add(event_name, self, handler)
46
46
  end
47
47
 
48
48
  # Remove the event binding
49
49
  def remove
50
- @page.events.remove(@event_name, self)
50
+ page.events.remove(@event_name, self)
51
51
  end
52
52
  end
53
53
  end
@@ -2,8 +2,8 @@ require 'volt/page/bindings/base_binding'
2
2
 
3
3
  module Volt
4
4
  class IfBinding < BaseBinding
5
- def initialize(page, target, context, binding_name, branches)
6
- super(page, target, context, binding_name)
5
+ def initialize(volt_app, target, context, binding_name, branches)
6
+ super(volt_app, target, context, binding_name)
7
7
 
8
8
  getter, template_name = branches[0]
9
9
 
@@ -88,7 +88,7 @@ module Volt
88
88
  end
89
89
 
90
90
  if true_template
91
- @template = TemplateRenderer.new(@page, @target, @context, binding_name, true_template)
91
+ @template = TemplateRenderer.new(@volt_app, @target, @context, binding_name, true_template)
92
92
  end
93
93
  end
94
94
  end
@@ -10,8 +10,8 @@ module Volt
10
10
  # lookup paths in ViewLookupForPath
11
11
  # @param [String|nil] content_template_path is the path to the template for the content
12
12
  # provided in the tag.
13
- def initialize(page, target, context, binding_name, binding_in_path, getter, content_template_path = nil)
14
- super(page, target, context, binding_name)
13
+ def initialize(volt_app, target, context, binding_name, binding_in_path, getter, content_template_path = nil)
14
+ super(volt_app, target, context, binding_name)
15
15
 
16
16
  @content_template_path = content_template_path
17
17
 
@@ -189,7 +189,7 @@ module Volt
189
189
  # from the group)
190
190
  generated_new = true
191
191
  # Setup the controller
192
- controller_class.new(*args)
192
+ controller_class.new(@volt_app, *args)
193
193
  end
194
194
 
195
195
  # Fetch grouped controllers if we're grouping
@@ -217,7 +217,7 @@ module Volt
217
217
 
218
218
  # The context for templates can be either a controller, or the original context.
219
219
  def render_template(full_path, path)
220
- @current_template = TemplateRenderer.new(@page, @target, @controller, @binding_name, full_path, path)
220
+ @current_template = TemplateRenderer.new(@volt_app, @target, @controller, @binding_name, full_path, path)
221
221
 
222
222
  call_ready
223
223
  end
@@ -5,8 +5,8 @@ require 'volt/page/template_renderer'
5
5
 
6
6
  module Volt
7
7
  class YieldBinding < BaseBinding
8
- def initialize(page, target, context, binding_name)
9
- super(page, target, context, binding_name)
8
+ def initialize(volt_app, target, context, binding_name)
9
+ super(volt_app, target, context, binding_name)
10
10
 
11
11
  # Get the path to the template to yield
12
12
  full_path = @context.attrs.content_template_path
@@ -14,7 +14,7 @@ module Volt
14
14
  # Grab the controller for the content
15
15
  controller = @context.attrs.content_controller
16
16
 
17
- @current_template = TemplateRenderer.new(@page, @target, controller, @binding_name, full_path)
17
+ @current_template = TemplateRenderer.new(volt_app, @target, controller, @binding_name, full_path)
18
18
  end
19
19
 
20
20
  def remove
@@ -64,6 +64,9 @@ module Volt
64
64
  @queue.each do |message|
65
65
  send_message(message)
66
66
  end
67
+
68
+ # Trigger a connect event
69
+ trigger!('connect')
67
70
  end
68
71
 
69
72
  def closed(error)
@@ -71,6 +74,9 @@ module Volt
71
74
  self.connected = false
72
75
  self.error = `error.reason`
73
76
 
77
+ # Trigger a disconnect event
78
+ trigger!('disconnect')
79
+
74
80
  reconnect!
75
81
  end
76
82
 
@@ -13,7 +13,7 @@ module Volt
13
13
 
14
14
  attr_reader :state, :error, :reconnect_interval
15
15
 
16
- def initiailze
16
+ def initialize
17
17
  @state = :connected
18
18
  end
19
19
 
@@ -1,38 +1,10 @@
1
- require 'opal'
2
- require 'volt/models'
3
- require 'volt/controllers/model_controller'
4
- require 'volt/tasks/task_handler'
5
- require 'volt/page/bindings/attribute_binding'
6
- require 'volt/page/bindings/content_binding'
7
- require 'volt/page/bindings/each_binding'
8
- require 'volt/page/bindings/if_binding'
9
- require 'volt/page/bindings/view_binding'
10
- require 'volt/page/bindings/yield_binding'
11
- require 'volt/page/bindings/component_binding'
12
- require 'volt/page/bindings/event_binding'
13
- require 'volt/page/template_renderer'
14
- require 'volt/page/string_template_renderer'
15
- require 'volt/page/document_events'
16
- require 'volt/page/sub_context'
17
- require 'volt/page/targets/dom_target'
18
- require 'volt/data_stores/base_adaptor_client'
19
-
20
- if RUBY_PLATFORM == 'opal'
21
- require 'volt/page/channel'
22
- else
23
- require 'volt/page/channel_stub'
24
- end
25
- require 'volt/router/routes'
26
- require 'volt/models/url'
27
- require 'volt/page/url_tracker'
28
- require 'volt/benchmark/benchmark'
29
- require 'volt/page/tasks'
30
1
 
31
2
  module Volt
32
3
  class Page
33
4
  attr_reader :url, :params, :page, :routes, :events
34
5
 
35
- def initialize
6
+ def initialize(volt_app)
7
+ @volt_app = volt_app
36
8
  # Run the code to setup the page
37
9
  @page = Model.new
38
10
 
@@ -44,24 +16,26 @@ module Volt
44
16
  @events = DocumentEvents.new
45
17
 
46
18
  if RUBY_PLATFORM == 'opal'
47
- # Setup escape binding for console
48
- `
49
- $(document).keyup(function(e) {
50
- if (e.keyCode == 27) {
51
- Opal.gvars.page.$launch_console();
52
- }
53
- });
54
-
55
- $(document).on('click', 'a', function(event) {
56
- return Opal.gvars.page.$link_clicked($(this).attr('href'), event);
57
- });
58
- `
19
+ if Volt.in_browser?
20
+ # Setup escape binding for console
21
+ `
22
+ $(document).keyup(function(e) {
23
+ if (e.keyCode == 27) {
24
+ Opal.gvars.page.$launch_console();
25
+ }
26
+ });
27
+
28
+ $(document).on('click', 'a', function(event) {
29
+ return Opal.gvars.page.$link_clicked($(this).attr('href'), event);
30
+ });
31
+ `
32
+ end
59
33
  end
60
34
 
61
35
  # Initialize tasks so we can get the reload message
62
36
  tasks if Volt.env.development?
63
37
 
64
- if Volt.client?
38
+ if Volt.in_browser?
65
39
  channel.on('reconnected') do
66
40
  @page._reconnected = true
67
41
 
@@ -178,13 +152,13 @@ module Volt
178
152
  # Do the initial url params parse
179
153
  @url_tracker.url_updated(true)
180
154
 
181
- main_controller = Main::MainController.new
155
+ main_controller = Main::MainController.new(@volt_app)
182
156
 
183
157
  # Setup main page template
184
- TemplateRenderer.new(self, DomTarget.new, main_controller, 'CONTENT', 'main/main/main/body')
158
+ TemplateRenderer.new(@volt_app, DomTarget.new, main_controller, 'CONTENT', 'main/main/main/body')
185
159
 
186
160
  # Setup title reactive template
187
- @title_template = StringTemplateRenderer.new(self, main_controller, 'main/main/main/title')
161
+ @title_template = StringTemplateRenderer.new(@volt_app, main_controller, 'main/main/main/title')
188
162
 
189
163
  # Watch for changes to the title template
190
164
  proc do
@@ -214,12 +188,4 @@ module Volt
214
188
  Volt.logger.error("Unable to restore: #{e.inspect}")
215
189
  end
216
190
  end
217
-
218
- if Volt.client?
219
- $page = Page.new
220
-
221
- `$(document).ready(function() {`
222
- $page.start
223
- `});`
224
- end
225
191
  end
@@ -9,13 +9,12 @@ module Volt
9
9
  class ViewLookupException < Exception; end
10
10
  class PathStringRenderer
11
11
  attr_reader :html
12
- def initialize(path, attrs = nil, page = nil, render_from_path = nil)
13
- # use the global page if one is not passed in
14
- page ||= $page
15
-
12
+ def initialize(volt_app, path, attrs = nil, page = nil, render_from_path = nil)
16
13
  # where to do the path lookup from
17
14
  render_from_path ||= 'main/main/main/body'
18
15
 
16
+ page ||= volt_app.page
17
+
19
18
  # Make path into a full path
20
19
  @view_lookup = Volt::ViewLookupForPath.new(page, render_from_path)
21
20
  full_path, controller_path = @view_lookup.path_for_template(path, nil)
@@ -26,10 +25,10 @@ module Volt
26
25
 
27
26
  controller_class, action = ControllerHandler.get_controller_and_action(controller_path)
28
27
 
29
- controller = controller_class.new # (SubContext.new(attrs, nil, true))
28
+ controller = controller_class.new(volt_app) # (SubContext.new(attrs, nil, true))
30
29
  controller.model = SubContext.new(attrs, nil, true)
31
30
 
32
- renderer = StringTemplateRenderer.new(page, controller, full_path)
31
+ renderer = StringTemplateRenderer.new(volt_app, controller, full_path)
33
32
 
34
33
  @html = renderer.html
35
34
 
@@ -5,12 +5,12 @@ module Volt
5
5
  # StringTemplateRenderer will intellegently update the string in the same way
6
6
  # a normal bindings will update the dom.
7
7
  class StringTemplateRenderer
8
- def initialize(page, context, template_path)
8
+ def initialize(volt_app, context, template_path)
9
9
  @dependency = Dependency.new
10
10
 
11
11
  @template_path = template_path
12
12
  @target = AttributeTarget.new(nil, nil, self)
13
- @template = TemplateRenderer.new(page, @target, context, 'main', template_path)
13
+ @template = TemplateRenderer.new(volt_app, @target, context, 'main', template_path)
14
14
  end
15
15
 
16
16
  # Render the template and get the current value
@@ -18,9 +18,56 @@ module Volt
18
18
  set_content_and_rezero_bindings(value, {})
19
19
  end
20
20
 
21
+ def insert_anchor_before_end(binding_name)
22
+ end_node = @target.find_by_binding_id(@binding_name)
23
+ if end_node.is_a?(ComponentNode)
24
+ component_node = ComponentNode.new(binding_name, end_node, end_node.root || end_node)
25
+ end_node.insert(-1, component_node)
26
+ else
27
+ raise "can not insert on HtmlNode"
28
+ end
29
+ end
30
+
31
+ # When using bindings, we have to change the binding id so we don't reuse
32
+ # the same id when rendering a binding multiple times.
33
+ def rezero_bindings(html, bindings)
34
+ @@base_binding_id ||= 20_000
35
+ # rezero
36
+ parts = html.split(/(\<\!\-\- \$\/?[0-9]+ \-\-\>)/).reject { |v| v == '' }
37
+
38
+ new_html = []
39
+ new_bindings = {}
40
+ id_map = {}
41
+
42
+ parts.each do |part|
43
+ case part
44
+ when /\<\!\-\- \$[0-9]+ \-\-\>/
45
+ # Open
46
+ binding_id = part.match(/\<\!\-\- \$([0-9]+) \-\-\>/)[1].to_i
47
+ binding = bindings[binding_id]
48
+ new_bindings[@@base_binding_id] = binding if binding
49
+
50
+ new_html << "<!-- $#{@@base_binding_id} -->"
51
+ id_map[binding_id] = @@base_binding_id
52
+ @@base_binding_id += 1
53
+ when /\<\!\-\- \$\/[0-9]+ \-\-\>/
54
+ # Close
55
+ binding_id = part.match(/\<\!\-\- \$\/([0-9]+) \-\-\>/)[1].to_i
56
+ new_html << "<!-- $/#{id_map[binding_id]} -->"
57
+ else
58
+ # html string
59
+ new_html << part
60
+ end
61
+ end
62
+
63
+ return new_html.join(''), new_bindings
64
+ end
65
+
21
66
  # Takes in our html and bindings, and rezero's the comment names, and the
22
67
  # bindings. Returns an updated bindings hash
23
68
  def set_content_and_rezero_bindings(html, bindings)
69
+ html, bindings = rezero_bindings(html, bindings)
70
+
24
71
  if @binding_name == 'main'
25
72
  @target.html = html
26
73
  else
@@ -6,19 +6,19 @@ module Volt
6
6
  @@template_cache = {}
7
7
 
8
8
  def remove
9
- fail 'not implemented'
9
+ fail 'remove is not implemented'
10
10
  end
11
11
 
12
12
  def remove_anchors
13
- fail 'not implemented'
13
+ fail 'remove_anchors is not implemented'
14
14
  end
15
15
 
16
- def insert_anchor_before_end
17
- fail 'not implemented'
16
+ def insert_anchor_before_end(binding_name)
17
+ fail 'insert_anchor_before_end is not implemented'
18
18
  end
19
19
 
20
20
  def set_template
21
- fail 'not implemented'
21
+ fail 'set_template is not implemented'
22
22
  end
23
23
 
24
24
  def set_content_to_template(page, template_name)
@@ -9,7 +9,7 @@ module Volt
9
9
  class ComponentNode < BaseNode
10
10
  include Eventable
11
11
 
12
- attr_accessor :parent, :binding_id, :nodes
12
+ attr_accessor :parent, :binding_id, :nodes, :root
13
13
 
14
14
  def initialize(binding_id = nil, parent = nil, root = nil)
15
15
  @nodes = []
@@ -65,6 +65,11 @@ module Volt
65
65
  @nodes << node
66
66
  end
67
67
 
68
+ def insert(index, node)
69
+ @nodes.insert(index, node)
70
+ changed!
71
+ end
72
+
68
73
  def to_html
69
74
  str = []
70
75
  @nodes.each do |node|
@@ -4,16 +4,16 @@ module Volt
4
4
  class TemplateRenderer < BaseBinding
5
5
  attr_reader :context
6
6
 
7
- def initialize(page, target, context, binding_name, template_name)
8
- super(page, target, context, binding_name)
7
+ def initialize(volt_app, target, context, binding_name, template_name)
8
+ super(volt_app, target, context, binding_name)
9
9
 
10
10
  @sub_bindings = []
11
11
 
12
- bindings = dom_section.set_content_to_template(page, template_name)
12
+ bindings = dom_section.set_content_to_template(volt_app.page, template_name)
13
13
 
14
14
  bindings.each_pair do |id, bindings_for_id|
15
15
  bindings_for_id.each do |binding|
16
- @sub_bindings << binding.call(page, target, context, id)
16
+ @sub_bindings << binding.call(volt_app, target, context, id)
17
17
  end
18
18
  end
19
19
  end
@@ -74,6 +74,10 @@ module Volt
74
74
  end
75
75
  end
76
76
 
77
+ def stopped?
78
+ @stopped
79
+ end
80
+
77
81
  # Runs in this computation as the current computation, returns the computation
78
82
  def run_in
79
83
  previous = Computation.current
@@ -172,20 +176,45 @@ class Proc
172
176
  #
173
177
  # Example:
174
178
  # -> { }
175
- def watch_and_resolve!
179
+ def watch_and_resolve!(yield_nil_for_unresolved_promise=false)
176
180
  unless block_given?
177
181
  fail 'watch_and_resolve! requires a block to call when the value is resolved or another value other than a promise is returned in the watch.'
178
182
  end
179
183
 
180
- computation = proc do
184
+ # Keep results between runs
185
+ result = nil
186
+
187
+ computation = proc do |comp|
181
188
  result = call
189
+ last_promise = nil
182
190
 
183
191
  if result.is_a?(Promise)
192
+ last_promise = result
193
+
194
+ # Often you want a to be alerted that an unresolved promise is waiting
195
+ # to be resolved.
196
+ if yield_nil_for_unresolved_promise && !result.resolved?
197
+ yield(nil)
198
+ end
199
+
184
200
  result.then do |final|
185
- yield(final)
201
+ # Check to make sure that a new value didn't get reactively pushed
202
+ # before the promise resolved.
203
+ if last_promise.is_a?(Promise) && last_promise == result
204
+ # Don't resolve if the computation was stopped
205
+ unless comp.stopped?
206
+ yield(final)
207
+ end
208
+
209
+ # Clear result for GC
210
+ result = nil
211
+ end
186
212
  end
187
213
  else
188
214
  yield(result)
215
+
216
+ # Clear result for GC
217
+ result = nil
189
218
  end
190
219
  end.watch!
191
220
 
@@ -5,8 +5,8 @@ module Volt
5
5
  # a url to params, and params to url.
6
6
  # routes do
7
7
  # client "/about", _view: 'about'
8
- # client "/blog/{_id}/edit", _view: 'blog/edit', _action: 'edit'
9
- # client "/blog/{_id}", _view: 'blog/show', _action: 'show'
8
+ # client "/blog/{id}/edit", _view: 'blog/edit', _action: 'edit'
9
+ # client "/blog/{id}", _view: 'blog/show', _action: 'show'
10
10
  # client "/blog", _view: 'blog'
11
11
  # client "/blog/new", _view: 'blog/new', _action: 'new'
12
12
  # client "/cool/{_name}", _view: 'cool'
@@ -27,16 +27,16 @@ module Volt
27
27
  # @indirect_routes = {
28
28
  # '*' => {
29
29
  # 'edit' => {
30
- # nil => {_id: 1, _view: 'blog/edit', _action: 'edit'}
30
+ # nil => {id: 1, _view: 'blog/edit', _action: 'edit'}
31
31
  # }
32
- # nil => {_id: 1, _view: 'blog/show', _action: 'show'}
32
+ # nil => {id: 1, _view: 'blog/show', _action: 'show'}
33
33
  # }
34
34
  # }
35
35
  # }
36
36
  #
37
37
  # Match for params
38
38
  # @param_matches = [
39
- # {_id: nil, _view: 'blog/edit', _action: 'edit'} => Proc.new {|params| "/blog/#{params.id}/edit", params.reject {|k,v| k == :id }}
39
+ # {id: nil, _view: 'blog/edit', _action: 'edit'} => Proc.new {|params| "/blog/#{params.id}/edit", params.reject {|k,v| k == :id }}
40
40
  # ]
41
41
  class Routes
42
42
  def initialize
@@ -79,6 +79,9 @@ module Volt
79
79
 
80
80
  # Load all templates in the folder
81
81
  Dir["#{views_path}*/*.{#{exts.join(',')}}"].sort.each do |view_path|
82
+ path_parts = view_path.scan(/([^\/]+)\/([^\/]+)\/[^\/]+\/([^\/]+)[.](html|email)$/)
83
+ component_name, controller_name, view, _ = path_parts[0]
84
+
82
85
  # file extension
83
86
  format = File.extname(view_path).downcase.delete('.').to_sym
84
87
 
@@ -88,6 +91,8 @@ module Volt
88
91
 
89
92
  file_contents = File.read(view_path)
90
93
 
94
+ template_calls = []
95
+
91
96
  # Process template if we have a handler for this file type
92
97
  if handler = ComponentTemplates.handler_for_extension(format)
93
98
  file_contents = handler.call(file_contents)
@@ -107,8 +112,11 @@ module Volt
107
112
  binding_code = "{#{binding_code.join(', ')}}"
108
113
 
109
114
  code << "#{page_reference}.add_template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})\n"
115
+ template_calls << "template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})"
110
116
  end
111
117
  end
118
+
119
+ # puts "module #{component_name.camelize}\n class #{controller_name.camelize}\n class VoltTemplates < VoltTemplates\n #{template_calls.join("\n")}\n end\n end\nend"
112
120
  end
113
121
 
114
122
  code
@@ -118,9 +126,29 @@ module Volt
118
126
  def generate_controller_code
119
127
  code = ''
120
128
  controllers_path = "#{@component_path}/controllers/"
129
+ views_path = "#{@component_path}/views/"
121
130
 
122
- Dir["#{controllers_path}*_controller.rb"].sort.each do |controller_path|
123
- code << File.read(controller_path) + "\n\n"
131
+ # Controllers are optional, specifying a view folder is enough to auto
132
+ # generate the controller.
133
+
134
+ implicit_controllers = Dir["#{views_path}*"].sort.map do |path|
135
+ # remove the /views/ folder and add _controller.rb
136
+ path.split('/').tap {|v| v[-2] = 'controllers' }.join('/') + '_controller.rb'
137
+ end
138
+ explicit_controllers = Dir["#{controllers_path}*_controller.rb"].sort
139
+
140
+ controllers = (implicit_controllers + explicit_controllers).uniq
141
+ controllers.each do |path|
142
+ if File.exists?(path)
143
+ code << File.read(path) + "\n\n"
144
+ else
145
+ # parts = path.scan(/([^\/]+)\/controllers\/([^\/]+)_controller[.]rb$/)
146
+ # component, controller = parts[0]
147
+
148
+ # # Generate a blank controller. (We need to actually generate one so
149
+ # # the Template can be attached to it for template inheritance)
150
+ # code << "\nmodule #{component.camelize}\n class #{controller.camelize} < Volt::ModelController\n end\nend\n"
151
+ end
124
152
  end
125
153
 
126
154
  code
@@ -58,11 +58,11 @@ module Volt
58
58
  # Running as child
59
59
  @reader.close
60
60
 
61
- @server.boot_volt
61
+ volt_app = @server.boot_volt
62
62
  @rack_app = @server.new_server
63
63
 
64
64
  # Set the drb object locally
65
- @dispatcher = Dispatcher.new
65
+ @dispatcher = Dispatcher.new(volt_app)
66
66
  drb_object = DRb.start_service('drbunix:', [self, @dispatcher])
67
67
 
68
68
  @writer.puts(drb_object.uri)