volt 0.8.14 → 0.8.15
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/.gitignore +1 -1
- data/Readme.md +8 -2
- data/VERSION +1 -1
- data/app/volt/controllers/notices_controller.rb +1 -1
- data/app/volt/models/user.rb +2 -2
- data/app/volt/tasks/live_query/live_query_pool.rb +1 -1
- data/app/volt/tasks/query_tasks.rb +1 -1
- data/app/volt/tasks/store_tasks.rb +1 -1
- data/app/volt/tasks/user_tasks.rb +2 -2
- data/lib/volt/boot.rb +2 -2
- data/lib/volt/cli/asset_compile.rb +31 -27
- data/lib/volt/cli.rb +64 -65
- data/lib/volt/config.rb +25 -23
- data/lib/volt/console.rb +17 -16
- data/lib/volt/controllers/model_controller.rb +82 -80
- data/lib/volt/data_stores/data_store.rb +2 -2
- data/lib/volt/data_stores/mongo_driver.rb +2 -2
- data/lib/volt/extra_core/inflections.rb +2 -2
- data/lib/volt/extra_core/inflector/inflections.rb +185 -183
- data/lib/volt/extra_core/inflector/methods.rb +50 -48
- data/lib/volt/extra_core/string.rb +2 -2
- data/lib/volt/models/array_model.rb +93 -92
- data/lib/volt/models/cursor.rb +3 -2
- data/lib/volt/models/model.rb +248 -251
- data/lib/volt/models/model_hash_behaviour.rb +44 -44
- data/lib/volt/models/model_helpers.rb +38 -36
- data/lib/volt/models/model_state.rb +16 -17
- data/lib/volt/models/model_wrapper.rb +25 -24
- data/lib/volt/models/persistors/array_store.rb +145 -143
- data/lib/volt/models/persistors/base.rb +18 -16
- data/lib/volt/models/persistors/flash.rb +24 -22
- data/lib/volt/models/persistors/local_store.rb +46 -44
- data/lib/volt/models/persistors/model_identity_map.rb +10 -8
- data/lib/volt/models/persistors/model_store.rb +76 -76
- data/lib/volt/models/persistors/params.rb +19 -17
- data/lib/volt/models/persistors/query/query_listener.rb +65 -63
- data/lib/volt/models/persistors/query/query_listener_pool.rb +12 -10
- data/lib/volt/models/persistors/store.rb +28 -28
- data/lib/volt/models/persistors/store_factory.rb +12 -10
- data/lib/volt/models/persistors/store_state.rb +33 -31
- data/lib/volt/models/url.rb +96 -104
- data/lib/volt/models/validations.rb +56 -54
- data/lib/volt/models/validators/length_validator.rb +24 -22
- data/lib/volt/models/validators/presence_validator.rb +14 -12
- data/lib/volt/page/bindings/attribute_binding.rb +106 -106
- data/lib/volt/page/bindings/base_binding.rb +23 -21
- data/lib/volt/page/bindings/component_binding.rb +3 -1
- data/lib/volt/page/bindings/content_binding.rb +34 -34
- data/lib/volt/page/bindings/each_binding.rb +113 -113
- data/lib/volt/page/bindings/event_binding.rb +38 -34
- data/lib/volt/page/bindings/if_binding.rb +56 -54
- data/lib/volt/page/bindings/template_binding/grouped_controllers.rb +24 -22
- data/lib/volt/page/bindings/template_binding.rb +182 -185
- data/lib/volt/page/channel.rb +79 -77
- data/lib/volt/page/channel_stub.rb +29 -27
- data/lib/volt/page/document.rb +6 -5
- data/lib/volt/page/document_events.rb +54 -52
- data/lib/volt/page/page.rb +139 -138
- data/lib/volt/page/string_template_renderer.rb +36 -36
- data/lib/volt/page/sub_context.rb +26 -25
- data/lib/volt/page/targets/attribute_section.rb +27 -25
- data/lib/volt/page/targets/attribute_target.rb +7 -6
- data/lib/volt/page/targets/base_section.rb +27 -26
- data/lib/volt/page/targets/binding_document/base_node.rb +3 -1
- data/lib/volt/page/targets/binding_document/component_node.rb +85 -82
- data/lib/volt/page/targets/binding_document/html_node.rb +11 -9
- data/lib/volt/page/targets/dom_section.rb +78 -77
- data/lib/volt/page/targets/dom_target.rb +8 -6
- data/lib/volt/page/targets/dom_template.rb +90 -88
- data/lib/volt/page/targets/helpers/comment_searchers.rb +51 -49
- data/lib/volt/page/tasks.rb +59 -57
- data/lib/volt/page/template_renderer.rb +17 -14
- data/lib/volt/page/url_tracker.rb +26 -24
- data/lib/volt/reactive/computation.rb +87 -88
- data/lib/volt/reactive/dependency.rb +30 -28
- data/lib/volt/reactive/eventable.rb +64 -62
- data/lib/volt/reactive/hash_dependency.rb +25 -23
- data/lib/volt/reactive/reactive_accessors.rb +34 -32
- data/lib/volt/reactive/reactive_array.rb +162 -162
- data/lib/volt/reactive/reactive_hash.rb +37 -35
- data/lib/volt/router/routes.rb +99 -101
- data/lib/volt/server/component_handler.rb +20 -21
- data/lib/volt/server/component_templates.rb +72 -70
- data/lib/volt/server/html_parser/attribute_scope.rb +109 -99
- data/lib/volt/server/html_parser/each_scope.rb +17 -16
- data/lib/volt/server/html_parser/if_view_scope.rb +51 -49
- data/lib/volt/server/html_parser/sandlebars_parser.rb +184 -177
- data/lib/volt/server/html_parser/textarea_scope.rb +24 -22
- data/lib/volt/server/html_parser/view_handler.rb +66 -65
- data/lib/volt/server/html_parser/view_parser.rb +23 -21
- data/lib/volt/server/html_parser/view_scope.rb +142 -141
- data/lib/volt/server/rack/asset_files.rb +81 -79
- data/lib/volt/server/rack/component_code.rb +17 -15
- data/lib/volt/server/rack/component_html_renderer.rb +14 -12
- data/lib/volt/server/rack/component_paths.rb +72 -71
- data/lib/volt/server/rack/index_files.rb +36 -39
- data/lib/volt/server/rack/opal_files.rb +43 -41
- data/lib/volt/server/rack/source_map_server.rb +23 -21
- data/lib/volt/server/socket_connection_handler.rb +46 -45
- data/lib/volt/server/socket_connection_handler_stub.rb +21 -19
- data/lib/volt/server.rb +60 -58
- data/lib/volt/spec/setup.rb +3 -3
- data/lib/volt/tasks/dispatcher.rb +24 -23
- data/lib/volt/tasks/task_handler.rb +35 -33
- data/lib/volt/utils/ejson.rb +8 -6
- data/lib/volt/utils/generic_counting_pool.rb +33 -31
- data/lib/volt/utils/generic_pool.rb +73 -70
- data/lib/volt/utils/local_storage.rb +42 -38
- data/lib/volt/volt/environment.rb +1 -1
- data/lib/volt.rb +44 -42
- data/spec/apps/kitchen_sink/app/main/assets/css/todos.css +28 -0
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +2 -2
- data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +17 -0
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -0
- data/spec/apps/kitchen_sink/app/main/views/todos/index.html +24 -0
- data/spec/apps/kitchen_sink/config.ru +1 -1
- data/spec/controllers/reactive_accessors_spec.rb +5 -5
- data/spec/extra_core/inflector_spec.rb +2 -2
- data/spec/integration/list_spec.rb +68 -0
- data/spec/models/model_spec.rb +57 -57
- data/spec/models/persistors/params_spec.rb +6 -6
- data/spec/models/persistors/store_spec.rb +7 -7
- data/spec/models/validations_spec.rb +3 -3
- data/spec/page/bindings/content_binding_spec.rb +7 -7
- data/spec/page/bindings/template_binding_spec.rb +4 -5
- data/spec/page/sub_context_spec.rb +2 -2
- data/spec/reactive/computation_spec.rb +10 -10
- data/spec/reactive/dependency_spec.rb +2 -2
- data/spec/reactive/eventable_spec.rb +4 -4
- data/spec/reactive/reactive_array_spec.rb +13 -13
- data/spec/router/routes_spec.rb +5 -5
- data/spec/server/html_parser/sandlebars_parser_spec.rb +9 -9
- data/spec/server/html_parser/view_parser_spec.rb +27 -27
- data/spec/server/rack/asset_files_spec.rb +5 -5
- data/spec/server/rack/component_paths_spec.rb +2 -2
- data/spec/tasks/live_query_spec.rb +2 -2
- data/spec/tasks/query_tasks.rb +1 -1
- data/spec/tasks/query_tracker_spec.rb +1 -1
- data/spec/templates/targets/binding_document/component_node_spec.rb +2 -2
- data/spec/utils/generic_counting_pool_spec.rb +2 -2
- data/spec/utils/generic_pool_spec.rb +2 -2
- data/templates/component/controllers/main_controller.rb +1 -1
- data/templates/model/model.rb.tt +2 -2
- data/templates/newgem/app/newgem/controllers/main_controller.rb.tt +2 -2
- data/templates/project/app/main/controllers/main_controller.rb +1 -1
- data/templates/project/config.ru +1 -1
- metadata +10 -3
- data/app/volt/assets/js/vertxbus.js +0 -216
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
require 'volt/models/persistors/base'
|
|
2
2
|
require 'volt/models/persistors/model_identity_map'
|
|
3
3
|
|
|
4
|
-
module
|
|
5
|
-
|
|
4
|
+
module Volt
|
|
5
|
+
module Persistors
|
|
6
|
+
class Store < Base
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
@@identity_map = ModelIdentityMap.new
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
def initialize(model, tasks=nil)
|
|
11
|
+
@tasks = tasks
|
|
12
|
+
@model = model
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def saved?
|
|
17
|
-
@saved
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# On stores, we store the model so we don't have to look it up
|
|
21
|
-
# every time we do a read.
|
|
22
|
-
def read_new_model(method_name)
|
|
23
|
-
# On stores, plural associations are automatically assumed to be
|
|
24
|
-
# collections.
|
|
25
|
-
options = @model.options.merge(parent: @model, path: @model.path + [method_name])
|
|
26
|
-
if method_name.plural?
|
|
27
|
-
model = @model.new_array_model([], options)
|
|
28
|
-
else
|
|
29
|
-
model = @model.new_model(nil, options)
|
|
30
|
-
|
|
31
|
-
# TODO: Might not need to assign this
|
|
32
|
-
@model.attributes ||= {}
|
|
33
|
-
@model.attributes[method_name] = model
|
|
14
|
+
@saved = false
|
|
34
15
|
end
|
|
35
16
|
|
|
17
|
+
def saved?
|
|
18
|
+
@saved
|
|
19
|
+
end
|
|
36
20
|
|
|
37
|
-
|
|
21
|
+
# On stores, we store the model so we don't have to look it up
|
|
22
|
+
# every time we do a read.
|
|
23
|
+
def read_new_model(method_name)
|
|
24
|
+
# On stores, plural associations are automatically assumed to be
|
|
25
|
+
# collections.
|
|
26
|
+
options = @model.options.merge(parent: @model, path: @model.path + [method_name])
|
|
27
|
+
if method_name.plural?
|
|
28
|
+
model = @model.new_array_model([], options)
|
|
29
|
+
else
|
|
30
|
+
model = @model.new_model(nil, options)
|
|
31
|
+
|
|
32
|
+
# TODO: Might not need to assign this
|
|
33
|
+
@model.attributes ||= {}
|
|
34
|
+
@model.attributes[method_name] = model
|
|
35
|
+
end
|
|
36
|
+
model
|
|
37
|
+
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
module
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
module Volt
|
|
2
|
+
module Persistors
|
|
3
|
+
class StoreFactory
|
|
4
|
+
def initialize(tasks)
|
|
5
|
+
@tasks = tasks
|
|
6
|
+
end
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
def new(model)
|
|
9
|
+
if model.is_a?(ArrayModel)
|
|
10
|
+
ArrayStore.new(model, @tasks)
|
|
11
|
+
else
|
|
12
|
+
ModelStore.new(model, @tasks)
|
|
13
|
+
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
end
|
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
module
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def state
|
|
10
|
-
@state_dep ||= Dependency.new
|
|
11
|
-
@state_dep.depend
|
|
12
|
-
|
|
13
|
-
return @state
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Called from the QueryListener when the data is loaded
|
|
17
|
-
def change_state_to(new_state, skip_trigger=false)
|
|
18
|
-
old_state = @state
|
|
19
|
-
@state = new_state
|
|
20
|
-
|
|
21
|
-
# Trigger changed on the 'state' method
|
|
22
|
-
unless skip_trigger
|
|
23
|
-
if old_state != @state
|
|
24
|
-
@state_dep.changed! if @state_dep
|
|
1
|
+
module Volt
|
|
2
|
+
module Persistors
|
|
3
|
+
# StoreState provides method for a store to track its loading state.
|
|
4
|
+
module StoreState
|
|
5
|
+
|
|
6
|
+
# Called when a collection loads
|
|
7
|
+
def loaded(initial_state=nil)
|
|
8
|
+
change_state_to(initial_state || :not_loaded)
|
|
25
9
|
end
|
|
26
|
-
end
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
11
|
+
def state
|
|
12
|
+
@state_dep ||= Dependency.new
|
|
13
|
+
@state_dep.depend
|
|
14
|
+
@state
|
|
15
|
+
end
|
|
32
16
|
|
|
33
|
-
|
|
17
|
+
# Called from the QueryListener when the data is loaded
|
|
18
|
+
def change_state_to(new_state, skip_trigger=false)
|
|
19
|
+
old_state = @state
|
|
20
|
+
@state = new_state
|
|
21
|
+
|
|
22
|
+
# Trigger changed on the 'state' method
|
|
23
|
+
unless skip_trigger
|
|
24
|
+
if old_state != @state
|
|
25
|
+
@state_dep.changed! if @state_dep
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if @state == :loaded && @fetch_promises
|
|
30
|
+
# Trigger each waiting fetch
|
|
31
|
+
@fetch_promises.compact.each { |fp| fp.resolve(@model) }
|
|
32
|
+
@fetch_promises = nil
|
|
33
|
+
|
|
34
|
+
stop_listening
|
|
35
|
+
end
|
|
36
|
+
end
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
|
-
|
|
37
39
|
end
|
data/lib/volt/models/url.rb
CHANGED
|
@@ -1,124 +1,117 @@
|
|
|
1
1
|
# The url class handles parsing and updating the url
|
|
2
2
|
require 'volt/reactive/reactive_accessors'
|
|
3
|
+
module Volt
|
|
4
|
+
class URL
|
|
5
|
+
include ReactiveAccessors
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
# TODO: we need to make it so change events only trigger on changes
|
|
8
|
+
reactive_accessor :scheme, :host, :port, :path, :query, :params, :fragment
|
|
9
|
+
attr_accessor :router
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def initialize(router=nil)
|
|
12
|
-
@router = router
|
|
13
|
-
@params = Model.new({}, persistor: Persistors::Params)
|
|
14
|
-
end
|
|
11
|
+
def initialize(router = nil)
|
|
12
|
+
@router = router
|
|
13
|
+
@params = Model.new({}, persistor: Persistors::Params)
|
|
14
|
+
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
else
|
|
24
|
-
host = `document.location.host`
|
|
25
|
-
protocol = `document.location.protocol`
|
|
26
|
-
|
|
27
|
-
if url !~ /[:]\/\//
|
|
28
|
-
# Add the host for local urls
|
|
29
|
-
url = protocol + "//#{host}" + url
|
|
16
|
+
# Parse takes in a url and extracts each sections.
|
|
17
|
+
# It also assigns and changes to the params.
|
|
18
|
+
def parse(url)
|
|
19
|
+
if url[0] == '#'
|
|
20
|
+
# url only updates fragment
|
|
21
|
+
self.fragment = url[1..-1]
|
|
22
|
+
update!
|
|
30
23
|
else
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
host = `document.location.host`
|
|
25
|
+
protocol = `document.location.protocol`
|
|
26
|
+
|
|
27
|
+
if url !~ /[:]\/\//
|
|
28
|
+
# Add the host for local urls
|
|
29
|
+
url = protocol + "//#{host}" + url
|
|
30
|
+
else
|
|
31
|
+
# Make sure its on the same protocol and host, otherwise its external.
|
|
32
|
+
if url !~ /#{protocol}\/\/#{host}/
|
|
33
|
+
# Different host, don't process
|
|
34
|
+
return false
|
|
35
|
+
end
|
|
35
36
|
end
|
|
36
|
-
end
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
matcher = url.match(/^(#{protocol[0..-2]})[:]\/\/([^\/]+)(.*)$/)
|
|
39
|
+
self.scheme = matcher[1]
|
|
40
|
+
self.host, port = matcher[2].split(':')
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
self.port = (port || 80).to_i
|
|
42
|
+
self.port = (port || 80).to_i
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
path = matcher[3]
|
|
45
|
+
path, fragment = path.split('#', 2)
|
|
46
|
+
path, query = path.split('?', 2)
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
self.path = path
|
|
49
|
+
self.fragment = fragment
|
|
50
|
+
self.query = query
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
assign_query_hash_to_params
|
|
53
|
+
end
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
scroll
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
return true
|
|
58
|
+
end
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if port && port != 80
|
|
64
|
-
host_with_port = "#{host}:#{port}"
|
|
65
|
-
else
|
|
60
|
+
# Full url rebuilds the url from it's constituent parts
|
|
61
|
+
def full_url
|
|
66
62
|
host_with_port = host
|
|
67
|
-
|
|
63
|
+
host_with_port += ":#{port}" if port && port != 80
|
|
68
64
|
|
|
69
|
-
|
|
65
|
+
self.path, params = @router.params_to_url(@params.to_h)
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
new_url = "#{scheme}://#{host_with_port}#{path.chomp('/')}"
|
|
72
68
|
|
|
73
|
-
|
|
69
|
+
# Add query params
|
|
70
|
+
params_str = ''
|
|
71
|
+
unless params.empty?
|
|
72
|
+
query_parts = []
|
|
73
|
+
nested_params_hash(params).each_pair do |key, value|
|
|
74
|
+
# remove the _ from the front
|
|
75
|
+
value = `encodeURI(value)`
|
|
76
|
+
query_parts << "#{key[1..-1]}=#{value}"
|
|
77
|
+
end
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# remove the _ from the front
|
|
80
|
-
value = `encodeURI(value)`
|
|
81
|
-
query_parts << "#{key[1..-1]}=#{value}"
|
|
79
|
+
if query_parts.size > 0
|
|
80
|
+
self.query = query_parts.join('&')
|
|
81
|
+
new_url += '?' + self.query
|
|
82
|
+
end
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
end
|
|
88
|
-
end
|
|
85
|
+
# Add fragment
|
|
86
|
+
frag = self.fragment
|
|
87
|
+
new_url += '#' + frag if frag.present?
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
if frag.present?
|
|
92
|
-
self.fragment = frag
|
|
93
|
-
new_url += '#' + frag
|
|
89
|
+
return new_url
|
|
94
90
|
end
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def update!
|
|
103
|
-
if Volt.client?
|
|
104
|
-
new_url = full_url()
|
|
92
|
+
# Called when the state has changed and the url in the
|
|
93
|
+
# browser should be updated
|
|
94
|
+
# Called when an attribute changes to update the url
|
|
95
|
+
def update!
|
|
96
|
+
if Volt.client?
|
|
97
|
+
new_url = full_url()
|
|
105
98
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
# Push the new url if pushState is supported
|
|
100
|
+
# TODO: add fragment fallback
|
|
101
|
+
%x{
|
|
109
102
|
if (document.location.href != new_url && history && history.pushState) {
|
|
110
103
|
history.pushState(null, null, new_url);
|
|
111
104
|
}
|
|
112
105
|
}
|
|
106
|
+
end
|
|
113
107
|
end
|
|
114
|
-
end
|
|
115
108
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
def scroll
|
|
110
|
+
if Volt.client?
|
|
111
|
+
frag = self.fragment
|
|
112
|
+
if frag
|
|
113
|
+
# Scroll to anchor via http://www.w3.org/html/wg/drafts/html/master/browsers.html#scroll-to-fragid
|
|
114
|
+
%x{
|
|
122
115
|
var anchor = $('#' + frag);
|
|
123
116
|
if (anchor.length == 0) {
|
|
124
117
|
anchor = $('*[name="' + frag + '"]:first');
|
|
@@ -128,14 +121,14 @@ class URL
|
|
|
128
121
|
$(document.body).scrollTop(anchor.offset().top);
|
|
129
122
|
}
|
|
130
123
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
124
|
+
else
|
|
125
|
+
# Scroll to the top by default
|
|
126
|
+
`$(document.body).scrollTop(0);`
|
|
127
|
+
end
|
|
134
128
|
end
|
|
135
129
|
end
|
|
136
|
-
end
|
|
137
130
|
|
|
138
|
-
|
|
131
|
+
private
|
|
139
132
|
# Assigning the params is tricky since we don't want to trigger changed on
|
|
140
133
|
# any values that have not changed. So we first loop through all current
|
|
141
134
|
# url params, removing any not present in the params, while also removing
|
|
@@ -165,7 +158,7 @@ class URL
|
|
|
165
158
|
def assign_from_old(params, new_params)
|
|
166
159
|
queued_deletes = []
|
|
167
160
|
|
|
168
|
-
params.attributes.each_pair do |name,old_val|
|
|
161
|
+
params.attributes.each_pair do |name, old_val|
|
|
169
162
|
# If there is a new value, see if it has [name]
|
|
170
163
|
new_val = new_params ? new_params[name] : nil
|
|
171
164
|
|
|
@@ -183,7 +176,7 @@ class URL
|
|
|
183
176
|
end
|
|
184
177
|
end
|
|
185
178
|
|
|
186
|
-
queued_deletes.each {|name| params.delete(name) }
|
|
179
|
+
queued_deletes.each { |name| params.delete(name) }
|
|
187
180
|
end
|
|
188
181
|
|
|
189
182
|
# Assign any new params, which weren't in the old params.
|
|
@@ -200,10 +193,10 @@ class URL
|
|
|
200
193
|
|
|
201
194
|
def query_hash
|
|
202
195
|
query_hash = {}
|
|
203
|
-
qury
|
|
196
|
+
qury = self.query
|
|
204
197
|
if qury
|
|
205
|
-
qury.split('&').reject {|v| v == '' }.each do |part|
|
|
206
|
-
parts
|
|
198
|
+
qury.split('&').reject { |v| v == '' }.each do |part|
|
|
199
|
+
parts = part.split('=').reject { |v| v == '' }
|
|
207
200
|
|
|
208
201
|
# Decode string
|
|
209
202
|
# parts[0] = `decodeURI(parts[0])`
|
|
@@ -212,7 +205,7 @@ class URL
|
|
|
212
205
|
sections = query_key_sections(parts[0])
|
|
213
206
|
|
|
214
207
|
hash_part = query_hash
|
|
215
|
-
sections.each_with_index do |section,index|
|
|
208
|
+
sections.each_with_index do |section, index|
|
|
216
209
|
if index == sections.size-1
|
|
217
210
|
# Last part, assign the value
|
|
218
211
|
hash_part[section] = parts[1]
|
|
@@ -231,14 +224,13 @@ class URL
|
|
|
231
224
|
# Example:
|
|
232
225
|
# user[name]=Ryan would parse as [:_user, :_name]
|
|
233
226
|
def query_key_sections(key)
|
|
234
|
-
key.split(/\[([^\]]+)\]/).reject(&:empty?).map {|v| :"_#{v}"}
|
|
227
|
+
key.split(/\[([^\]]+)\]/).reject(&:empty?).map { |v| :"_#{v}" }
|
|
235
228
|
end
|
|
236
229
|
|
|
237
230
|
# Generate the key for a nested param attribute
|
|
238
231
|
def query_key(path)
|
|
239
232
|
i = 0
|
|
240
233
|
path.map do |v|
|
|
241
|
-
# v = v[1..-1]
|
|
242
234
|
i += 1
|
|
243
235
|
if i != 1
|
|
244
236
|
"[#{v}]"
|
|
@@ -251,7 +243,7 @@ class URL
|
|
|
251
243
|
def nested_params_hash(params, path=[])
|
|
252
244
|
results = {}
|
|
253
245
|
|
|
254
|
-
params.each_pair do |key,value|
|
|
246
|
+
params.each_pair do |key, value|
|
|
255
247
|
unless value.nil?
|
|
256
248
|
if value.respond_to?(:persistor) && value.persistor && value.persistor.is_a?(Persistors::Params)
|
|
257
249
|
# TODO: Should be a param
|
|
@@ -264,5 +256,5 @@ class URL
|
|
|
264
256
|
|
|
265
257
|
return results
|
|
266
258
|
end
|
|
267
|
-
|
|
259
|
+
end
|
|
268
260
|
end
|
|
@@ -2,77 +2,78 @@
|
|
|
2
2
|
require 'volt/models/validators/length_validator'
|
|
3
3
|
require 'volt/models/validators/presence_validator'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
module
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
module Volt
|
|
6
|
+
# Include in any class to get validation logic
|
|
7
|
+
module Validations
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def validate(field_name, options)
|
|
10
|
+
@validations ||= {}
|
|
11
|
+
@validations[field_name] = options
|
|
12
|
+
end
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
def validations
|
|
15
|
+
@validations
|
|
16
|
+
end
|
|
15
17
|
end
|
|
16
|
-
end
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
def self.included(base)
|
|
20
|
+
base.send :extend, ClassMethods
|
|
21
|
+
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
# Once a field is ready, we can use include_in_errors! to start
|
|
24
|
+
# showing its errors.
|
|
25
|
+
def mark_field!(field_name, trigger_changed=true)
|
|
26
|
+
marked_fields[field_name] = true
|
|
27
|
+
end
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
def marked_fields
|
|
30
|
+
@marked_fields ||= ReactiveHash.new
|
|
31
|
+
end
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
def marked_errors
|
|
34
|
+
errors(true)
|
|
35
|
+
end
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
# TODO: Errors is being called for any validation change. We should have errors return a
|
|
38
|
+
# hash like object that only calls the validation for each one.
|
|
39
|
+
def errors(marked_only=false)
|
|
40
|
+
errors = {}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
validations = self.class.validations
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
if validations
|
|
45
|
+
# Merge into errors, combining any error arrays
|
|
46
|
+
merge = Proc.new do |new_errors|
|
|
47
|
+
errors.merge!(new_errors) do |key, new_val, old_val|
|
|
48
|
+
new_val + old_val
|
|
49
|
+
end
|
|
48
50
|
end
|
|
49
|
-
end
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
# Run through each validation
|
|
53
|
+
validations.each_pair do |field_name, options|
|
|
54
|
+
if marked_only
|
|
55
|
+
# When marked only, skip any validations on non-marked fields
|
|
56
|
+
next unless marked_fields[field_name]
|
|
57
|
+
end
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
options.each_pair do |validation, args|
|
|
60
|
+
# Call the specific validator, then merge the results back
|
|
61
|
+
# into one large errors hash.
|
|
62
|
+
klass = validation_class(validation, args)
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
if klass
|
|
65
|
+
validate_with(merge, klass, field_name, args)
|
|
66
|
+
else
|
|
67
|
+
raise "validation type #{validation} is not specified."
|
|
68
|
+
end
|
|
67
69
|
end
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
|
-
end
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
return errors
|
|
74
|
+
end
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
private
|
|
76
77
|
# calls the validate method on the class, passing the right arguments.
|
|
77
78
|
def validate_with(merge, klass, field_name, args)
|
|
78
79
|
return merge.call(klass.validate(self, field_name, args))
|
|
@@ -80,9 +81,10 @@ module Validations
|
|
|
80
81
|
|
|
81
82
|
def validation_class(validation, args)
|
|
82
83
|
begin
|
|
83
|
-
|
|
84
|
+
Volt.const_get(:"#{validation.camelize}Validator")
|
|
84
85
|
rescue NameError => e
|
|
85
86
|
puts "Unable to find #{validation} validator"
|
|
86
87
|
end
|
|
87
88
|
end
|
|
89
|
+
end
|
|
88
90
|
end
|
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
module Volt
|
|
2
|
+
class LengthValidator
|
|
3
|
+
def self.validate(model, field_name, args)
|
|
4
|
+
errors = {}
|
|
5
|
+
value = model.send(field_name)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
if args.is_a?(Fixnum)
|
|
8
|
+
min = args
|
|
9
|
+
max = nil
|
|
10
|
+
message = nil
|
|
11
|
+
elsif args.is_a?(Hash)
|
|
12
|
+
min = args[:length] || args[:minimum]
|
|
13
|
+
max = args[:maximum]
|
|
14
|
+
raise "length or minimum must be specified" unless min.is_a?(Fixnum)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
message = args[:message]
|
|
17
|
+
else
|
|
18
|
+
raise "The arguments to length must be a number or a hash"
|
|
19
|
+
end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
if !value || value.size < min
|
|
22
|
+
errors[field_name] = [message || "must be at least #{args} characters"]
|
|
23
|
+
elsif max && value.size > max
|
|
24
|
+
errors[field_name] = [message || "must be less than #{args} characters"]
|
|
25
|
+
end
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
return errors
|
|
28
|
+
end
|
|
27
29
|
end
|
|
28
30
|
end
|