volt 0.7.23 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile +8 -0
- data/Guardfile +2 -2
- data/Readme.md +139 -136
- data/VERSION +1 -1
- data/app/volt/assets/js/setImmediate.js +175 -0
- data/app/volt/tasks/live_query/data_store.rb +0 -2
- data/app/volt/tasks/live_query/live_query.rb +4 -4
- data/docs/GETTING_STARTED.md +24 -3
- data/docs/WHY.md +1 -22
- data/lib/volt.rb +20 -1
- data/lib/volt/console.rb +20 -0
- data/lib/volt/controllers/model_controller.rb +25 -11
- data/lib/volt/extra_core/object.rb +2 -14
- data/lib/volt/extra_core/string.rb +4 -0
- data/lib/volt/models.rb +0 -1
- data/lib/volt/models/array_model.rb +8 -16
- data/lib/volt/models/cursor.rb +1 -1
- data/lib/volt/models/model.rb +40 -60
- data/lib/volt/models/model_hash_behaviour.rb +10 -24
- data/lib/volt/models/model_helpers.rb +2 -2
- data/lib/volt/models/model_state.rb +1 -1
- data/lib/volt/models/model_wrapper.rb +4 -4
- data/lib/volt/models/persistors/array_store.rb +44 -28
- data/lib/volt/models/persistors/base.rb +1 -1
- data/lib/volt/models/persistors/model_store.rb +1 -1
- data/lib/volt/models/persistors/params.rb +5 -1
- data/lib/volt/models/persistors/query/query_listener.rb +2 -0
- data/lib/volt/models/persistors/store.rb +3 -2
- data/lib/volt/models/persistors/store_state.rb +7 -2
- data/lib/volt/models/url.rb +35 -29
- data/lib/volt/models/validations.rb +7 -17
- data/lib/volt/page/bindings/attribute_binding.rb +57 -39
- data/lib/volt/page/bindings/base_binding.rb +0 -14
- data/lib/volt/page/bindings/content_binding.rb +15 -18
- data/lib/volt/page/bindings/each_binding.rb +67 -34
- data/lib/volt/page/bindings/if_binding.rb +15 -12
- data/lib/volt/page/bindings/template_binding.rb +77 -59
- data/lib/volt/page/bindings/template_binding/grouped_controllers.rb +19 -4
- data/lib/volt/page/channel.rb +22 -38
- data/lib/volt/page/channel_stub.rb +3 -6
- data/lib/volt/page/page.rb +24 -26
- data/lib/volt/page/string_template_renderer.rb +46 -0
- data/lib/volt/page/sub_context.rb +7 -1
- data/lib/volt/page/targets/binding_document/component_node.rb +11 -9
- data/lib/volt/page/tasks.rb +3 -2
- data/lib/volt/page/url_tracker.rb +4 -3
- data/lib/volt/reactive/computation.rb +131 -0
- data/lib/volt/reactive/dependency.rb +71 -0
- data/lib/volt/reactive/eventable.rb +82 -0
- data/lib/volt/reactive/hash_dependency.rb +36 -0
- data/lib/volt/{controllers → reactive}/reactive_accessors.rb +8 -11
- data/lib/volt/reactive/reactive_array.rb +100 -193
- data/lib/volt/reactive/reactive_hash.rb +49 -0
- data/lib/volt/server/html_parser/attribute_scope.rb +24 -4
- data/lib/volt/server/html_parser/if_view_scope.rb +15 -15
- data/lib/volt/server/html_parser/view_scope.rb +31 -1
- data/spec/apps/kitchen_sink/Gemfile +4 -8
- data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +8 -0
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +8 -1
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +8 -0
- data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +73 -0
- data/spec/apps/kitchen_sink/app/main/views/main/index.html +6 -1
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +26 -6
- data/spec/apps/kitchen_sink/app/main/views/main/store.html +6 -0
- data/spec/controllers/reactive_accessors_spec.rb +13 -15
- data/spec/integration/bindings_spec.rb +159 -0
- data/spec/integration/templates_spec.rb +15 -0
- data/spec/models/model_spec.rb +130 -228
- data/spec/reactive/computation_spec.rb +63 -0
- data/spec/reactive/dependency_spec.rb +5 -0
- data/spec/reactive/eventable_spec.rb +48 -0
- data/spec/reactive/reactive_array_spec.rb +97 -0
- data/spec/router/routes_spec.rb +26 -27
- data/spec/server/html_parser/view_parser_spec.rb +3 -21
- data/spec/server/rack/asset_files_spec.rb +1 -1
- data/templates/project/app/main/views/main/main.html +2 -2
- metadata +29 -41
- data/lib/volt/extra_core/time.rb +0 -16
- data/lib/volt/page/draw_cycle.rb +0 -31
- data/lib/volt/page/memory_test.rb +0 -26
- data/lib/volt/page/reactive_template.rb +0 -32
- data/lib/volt/reactive/array_extensions.rb +0 -12
- data/lib/volt/reactive/destructive_methods.rb +0 -19
- data/lib/volt/reactive/event_chain.rb +0 -125
- data/lib/volt/reactive/events.rb +0 -216
- data/lib/volt/reactive/object_tracking.rb +0 -14
- data/lib/volt/reactive/reactive_block.rb +0 -88
- data/lib/volt/reactive/reactive_generator.rb +0 -44
- data/lib/volt/reactive/reactive_tags.rb +0 -71
- data/lib/volt/reactive/reactive_value.rb +0 -427
- data/lib/volt/reactive/string_extensions.rb +0 -31
- data/spec/integration/test_integration_spec.rb +0 -14
- data/spec/models/event_chain_spec.rb +0 -150
- data/spec/models/model_buffers_spec.rb +0 -9
- data/spec/models/old_model_spec.rb +0 -67
- data/spec/models/reactive_array_spec.rb +0 -364
- data/spec/models/reactive_block_spec.rb +0 -13
- data/spec/models/reactive_call_times_spec.rb +0 -28
- data/spec/models/reactive_generator_spec.rb +0 -58
- data/spec/models/reactive_tags_spec.rb +0 -35
- data/spec/models/reactive_value_spec.rb +0 -370
- data/spec/models/store_spec.rb +0 -16
- 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
|
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
|
-
|
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
|
-
|
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
|
data/lib/volt/page/channel.rb
CHANGED
@@ -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
|
8
|
+
include ReactiveAccessors
|
9
|
+
include Eventable
|
8
10
|
|
9
|
-
|
11
|
+
reactive_accessor :connected, :status, :error, :reconnect_interval, :retry_count
|
10
12
|
|
11
13
|
def initialize
|
12
14
|
@socket = nil
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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 =
|
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
|
-
|
100
|
-
destructive!
|
83
|
+
trigger!('message', *message)
|
101
84
|
end
|
85
|
+
|
102
86
|
def send_message(message)
|
103
|
-
if
|
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
|
-
|
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
|
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',
|
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
|
data/lib/volt/page/page.rb
CHANGED
@@ -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/
|
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, :
|
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 =
|
40
|
+
@page = Model.new
|
42
41
|
|
43
|
-
@url =
|
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
|
-
|
69
|
-
|
66
|
+
if Volt.client?
|
67
|
+
channel.on('reconnected') do
|
68
|
+
@page._reconnected = true
|
70
69
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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 ||=
|
78
|
+
@flash ||= Model.new({}, persistor: Persistors::Flash)
|
79
79
|
end
|
80
80
|
|
81
81
|
def store
|
82
|
-
@store ||=
|
82
|
+
@store ||= Model.new({}, persistor: Persistors::StoreFactory.new(tasks))
|
83
83
|
end
|
84
84
|
|
85
85
|
def local_store
|
86
|
-
@local_store ||=
|
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
|
-
|
129
|
+
Channel.new
|
130
130
|
else
|
131
|
-
|
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.
|
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
|
170
|
-
|
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
|
-
#
|
178
|
-
|
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
|
-
|
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/
|
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
|
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
|
19
|
+
def changed!
|
20
20
|
if @root
|
21
|
-
@root.
|
21
|
+
@root.changed!
|
22
22
|
else
|
23
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
105
|
+
changed!
|
104
106
|
@parent = nil
|
105
107
|
@binding_id = nil
|
106
108
|
end
|
data/lib/volt/page/tasks.rb
CHANGED
@@ -6,7 +6,8 @@ class Tasks
|
|
6
6
|
@callback_id = 0
|
7
7
|
@callbacks = {}
|
8
8
|
|
9
|
-
|
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.
|
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.
|