volt 0.7.23 → 0.8.0

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -1
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile +8 -0
  5. data/Guardfile +2 -2
  6. data/Readme.md +139 -136
  7. data/VERSION +1 -1
  8. data/app/volt/assets/js/setImmediate.js +175 -0
  9. data/app/volt/tasks/live_query/data_store.rb +0 -2
  10. data/app/volt/tasks/live_query/live_query.rb +4 -4
  11. data/docs/GETTING_STARTED.md +24 -3
  12. data/docs/WHY.md +1 -22
  13. data/lib/volt.rb +20 -1
  14. data/lib/volt/console.rb +20 -0
  15. data/lib/volt/controllers/model_controller.rb +25 -11
  16. data/lib/volt/extra_core/object.rb +2 -14
  17. data/lib/volt/extra_core/string.rb +4 -0
  18. data/lib/volt/models.rb +0 -1
  19. data/lib/volt/models/array_model.rb +8 -16
  20. data/lib/volt/models/cursor.rb +1 -1
  21. data/lib/volt/models/model.rb +40 -60
  22. data/lib/volt/models/model_hash_behaviour.rb +10 -24
  23. data/lib/volt/models/model_helpers.rb +2 -2
  24. data/lib/volt/models/model_state.rb +1 -1
  25. data/lib/volt/models/model_wrapper.rb +4 -4
  26. data/lib/volt/models/persistors/array_store.rb +44 -28
  27. data/lib/volt/models/persistors/base.rb +1 -1
  28. data/lib/volt/models/persistors/model_store.rb +1 -1
  29. data/lib/volt/models/persistors/params.rb +5 -1
  30. data/lib/volt/models/persistors/query/query_listener.rb +2 -0
  31. data/lib/volt/models/persistors/store.rb +3 -2
  32. data/lib/volt/models/persistors/store_state.rb +7 -2
  33. data/lib/volt/models/url.rb +35 -29
  34. data/lib/volt/models/validations.rb +7 -17
  35. data/lib/volt/page/bindings/attribute_binding.rb +57 -39
  36. data/lib/volt/page/bindings/base_binding.rb +0 -14
  37. data/lib/volt/page/bindings/content_binding.rb +15 -18
  38. data/lib/volt/page/bindings/each_binding.rb +67 -34
  39. data/lib/volt/page/bindings/if_binding.rb +15 -12
  40. data/lib/volt/page/bindings/template_binding.rb +77 -59
  41. data/lib/volt/page/bindings/template_binding/grouped_controllers.rb +19 -4
  42. data/lib/volt/page/channel.rb +22 -38
  43. data/lib/volt/page/channel_stub.rb +3 -6
  44. data/lib/volt/page/page.rb +24 -26
  45. data/lib/volt/page/string_template_renderer.rb +46 -0
  46. data/lib/volt/page/sub_context.rb +7 -1
  47. data/lib/volt/page/targets/binding_document/component_node.rb +11 -9
  48. data/lib/volt/page/tasks.rb +3 -2
  49. data/lib/volt/page/url_tracker.rb +4 -3
  50. data/lib/volt/reactive/computation.rb +131 -0
  51. data/lib/volt/reactive/dependency.rb +71 -0
  52. data/lib/volt/reactive/eventable.rb +82 -0
  53. data/lib/volt/reactive/hash_dependency.rb +36 -0
  54. data/lib/volt/{controllers → reactive}/reactive_accessors.rb +8 -11
  55. data/lib/volt/reactive/reactive_array.rb +100 -193
  56. data/lib/volt/reactive/reactive_hash.rb +49 -0
  57. data/lib/volt/server/html_parser/attribute_scope.rb +24 -4
  58. data/lib/volt/server/html_parser/if_view_scope.rb +15 -15
  59. data/lib/volt/server/html_parser/view_scope.rb +31 -1
  60. data/spec/apps/kitchen_sink/Gemfile +4 -8
  61. data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +8 -0
  62. data/spec/apps/kitchen_sink/app/main/config/routes.rb +8 -1
  63. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +8 -0
  64. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +73 -0
  65. data/spec/apps/kitchen_sink/app/main/views/main/index.html +6 -1
  66. data/spec/apps/kitchen_sink/app/main/views/main/main.html +26 -6
  67. data/spec/apps/kitchen_sink/app/main/views/main/store.html +6 -0
  68. data/spec/controllers/reactive_accessors_spec.rb +13 -15
  69. data/spec/integration/bindings_spec.rb +159 -0
  70. data/spec/integration/templates_spec.rb +15 -0
  71. data/spec/models/model_spec.rb +130 -228
  72. data/spec/reactive/computation_spec.rb +63 -0
  73. data/spec/reactive/dependency_spec.rb +5 -0
  74. data/spec/reactive/eventable_spec.rb +48 -0
  75. data/spec/reactive/reactive_array_spec.rb +97 -0
  76. data/spec/router/routes_spec.rb +26 -27
  77. data/spec/server/html_parser/view_parser_spec.rb +3 -21
  78. data/spec/server/rack/asset_files_spec.rb +1 -1
  79. data/templates/project/app/main/views/main/main.html +2 -2
  80. metadata +29 -41
  81. data/lib/volt/extra_core/time.rb +0 -16
  82. data/lib/volt/page/draw_cycle.rb +0 -31
  83. data/lib/volt/page/memory_test.rb +0 -26
  84. data/lib/volt/page/reactive_template.rb +0 -32
  85. data/lib/volt/reactive/array_extensions.rb +0 -12
  86. data/lib/volt/reactive/destructive_methods.rb +0 -19
  87. data/lib/volt/reactive/event_chain.rb +0 -125
  88. data/lib/volt/reactive/events.rb +0 -216
  89. data/lib/volt/reactive/object_tracking.rb +0 -14
  90. data/lib/volt/reactive/reactive_block.rb +0 -88
  91. data/lib/volt/reactive/reactive_generator.rb +0 -44
  92. data/lib/volt/reactive/reactive_tags.rb +0 -71
  93. data/lib/volt/reactive/reactive_value.rb +0 -427
  94. data/lib/volt/reactive/string_extensions.rb +0 -31
  95. data/spec/integration/test_integration_spec.rb +0 -14
  96. data/spec/models/event_chain_spec.rb +0 -150
  97. data/spec/models/model_buffers_spec.rb +0 -9
  98. data/spec/models/old_model_spec.rb +0 -67
  99. data/spec/models/reactive_array_spec.rb +0 -364
  100. data/spec/models/reactive_block_spec.rb +0 -13
  101. data/spec/models/reactive_call_times_spec.rb +0 -28
  102. data/spec/models/reactive_generator_spec.rb +0 -58
  103. data/spec/models/reactive_tags_spec.rb +0 -35
  104. data/spec/models/reactive_value_spec.rb +0 -370
  105. data/spec/models/store_spec.rb +0 -16
  106. data/spec/models/string_extensions_spec.rb +0 -57
