volt 0.7.23 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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