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