@@ -1,5 +1,7 @@
1
1
  # Some template bindings share the controller with other template bindings based
2
- # on a name. This class stores those and provides helper methods to clear/set/get.
2
+ # on a name. This class keeps track of the number of templates using this controller
3
+ # and clears it once no one else is using it. Use #get or #inc to add to the count.
4
+ # #clear removes 1 from the count. When the count is 0, delete the controller.
3
5
  class GroupedControllers
4
6
  @@controllers = {}
5
7
 
@@ -8,14 +10,27 @@ class GroupedControllers
8
10
  end
9
11
 
10
12
  def get
11
- @@controllers[@name]
13
+ return (controller = self.controller) && controller[0]
12
14
  end
13
15
 
14
16
  def set(controller)
15
- @@controllers[@name] = controller
17
+ @@controllers[@name] = [controller, 1]
18
+ end
19
+
20
+ def inc
21
+ controller[1] += 1
16
22
  end
17
23
 
18
24
  def clear
19
- @@controllers.delete(@name)
25
+ controller = self.controller
26
+ controller[1] -= 1
27
+ if controller[1] == 0
28
+ @@controllers.delete(@name)
29
+ end
20
30
  end
31
+
32
+ private
33
+ def controller
34
+ @@controllers[@name]
35
+ end
21
36
  end
@@ -1,30 +1,28 @@
1
1
  # The channel is the connection between the front end and the backend.
2
2
 
3
- require 'volt/reactive/events'
4
3
  require 'json'
4
+ require 'volt/reactive/reactive_accessors'
5
+ require 'volt/reactive/eventable'
5
6
 
6
7
  class Channel
7
- include ReactiveTags
8
+ include ReactiveAccessors
9
+ include Eventable
8
10
 
9
- attr_reader :status, :error, :reconnect_interval
11
+ reactive_accessor :connected, :status, :error, :reconnect_interval, :retry_count
10
12
 
11
13
  def initialize
12
14
  @socket = nil
13
- @status = :opening
14
- @connected = false
15
- @error = nil
16
- @retry_count = 0
15
+ self.status = :opening
16
+ self.connected = false
17
+ self.error = nil
18
+ self.retry_count = 0
17
19
  @queue = []
18
20
 
