volt 0.8.8 → 0.8.9
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Readme.md +2 -1036
- data/VERSION +1 -1
- data/app/volt/models/user.rb +5 -0
- data/app/volt/tasks/query_tasks.rb +3 -3
- data/app/volt/tasks/store_tasks.rb +17 -15
- data/app/volt/tasks/user_tasks.rb +6 -0
- data/lib/volt/cli.rb +9 -0
- data/lib/volt/extra_core/string.rb +10 -2
- data/lib/volt/models/array_model.rb +0 -1
- data/lib/volt/models/model.rb +45 -28
- data/lib/volt/models/model_hash_behaviour.rb +16 -4
- data/lib/volt/models/model_helpers.rb +4 -2
- data/lib/volt/models/model_wrapper.rb +2 -1
- data/lib/volt/models/persistors/array_store.rb +6 -6
- data/lib/volt/models/persistors/local_store.rb +1 -1
- data/lib/volt/models/persistors/model_store.rb +3 -3
- data/lib/volt/models/persistors/query/query_listener.rb +6 -4
- data/lib/volt/models/persistors/query/query_listener_pool.rb +9 -0
- data/lib/volt/models/persistors/store.rb +1 -0
- data/lib/volt/models/url.rb +20 -10
- data/lib/volt/page/bindings/template_binding.rb +7 -1
- data/lib/volt/page/page.rb +3 -3
- data/lib/volt/page/tasks.rb +20 -19
- data/lib/volt/reactive/reactive_array.rb +44 -0
- data/lib/volt/router/routes.rb +5 -0
- data/lib/volt/server.rb +3 -1
- data/lib/volt/server/component_templates.rb +7 -1
- data/lib/volt/server/html_parser/attribute_scope.rb +1 -1
- data/lib/volt/server/rack/component_paths.rb +1 -10
- data/lib/volt/server/socket_connection_handler.rb +3 -0
- data/lib/volt/tasks/dispatcher.rb +3 -7
- data/lib/volt/tasks/task_handler.rb +41 -0
- data/spec/integration/bindings_spec.rb +1 -1
- data/spec/models/model_spec.rb +32 -32
- data/spec/models/persistors/params_spec.rb +1 -1
- data/spec/router/routes_spec.rb +4 -4
- data/templates/model/model.rb.tt +3 -0
- metadata +6 -2
@@ -15,20 +15,22 @@ class QueryListener
|
|
15
15
|
|
16
16
|
def add_listener
|
17
17
|
@listening = true
|
18
|
-
|
19
|
-
|
18
|
+
QueryTasks.add_listener(@collection, @query).then do |ret|
|
19
|
+
results, errors = ret
|
20
|
+
|
20
21
|
# When the initial data comes back, add it into the stores.
|
21
22
|
@stores.dup.each do |store|
|
22
23
|
# Clear if there are existing items
|
23
24
|
store.model.clear if store.model.size > 0
|
24
25
|
|
25
26
|
results.each do |index, data|
|
26
|
-
# puts "ADD: #{index} - #{data.inspect}"
|
27
27
|
store.add(index, data)
|
28
28
|
end
|
29
29
|
|
30
30
|
store.change_state_to(:loaded)
|
31
31
|
end
|
32
|
+
end.fail do |err|
|
33
|
+
puts "Err: #{err.inspect}"
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
@@ -60,7 +62,7 @@ class QueryListener
|
|
60
62
|
# Stop listening
|
61
63
|
if @listening
|
62
64
|
@listening = false
|
63
|
-
|
65
|
+
QueryTasks.remove_listener(@collection, @query)
|
64
66
|
end
|
65
67
|
end
|
66
68
|
end
|
@@ -6,4 +6,13 @@ require 'volt/models/persistors/query/query_listener'
|
|
6
6
|
# query in different places. This makes it so we only need to track a
|
7
7
|
# single query at once. Data updates will only be sent once as well.
|
8
8
|
class QueryListenerPool < GenericPool
|
9
|
+
def print
|
10
|
+
puts "--- Running Queries ---"
|
11
|
+
|
12
|
+
@pool.each_pair do |table,query_hash|
|
13
|
+
query_hash.keys.each do |query|
|
14
|
+
puts "#{table}: #{query.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
9
18
|
end
|
data/lib/volt/models/url.rb
CHANGED
@@ -69,22 +69,30 @@ class URL
|
|
69
69
|
|
70
70
|
path, params = @router.params_to_url(@params.to_h)
|
71
71
|
|
72
|
+
self.path = path if path
|
73
|
+
|
72
74
|
new_url = "#{scheme}://#{host_with_port}#{(path || self.path).chomp('/')}"
|
73
75
|
|
76
|
+
params_str = ''
|
74
77
|
unless params.empty?
|
75
|
-
new_url += '?'
|
76
78
|
query_parts = []
|
77
79
|
nested_params_hash(params).each_pair do |key,value|
|
78
80
|
# remove the _ from the front
|
79
81
|
value = `encodeURI(value)`
|
80
|
-
query_parts << "#{key}=#{value}"
|
82
|
+
query_parts << "#{key[1..-1]}=#{value}"
|
81
83
|
end
|
82
84
|
|
83
|
-
|
85
|
+
if query_parts.size > 0
|
86
|
+
self.query = query_parts.join('&')
|
87
|
+
new_url += '?' + self.query
|
88
|
+
end
|
84
89
|
end
|
85
90
|
|
86
91
|
frag = self.fragment
|
87
|
-
|
92
|
+
if frag.present?
|
93
|
+
self.fragment = frag
|
94
|
+
new_url += '#' + frag
|
95
|
+
end
|
88
96
|
|
89
97
|
return new_url
|
90
98
|
end
|
@@ -231,7 +239,7 @@ class URL
|
|
231
239
|
def query_key(path)
|
232
240
|
i = 0
|
233
241
|
path.map do |v|
|
234
|
-
v = v[1..-1]
|
242
|
+
# v = v[1..-1]
|
235
243
|
i += 1
|
236
244
|
if i != 1
|
237
245
|
"[#{v}]"
|
@@ -245,11 +253,13 @@ class URL
|
|
245
253
|
results = {}
|
246
254
|
|
247
255
|
params.each_pair do |key,value|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
256
|
+
unless value.nil?
|
257
|
+
if value.respond_to?(:persistor) && value.persistor && value.persistor.is_a?(Persistors::Params)
|
258
|
+
# TODO: Should be a param
|
259
|
+
results.merge!(nested_params_hash(value, path + [key]))
|
260
|
+
else
|
261
|
+
results[query_key(path + [key])] = value
|
262
|
+
end
|
253
263
|
end
|
254
264
|
end
|
255
265
|
|
@@ -14,7 +14,13 @@ class TemplateBinding < BaseBinding
|
|
14
14
|
@getter = getter
|
15
15
|
|
16
16
|
# Run the initial render
|
17
|
-
@computation = ->
|
17
|
+
@computation = -> do
|
18
|
+
# Don't try to render if this has been removed
|
19
|
+
if @context
|
20
|
+
# Render
|
21
|
+
update(*@context.instance_eval(&getter))
|
22
|
+
end
|
23
|
+
end.watch!
|
18
24
|
end
|
19
25
|
|
20
26
|
def setup_path(binding_in_path)
|
data/lib/volt/page/page.rb
CHANGED
@@ -4,6 +4,7 @@ if RUBY_PLATFORM == 'opal'
|
|
4
4
|
end
|
5
5
|
require 'volt/models'
|
6
6
|
require 'volt/controllers/model_controller'
|
7
|
+
require 'volt/tasks/task_handler'
|
7
8
|
require 'volt/page/bindings/attribute_binding'
|
8
9
|
require 'volt/page/bindings/content_binding'
|
9
10
|
require 'volt/page/bindings/each_binding'
|
@@ -29,7 +30,6 @@ require 'volt/benchmark/benchmark'
|
|
29
30
|
require 'volt/page/tasks'
|
30
31
|
|
31
32
|
|
32
|
-
|
33
33
|
class Page
|
34
34
|
attr_reader :url, :params, :page, :templates, :routes, :events, :model_classes
|
35
35
|
|
@@ -138,7 +138,7 @@ class Page
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def add_model(model_name)
|
141
|
-
model_name = model_name.camelize
|
141
|
+
model_name = model_name.camelize.to_sym
|
142
142
|
@model_classes[model_name] = Object.const_get(model_name)
|
143
143
|
end
|
144
144
|
|
@@ -189,7 +189,7 @@ class Page
|
|
189
189
|
`sessionStorage.removeItem('___page');`
|
190
190
|
|
191
191
|
JSON.parse(page_obj_str).each_pair do |key, value|
|
192
|
-
self.page.send(:"#{key}=", value)
|
192
|
+
self.page.send(:"_#{key}=", value)
|
193
193
|
end
|
194
194
|
`}`
|
195
195
|
end
|
data/lib/volt/page/tasks.rb
CHANGED
@@ -3,36 +3,36 @@
|
|
3
3
|
class Tasks
|
4
4
|
def initialize(page)
|
5
5
|
@page = page
|
6
|
-
@
|
7
|
-
@
|
6
|
+
@promise_id = 0
|
7
|
+
@promises = {}
|
8
8
|
|
9
9
|
page.channel.on('message') do |*args|
|
10
10
|
received_message(*args)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def call(class_name, method_name, *args
|
15
|
-
|
16
|
-
|
17
|
-
@callback_id += 1
|
14
|
+
def call(class_name, method_name, *args)
|
15
|
+
promise_id = @promise_id
|
16
|
+
@promise_id += 1
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
# Track the callback
|
19
|
+
promise = Promise.new
|
20
|
+
@promises[promise_id] = promise
|
21
|
+
|
22
|
+
# TODO: Timeout on these callbacks
|
23
|
+
|
24
|
+
@page.channel.send_message([promise_id, class_name, method_name, *args])
|
25
25
|
|
26
|
-
|
26
|
+
promise
|
27
27
|
end
|
28
28
|
|
29
29
|
|
30
|
-
def received_message(name,
|
30
|
+
def received_message(name, promise_id, *args)
|
31
31
|
case name
|
32
32
|
when 'added', 'removed', 'updated', 'changed'
|
33
33
|
notify_query(name, *args)
|
34
34
|
when 'response'
|
35
|
-
response(
|
35
|
+
response(promise_id, *args)
|
36
36
|
when 'reload'
|
37
37
|
reload
|
38
38
|
end
|
@@ -40,15 +40,16 @@ class Tasks
|
|
40
40
|
|
41
41
|
# When a request is sent to the backend, it can attach a callback,
|
42
42
|
# this is called from the backend to pass to the callback.
|
43
|
-
def response(
|
44
|
-
|
43
|
+
def response(promise_id, result, error)
|
44
|
+
promise = @promises.delete(promise_id)
|
45
45
|
|
46
|
-
if
|
46
|
+
if promise
|
47
47
|
if error
|
48
48
|
# TODO: full error handling
|
49
49
|
puts "Task Response: #{error.inspect}"
|
50
|
+
promise.reject(error)
|
50
51
|
else
|
51
|
-
|
52
|
+
promise.resolve(result)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
@@ -39,6 +39,50 @@ class ReactiveArray# < Array
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
def select
|
43
|
+
result = []
|
44
|
+
size.times do |index|
|
45
|
+
val = self[index]
|
46
|
+
if yield(val).true?
|
47
|
+
result << val
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
return result
|
52
|
+
end
|
53
|
+
|
54
|
+
def any?
|
55
|
+
if block_given?
|
56
|
+
size.times do |index|
|
57
|
+
val = self[index]
|
58
|
+
|
59
|
+
if yield(val).true?
|
60
|
+
return true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
return false
|
65
|
+
else
|
66
|
+
return @array.any?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def all?
|
71
|
+
if block_given?
|
72
|
+
size.times do |index|
|
73
|
+
val = self[index]
|
74
|
+
|
75
|
+
if !yield(val).true?
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
return true
|
81
|
+
else
|
82
|
+
return @array.all?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
42
86
|
# TODO: Handle a range
|
43
87
|
def [](index)
|
44
88
|
# Handle a negative index
|
data/lib/volt/router/routes.rb
CHANGED
@@ -74,6 +74,11 @@ class Routes
|
|
74
74
|
#
|
75
75
|
# returns the url and new params, or nil, nil if no match is found.
|
76
76
|
def params_to_url(test_params)
|
77
|
+
# Add in underscores
|
78
|
+
test_params = test_params.each_with_object({}) do |(k,v), obj|
|
79
|
+
obj[:"_#{k}"] = v
|
80
|
+
end
|
81
|
+
|
77
82
|
@param_matches.each do |param_matcher|
|
78
83
|
# TODO: Maybe a deep dup?
|
79
84
|
result, new_params = check_params_match(test_params.dup, param_matcher[0])
|
data/lib/volt/server.rb
CHANGED
@@ -18,6 +18,8 @@ require 'listen'
|
|
18
18
|
|
19
19
|
require 'volt'
|
20
20
|
require 'volt/boot'
|
21
|
+
require 'volt/tasks/dispatcher'
|
22
|
+
require 'volt/tasks/task_handler'
|
21
23
|
require 'volt/server/component_handler'
|
22
24
|
if RUBY_PLATFORM != 'java'
|
23
25
|
require 'volt/server/socket_connection_handler'
|
@@ -25,7 +27,7 @@ end
|
|
25
27
|
require 'volt/server/rack/component_paths'
|
26
28
|
require 'volt/server/rack/index_files'
|
27
29
|
require 'volt/server/rack/opal_files'
|
28
|
-
require 'volt/
|
30
|
+
require 'volt/page/page'
|
29
31
|
|
30
32
|
module Rack
|
31
33
|
# TODO: For some reason in Rack (or maybe thin), 304 headers close
|
@@ -13,7 +13,7 @@ class ComponentTemplates
|
|
13
13
|
code = generate_view_code
|
14
14
|
if @client
|
15
15
|
# On the backend, we just need the views
|
16
|
-
code << generate_controller_code + generate_model_code + generate_routes_code
|
16
|
+
code << generate_controller_code + generate_model_code + generate_routes_code + generate_tasks_code
|
17
17
|
end
|
18
18
|
|
19
19
|
return code
|
@@ -96,4 +96,10 @@ class ComponentTemplates
|
|
96
96
|
|
97
97
|
return code
|
98
98
|
end
|
99
|
+
|
100
|
+
def generate_tasks_code
|
101
|
+
return TaskHandler.known_handlers.map do |handler|
|
102
|
+
"class #{handler.name} < TaskHandler; end"
|
103
|
+
end.join "\n"
|
104
|
+
end
|
99
105
|
end
|
@@ -89,7 +89,7 @@ module AttributeScope
|
|
89
89
|
raise "The content of text area's can not be bound to multiple bindings."
|
90
90
|
else
|
91
91
|
# Multiple values can not be passed to value or checked attributes.
|
92
|
-
raise "Multiple bindings can not be passed to a #{attribute_name} binding."
|
92
|
+
raise "Multiple bindings can not be passed to a #{attribute_name} binding: #{parts.inspect}"
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -56,7 +56,7 @@ class ComponentPaths
|
|
56
56
|
$LOAD_PATH.unshift(app_folder)
|
57
57
|
|
58
58
|
# Sort so we get consistent load order across platforms
|
59
|
-
Dir["#{app_folder}/*/{controllers,models}/*.rb"].
|
59
|
+
Dir["#{app_folder}/*/{controllers,models,tasks}/*.rb"].each do |ruby_file|
|
60
60
|
path = ruby_file.gsub(/^#{app_folder}\//, '')[0..-4]
|
61
61
|
require(path)
|
62
62
|
end
|
@@ -70,15 +70,6 @@ class ComponentPaths
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
73
|
-
|
74
|
-
# add each tasks folder directly
|
75
|
-
components.sort.each do |name,component_folders|
|
76
|
-
component_folders.sort.each do |component_folder|
|
77
|
-
Dir["#{component_folder}/tasks"].sort.each do |tasks_folder|
|
78
|
-
$LOAD_PATH.unshift(tasks_folder)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
73
|
end
|
83
74
|
|
84
75
|
# Returns the path for a specific component
|
@@ -5,14 +5,10 @@ class Dispatcher
|
|
5
5
|
def dispatch(channel, message)
|
6
6
|
callback_id, class_name, method_name, *args = message
|
7
7
|
|
8
|
-
#
|
9
|
-
|
10
|
-
# TODO: Improve error on a class we don't have
|
11
|
-
require(class_name.underscore)
|
12
|
-
|
13
|
-
# Get the class
|
14
|
-
klass = Object.send(:const_get, class_name)
|
8
|
+
# Get the class
|
9
|
+
klass = Object.send(:const_get, class_name)
|
15
10
|
|
11
|
+
if klass.ancestors.include?(TaskHandler)
|
16
12
|
# Init and send the method
|
17
13
|
begin
|
18
14
|
result = klass.new(channel, self).send(method_name, *args)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class TaskHandler
|
2
|
+
if RUBY_PLATFORM == 'opal'
|
3
|
+
# On the front-end we setup a proxy class to the backend that returns
|
4
|
+
# promises for all calls.
|
5
|
+
def self.method_missing(name, *args, &block)
|
6
|
+
$page.tasks.call(self.name, name, *args, &block)
|
7
|
+
end
|
8
|
+
else
|
9
|
+
def initialize(channel=nil, dispatcher=nil)
|
10
|
+
@channel = channel
|
11
|
+
@dispatcher = dispatcher
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.inherited(subclass)
|
15
|
+
@subclasses ||= []
|
16
|
+
@subclasses << subclass
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.known_handlers
|
20
|
+
@subclasses ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
# On the backend, we proxy all class methods like we would
|
24
|
+
# on the front-end. This returns promises.
|
25
|
+
def self.method_missing(name, *args, &block)
|
26
|
+
promise = Promise.new
|
27
|
+
|
28
|
+
begin
|
29
|
+
result = self.new(nil, nil).send(name, *args, &block)
|
30
|
+
|
31
|
+
promise.resolve(result)
|
32
|
+
rescue => e
|
33
|
+
puts "Task Error: #{e.inspect}"
|
34
|
+
puts e.backtrace
|
35
|
+
promise.reject(e)
|
36
|
+
end
|
37
|
+
|
38
|
+
return promise
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|