19
21
  connect!
20
22
  end
21
23
 
22
24
  def connected?
23
- @connected
24
- end
25
-
26
- def retry_count
27
- @retry_count
25
+ self.connected
28
26
  end
29
27
 
30
28
  def connect!
@@ -54,35 +52,23 @@ class Channel
54
52
  @queue.each do |message|
55
53
  send_message(message)
56
54
  end
57
-
58
- trigger!('open')
59
- trigger!('changed')
60
- if old_status == :reconnecting
61
- trigger!('reconnected')
62
- end
63
55
  end
64
56
 
65
57
  def closed(error)
66
- @status = :closed
67
- @connected = false
68
- @error = `error.reason`
69
-
70
- trigger!('closed')
71
- trigger!('changed')
58
+ self.status = :closed
59
+ self.connected = false
60
+ self.error = `error.reason`
72
61
 
73
62
  reconnect!
74
63
  end
75
64
 
76
65
  def reconnect!
77
- @status = :reconnecting
78
- @reconnect_interval ||= 0
79
- @reconnect_interval += (2000 + rand(5000))
80
- @retry_count += 1
81
-
82
- # Trigger changed for reconnect interval
83
- trigger!('changed')
66
+ self.status = :reconnecting
67
+ self.reconnect_interval ||= 0
68
+ self.reconnect_interval += (2000 + rand(5000))
69
+ self.retry_count += 1
84
70
 
85
- interval = @reconnect_interval
71
+ interval = self.reconnect_interval
86
72
 
87
73
  %x{
88
74
  setTimeout(function() {
@@ -93,14 +79,12 @@ class Channel
93
79
 
94
80
  def message_received(message)
95
81
  message = JSON.parse(message)
96
- trigger!('message', nil, *message)
97
- end
98
82
 
99
- tag_method(:send_message) do
100
- destructive!
83
+ trigger!('message', *message)
101
84
  end
85
+
102
86
  def send_message(message)
103
- if @status != :open
87
+ if self.status != :open
104
88
  @queue << message
105
89
  else
106
90
  # TODO: Temp: wrap message in an array, so we're sure its valid JSON
@@ -112,7 +96,7 @@ class Channel
112
96
  end
113
97
 
114
98
  def close!
115
- @status = :closed
99
+ self.status = :closed
116
100
  %x{
117
101
  this.socket.close();
118
102
  }
@@ -1,14 +1,14 @@
1
1
  # Acts the same as the Channel class on the front-end, but calls
2
2
  # directly instead of using sockjs.
3
3
 
4
- require 'volt/reactive/events'
5
4
  require 'volt/tasks/dispatcher'
5
+ require 'volt/reactive/eventable'
6
6
 
7
7
  # Behaves the same as the Channel class, only the Channel class uses
8
8
  # sockjs to pass messages to the backend. ChannelStub, simply passes
9
9
  # them directly to SocketConnectionHandlerStub.
10
10
  class ChannelStub
11
- include ReactiveTags
11
+ include Eventable
12
12
 
13
13
  attr_reader :state, :error, :reconnect_interval
14
14
 
@@ -22,12 +22,9 @@ class ChannelStub
22
22
  end
23
23
 
24
24
  def message_received(*message)
25
- trigger!('message', nil, *message)
25
+ trigger!('message', *message)
26
26
  end
27
27
 
28
- tag_method(:send_message) do
29
- destructive!
30
- end
31
28
  def send_message(message)
32
29
  SocketConnectionHandlerStub.new(self).process_message(message)
33
30
  end
@@ -12,7 +12,7 @@ require 'volt/page/bindings/template_binding'
12
12
  require 'volt/page/bindings/component_binding'
13
13
  require 'volt/page/bindings/event_binding'
14
14
  require 'volt/page/template_renderer'
15
- require 'volt/page/reactive_template'
15
+ require 'volt/page/string_template_renderer'
16
16
  require 'volt/page/document_events'
17
17
  require 'volt/page/sub_context'
18
18
  require 'volt/page/targets/dom_target'
@@ -26,26 +26,24 @@ require 'volt/router/routes'
26
26
  require 'volt/models/url'
27
27
  require 'volt/page/url_tracker'
28
28
  require 'volt/benchmark/benchmark'
29
- require 'volt/page/draw_cycle'
30
29
  require 'volt/page/tasks'
31
30
 
32
31
 
33
32
 
34
33
  class Page
35
- attr_reader :url, :params, :page, :templates, :routes, :draw_cycle, :events
34
+ attr_reader :url, :params, :page, :templates, :routes, :events
36
35
 
37
36
  def initialize
38
37
  @model_classes = {}
39
38
 
40
39
  # Run the code to setup the page
41
- @page = ReactiveValue.new(Model.new)
40
+ @page = Model.new
42
41
 
43
- @url = ReactiveValue.new(URL.new)
42
+ @url = URL.new
44
43
  @params = @url.params
45
44
  @url_tracker = UrlTracker.new(self)
46
45
 
47
46
  @events = DocumentEvents.new
48
- @draw_cycle = DrawCycle.new
49
47
 
50
48
  if RUBY_PLATFORM == 'opal'
51
49
  # Setup escape binding for console
@@ -65,25 +63,27 @@ class Page
65
63
  # Initialize tasks so we can get the reload message
66
64
  self.tasks if Volt.env.development?
67
65
 
68
- channel.on('reconnected') do
69
- @page._reconnected.cur = true
66
+ if Volt.client?
67
+ channel.on('reconnected') do
68
+ @page._reconnected = true
70
69
 
71
- `setTimeout(function() {`
72
- @page._reconnected.cur = false
73
- `}, 2000);`
70
+ `setTimeout(function() {`
71
+ @page._reconnected = false
72
+ `}, 2000);`
73
+ end
74
74
  end
75
75
  end
76
76
 
77
77
  def flash
78
- @flash ||= ReactiveValue.new(Model.new({}, persistor: Persistors::Flash))
78
+ @flash ||= Model.new({}, persistor: Persistors::Flash)
79
79
  end
80
80
 
81
81
  def store
82
- @store ||= ReactiveValue.new(Model.new({}, persistor: Persistors::StoreFactory.new(tasks)))
82
+ @store ||= Model.new({}, persistor: Persistors::StoreFactory.new(tasks))
83
83
  end
84
84
 
85
85
  def local_store
86
- @local_store ||= ReactiveValue.new(Model.new({}, persistor: Persistors::LocalStore))
86
+ @local_store ||= Model.new({}, persistor: Persistors::LocalStore)
87
87
  end
88
88
 
89
89
  def tasks
@@ -126,9 +126,9 @@ class Page
126
126
  def channel
127
127
  @channel ||= begin
128
128
  if Volt.client?
129
- ReactiveValue.new(Channel.new)
129
+ Channel.new
130
130
  else
131
- ReactiveValue.new(ChannelStub.new)
131
+ ChannelStub.new
132
132
  end
133
133
  end
134
134
  end
@@ -149,7 +149,7 @@ class Page
149
149
 
150
150
  def add_routes(&block)
151
151
  @routes = Routes.new.define(&block)
152
- @url.cur.router = @routes
152
+ @url.router = @routes
153
153
  end
154
154
 
155
155
  def start
@@ -166,16 +166,14 @@ class Page
166
166
  # Setup main page template
167
167
  TemplateRenderer.new(self, DomTarget.new, main_controller, 'CONTENT', 'main/main/main/body')
168
168
 
169
- # Setup title listener template
170
- title_target = AttributeTarget.new
171
- title_target.on('changed') do
172
- title = title_target.to_html
173
- `document.title = title;`
174
- end
175
- TemplateRenderer.new(self, title_target, main_controller, "main", "main/main/main/title")
169
+ # Setup title reactive template
170
+ @title_template = StringTemplateRender.new(self, main_controller, "main/main/main/title")
176
171
 
177
- # TODO: this dom ready should really happen in the template renderer
178
- main_controller.dom_ready if main_controller.respond_to?(:dom_ready)
172
+ # Watch for changes to the title template
173
+ Proc.new do
174
+ title = @title_template.html.gsub(/\n/, ' ')
175
+ `document.title = title;`
176
+ end.watch!
179
177
  end
180
178
 
181
179
  # When the page is reloaded from the backend, we store the $page.page, so we
@@ -0,0 +1,46 @@
1
+ # StringTemplateRender are used to render a template to a string. Call .html
2
+ # to get the string. Be sure to call .remove when complete.
3
+ #
4
+ # StringTemplateRender will intellegently update the string in the same way
5
+ # a normal bindings will update the dom.
6
+
7
+ class StringTemplateRender
8
+ def initialize(page, context, template_path)
9
+ @dependency = Dependency.new
10
+
11
+ @template_path = template_path
12
+ @target = AttributeTarget.new(nil, nil, self)
13
+ @template = TemplateRenderer.new(page, @target, context, "main", template_path)
14
+ end
15
+
16
+ # Render the template and get the current value
17
+ def html
18
+ @dependency.depend
19
+
20
+ html = nil
21
+ Computation.run_without_tracking do
22
+ html = @target.to_html
23
+ end
24
+
25
+ return html
26
+ end
27
+
28
+ def changed!
29
+ # if @dependency is missing, this template has been removed
30
+ @dependency.changed! if @dependency
31
+ end
32
+
33
+ def remove
34
+ @dependency.remove
35
+ @dependency = nil
36
+
37
+ Computation.run_without_tracking do
38
+ @template.remove
39
+ @template = nil
40
+ end
41
+
42
+ @target = nil
43
+ @template_path = nil
44
+ end
45
+
46
+ end
@@ -17,7 +17,13 @@ class SubContext
17
17
  def method_missing(method_name, *args, &block)
18
18
  method_name = method_name.to_s
19
19
  if @locals.has_key?(method_name)
20
- return @locals[method_name]
20
+ obj = @locals[method_name]
21
+
22
+ # TODORW: Might get a normal proc, flag internal procs
23
+ if obj.is_a?(Proc)
24
+ obj = obj.call(*args)
25
+ end
26
+ return obj
21
27
  elsif @context
22
28
  return @context.send(method_name, *args, &block)
23
29
  end
@@ -1,12 +1,12 @@
1
1
  require 'volt/page/targets/binding_document/html_node'
2
- require 'volt/reactive/events'
2
+ require 'volt/reactive/eventable'
3
3
 
4
4
  # Component nodes contain an array of both HtmlNodes and ComponentNodes.
5
5
  # Instead of providing a full DOM API, component nodes are the branch
6
6
  # nodes and html nodes are the leafs. This is all we need to produce
7
7
  # the html from templates outside of a normal dom.
8
8
  class ComponentNode < BaseNode
9
- include Events
9
+ include Eventable
10
10
 
11
11
  attr_accessor :parent, :binding_id, :nodes
12
12
  def initialize(binding_id=nil, parent=nil, root=nil)
@@ -16,12 +16,13 @@ class ComponentNode < BaseNode
16
16
  @root = root
17
17
  end
18
18
 
19
- def trigger!(*args, &block)
19
+ def changed!
20
20
  if @root
21
- @root.trigger!(*args, &block)
21
+ @root.changed!
22
22
  else
23
- super
23
+ trigger!('changed')
24
24
  end
25
+
25
26
  end
26
27
 
27
28
  def text=(text)
@@ -56,7 +57,7 @@ class ComponentNode < BaseNode
56
57
  end
57
58
  end
58
59
 
59
- trigger!('changed')
60
+ changed!
60
61
  end
61
62
 
62
63
  def <<(node)
@@ -78,7 +79,7 @@ class ComponentNode < BaseNode
78
79
  end
79
80
 
80
81
  @nodes.each do |node|
81
- if node.cur.is_a?(ComponentNode)
82
+ if node.is_a?(ComponentNode)
82
83
  val = node.find_by_binding_id(binding_id)
83
84
  return val if val
84
85
  end
@@ -90,7 +91,8 @@ class ComponentNode < BaseNode
90
91
  def remove
91
92
  @nodes = []
92
93
 
93
- trigger!('changed')
94
+ # puts "Component Node Removed"
95
+ changed!
94
96
 
95
97
  # @binding_id = nil
96
98
  end
@@ -100,7 +102,7 @@ class ComponentNode < BaseNode
100
102
 
101
103
  @parent.nodes.delete(self)
102
104
 
103
- trigger!('changed')
105
+ changed!
104
106
  @parent = nil
105
107
  @binding_id = nil
106
108
  end
@@ -6,7 +6,8 @@ class Tasks
6
6
  @callback_id = 0
7
7
  @callbacks = {}
8
8
 
9
- page.channel.on('message') do |_, *args|
9
+ # TODORW: ...
10
+ page.channel.on('message') do |*args|
10
11
  received_message(*args)
11
12
  end
12
13
  end
@@ -62,7 +63,7 @@ class Tasks
62
63
 
63
64
  def reload
64
65
  # Stash the current page value
65
- value = JSON.dump($page.page.cur.to_h.reject {|k,v| v.reactive? })
66
+ value = JSON.dump($page.page.to_h)
66
67
 
67
68
  # If this browser supports session storage, store the page, so it will
68
69
  # be in the same state when we reload.
@@ -6,9 +6,10 @@ class UrlTracker
6
6
  @page = page
7
7
 
8
8
  if Volt.client?
9
- page.params.on('child_changed') do
10
- @page.url.update!
11
- end
9
+ # TODORW:
10
+ # page.params.on('child_changed') do
11
+ # @page.url.update!
12
+ # end
12
13
 
13
14
  that = self
14
15