volt 0.7.1 → 0.7.2
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/Gemfile +1 -2
- data/Readme.md +97 -56
- data/VERSION +1 -1
- data/app/volt/assets/js/sockjs-0.3.4.min.js +27 -0
- data/app/volt/assets/js/vertxbus.js +216 -0
- data/app/volt/tasks/live_query/live_query.rb +5 -5
- data/app/volt/tasks/live_query/live_query_pool.rb +1 -1
- data/app/volt/tasks/query_tasks.rb +5 -0
- data/app/volt/tasks/store_tasks.rb +44 -18
- data/docs/WHY.md +10 -0
- data/lib/volt/cli.rb +18 -7
- data/lib/volt/controllers/model_controller.rb +30 -8
- data/lib/volt/extra_core/inflections.rb +63 -0
- data/lib/volt/extra_core/inflector/inflections.rb +203 -0
- data/lib/volt/extra_core/inflector/methods.rb +63 -0
- data/lib/volt/extra_core/inflector.rb +4 -0
- data/lib/volt/extra_core/object.rb +9 -0
- data/lib/volt/extra_core/string.rb +10 -14
- data/lib/volt/models/array_model.rb +45 -27
- data/lib/volt/models/cursor.rb +6 -0
- data/lib/volt/models/model.rb +127 -12
- data/lib/volt/models/model_hash_behaviour.rb +8 -5
- data/lib/volt/models/model_helpers.rb +4 -4
- data/lib/volt/models/model_state.rb +22 -0
- data/lib/volt/models/persistors/array_store.rb +49 -35
- data/lib/volt/models/persistors/base.rb +3 -3
- data/lib/volt/models/persistors/model_store.rb +17 -6
- data/lib/volt/models/persistors/query/query_listener.rb +0 -2
- data/lib/volt/models/persistors/store.rb +0 -4
- data/lib/volt/models/persistors/store_state.rb +27 -0
- data/lib/volt/models/url.rb +2 -2
- data/lib/volt/models/validations/errors.rb +0 -0
- data/lib/volt/models/validations/length.rb +13 -0
- data/lib/volt/models/validations/validations.rb +82 -0
- data/lib/volt/models.rb +1 -1
- data/lib/volt/page/bindings/attribute_binding.rb +29 -14
- data/lib/volt/page/bindings/base_binding.rb +2 -2
- data/lib/volt/page/bindings/component_binding.rb +29 -25
- data/lib/volt/page/bindings/content_binding.rb +1 -0
- data/lib/volt/page/bindings/each_binding.rb +25 -33
- data/lib/volt/page/bindings/event_binding.rb +0 -1
- data/lib/volt/page/bindings/if_binding.rb +3 -1
- data/lib/volt/page/bindings/template_binding.rb +61 -28
- data/lib/volt/page/document_events.rb +3 -1
- data/lib/volt/page/draw_cycle.rb +22 -0
- data/lib/volt/page/page.rb +10 -1
- data/lib/volt/page/reactive_template.rb +23 -16
- data/lib/volt/page/sub_context.rb +1 -1
- data/lib/volt/page/targets/attribute_section.rb +3 -2
- data/lib/volt/page/targets/attribute_target.rb +0 -4
- data/lib/volt/page/targets/base_section.rb +25 -0
- data/lib/volt/page/targets/binding_document/component_node.rb +13 -14
- data/lib/volt/page/targets/binding_document/html_node.rb +4 -0
- data/lib/volt/page/targets/dom_section.rb +16 -67
- data/lib/volt/page/targets/dom_template.rb +99 -0
- data/lib/volt/page/targets/helpers/comment_searchers.rb +29 -0
- data/lib/volt/page/template_renderer.rb +2 -14
- data/lib/volt/reactive/array_extensions.rb +0 -1
- data/lib/volt/reactive/event_chain.rb +9 -2
- data/lib/volt/reactive/events.rb +44 -37
- data/lib/volt/reactive/object_tracking.rb +1 -1
- data/lib/volt/reactive/reactive_array.rb +18 -0
- data/lib/volt/reactive/reactive_count.rb +108 -0
- data/lib/volt/reactive/reactive_generator.rb +44 -0
- data/lib/volt/reactive/reactive_value.rb +73 -73
- data/lib/volt/reactive/string_extensions.rb +1 -1
- data/lib/volt/router/routes.rb +205 -88
- data/lib/volt/server/component_handler.rb +3 -1
- data/lib/volt/server/html_parser/view_parser.rb +20 -4
- data/lib/volt/server/rack/component_paths.rb +13 -10
- data/lib/volt/server/rack/index_files.rb +4 -4
- data/lib/volt/server/socket_connection_handler.rb +5 -1
- data/lib/volt/server.rb +10 -3
- data/spec/apps/kitchen_sink/.gitignore +8 -0
- data/spec/apps/kitchen_sink/Gemfile +32 -0
- data/spec/apps/kitchen_sink/app/home/views/index/index.html +3 -5
- data/spec/apps/kitchen_sink/config.ru +4 -0
- data/spec/apps/kitchen_sink/public/index.html +2 -2
- data/spec/extra_core/inflector_spec.rb +8 -0
- data/spec/models/event_chain_spec.rb +18 -0
- data/spec/models/model_buffers_spec.rb +9 -0
- data/spec/models/model_spec.rb +22 -9
- data/spec/models/reactive_array_spec.rb +26 -1
- data/spec/models/reactive_call_times_spec.rb +28 -0
- data/spec/models/reactive_value_spec.rb +19 -0
- data/spec/models/validations_spec.rb +39 -0
- data/spec/page/bindings/content_binding_spec.rb +1 -0
- data/spec/{templates → page/bindings}/template_binding_spec.rb +54 -0
- data/spec/router/routes_spec.rb +156 -8
- data/spec/server/html_parser/sandlebars_parser_spec.rb +55 -47
- data/spec/server/html_parser/view_parser_spec.rb +3 -0
- data/spec/server/rack/asset_files_spec.rb +1 -1
- data/spec/spec_helper.rb +25 -11
- data/spec/templates/targets/binding_document/component_node_spec.rb +12 -0
- data/templates/project/Gemfile.tt +11 -0
- data/templates/project/app/home/config/routes.rb +1 -1
- data/templates/project/app/home/controllers/index_controller.rb +5 -5
- data/templates/project/app/home/views/index/index.html +6 -6
- data/volt.gemspec +5 -6
- metadata +34 -76
- data/app/volt/assets/js/sockjs-0.2.1.min.js +0 -27
- data/lib/volt/reactive/object_tracker.rb +0 -107
|
@@ -27,12 +27,15 @@ module ModelHashBehaviour
|
|
|
27
27
|
attributes.true?
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def delete(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
def delete(name)
|
|
31
|
+
name = name.to_sym
|
|
32
|
+
__clear_element(name)
|
|
33
|
+
value = attributes.delete(name)
|
|
34
|
+
trigger_by_attribute!('changed', name)
|
|
34
35
|
|
|
35
|
-
@persistor.removed(
|
|
36
|
+
@persistor.removed(name) if @persistor
|
|
37
|
+
|
|
38
|
+
return value
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
|
|
@@ -12,13 +12,13 @@ module ModelHelpers
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# Pass to the persisotr
|
|
15
|
-
def event_added(event, scope_provider, first)
|
|
16
|
-
@persistor.event_added(event, scope_provider, first) if @persistor
|
|
15
|
+
def event_added(event, scope_provider, first, first_for_event)
|
|
16
|
+
@persistor.event_added(event, scope_provider, first, first_for_event) if @persistor
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Pass to the persistor
|
|
20
|
-
def event_removed(event,
|
|
21
|
-
@persistor.event_removed(event,
|
|
20
|
+
def event_removed(event, last, last_for_event)
|
|
21
|
+
@persistor.event_removed(event, last, last_for_event) if @persistor
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Gets the class for a model at the specified path.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# All models have a state that has to do with it being loaded, in process of
|
|
2
|
+
# being loaded, or not yet loading.
|
|
3
|
+
module ModelState
|
|
4
|
+
|
|
5
|
+
def state
|
|
6
|
+
if @persistor && @persistor.respond_to?(:state)
|
|
7
|
+
@persistor.state
|
|
8
|
+
else
|
|
9
|
+
@state || :loaded
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def change_state_to(state)
|
|
14
|
+
@state = state
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def loaded?
|
|
18
|
+
state == :loaded
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
end
|
|
@@ -1,73 +1,70 @@
|
|
|
1
1
|
require 'volt/models/persistors/store'
|
|
2
2
|
require 'volt/models/persistors/query/query_listener_pool'
|
|
3
|
+
require 'volt/models/persistors/store_state'
|
|
3
4
|
|
|
4
5
|
module Persistors
|
|
5
6
|
class ArrayStore < Store
|
|
7
|
+
include StoreState
|
|
8
|
+
|
|
6
9
|
@@query_pool = QueryListenerPool.new
|
|
7
10
|
|
|
8
11
|
attr_reader :model
|
|
9
|
-
attr_accessor :state
|
|
10
12
|
|
|
11
13
|
def self.query_pool
|
|
12
14
|
@@query_pool
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def initialize(model, tasks=nil)
|
|
18
|
+
# puts "NEW ARRAY STORE FOR #{model.inspect}"
|
|
16
19
|
super
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
query = @model.options[:query]
|
|
19
22
|
|
|
23
|
+
@query = ReactiveValue.from_hash(query || {})
|
|
20
24
|
end
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
def loaded
|
|
24
|
-
change_state_to :not_loaded
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def event_added(event, scope_provider, first)
|
|
28
|
-
puts "ADD EV: #{event} - #{first}"
|
|
26
|
+
def event_added(event, scope_provider, first, first_for_event)
|
|
29
27
|
# First event, we load the data.
|
|
30
28
|
load_data if first
|
|
31
29
|
end
|
|
32
30
|
|
|
33
|
-
def event_removed(event,
|
|
31
|
+
def event_removed(event, last, last_for_event)
|
|
34
32
|
# Remove listener where there are no more events on this model
|
|
35
|
-
if
|
|
36
|
-
stop_listening
|
|
37
|
-
end
|
|
33
|
+
stop_listening if last
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
# Called when an event is removed and we no longer want to keep in
|
|
41
37
|
# sync with the database.
|
|
42
38
|
def stop_listening
|
|
43
|
-
@
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
39
|
+
if @query_changed_listener
|
|
40
|
+
@query_changed_listener.remove
|
|
41
|
+
@query_changed_listener = nil
|
|
42
|
+
end
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
if @query_listener
|
|
45
|
+
@query_listener.remove_store(self)
|
|
46
|
+
@query_listener = nil
|
|
47
|
+
end
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
@model.trigger_for_methods!('changed', :state, :loaded?)
|
|
49
|
+
@state = :dirty
|
|
55
50
|
end
|
|
56
51
|
|
|
57
52
|
# Called the first time data is requested from this collection
|
|
58
53
|
def load_data
|
|
59
54
|
# Don't load data from any queried
|
|
60
55
|
if @state == :not_loaded || @state == :dirty
|
|
61
|
-
puts "Load Data"
|
|
56
|
+
puts "Load Data at #{@model.path.inspect} - query: #{@query.inspect}"# on #{@model.inspect}"
|
|
62
57
|
change_state_to :loading
|
|
63
58
|
|
|
64
59
|
@query_changed_listener.remove if @query_changed_listener
|
|
65
60
|
if @query.reactive?
|
|
61
|
+
# puts "SETUP REACTIVE QUERY LISTENER: #{@query.inspect}"
|
|
66
62
|
# Query might change, change the query when it does
|
|
67
63
|
@query_changed_listener = @query.on('changed') do
|
|
68
64
|
stop_listening
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
# Don't load again if all of the listeners are gone
|
|
67
|
+
load_data if @model.has_listeners?
|
|
71
68
|
end
|
|
72
69
|
end
|
|
73
70
|
|
|
@@ -94,29 +91,50 @@ module Persistors
|
|
|
94
91
|
end
|
|
95
92
|
end
|
|
96
93
|
|
|
94
|
+
# puts "IN QUERY: #{query.inspect} - #{self.inspect}"
|
|
97
95
|
@query_listener = @@query_pool.lookup(collection, query) do
|
|
98
96
|
# Create if it does not exist
|
|
99
97
|
QueryListener.new(@@query_pool, @tasks, collection, query)
|
|
100
98
|
end
|
|
101
|
-
|
|
99
|
+
|
|
100
|
+
@query_listener.add_store(self)
|
|
102
101
|
end
|
|
103
102
|
|
|
104
103
|
def find(query={})
|
|
105
|
-
model =
|
|
104
|
+
model = Cursor.new([], @model.options.merge(:query => query))
|
|
106
105
|
|
|
107
106
|
return ReactiveValue.new(model)
|
|
108
107
|
end
|
|
109
108
|
|
|
109
|
+
# Fetch does a one time load of the data on an unloaded model and returns
|
|
110
|
+
# the result.
|
|
111
|
+
def fetch(&block)
|
|
112
|
+
# puts "FETCH: #{@state.inspect}"
|
|
113
|
+
if @state == :loaded
|
|
114
|
+
block.call(@model)
|
|
115
|
+
else
|
|
116
|
+
@fetch_callbacks ||= []
|
|
117
|
+
@fetch_callbacks << block
|
|
118
|
+
|
|
119
|
+
load_data
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
110
123
|
# Called from backend
|
|
111
124
|
def add(index, data)
|
|
112
125
|
$loading_models = true
|
|
126
|
+
# puts "INSERT: #{data.inspect} into #{self.inspect}"
|
|
113
127
|
|
|
114
128
|
new_options = @model.options.merge(path: @model.path + [:[]], parent: @model)
|
|
115
129
|
|
|
116
|
-
#
|
|
117
|
-
|
|
130
|
+
# Don't add if the model is already in the ArrayModel
|
|
131
|
+
if !@model.cur.array.find {|v| v['_id'] == data['_id'] }
|
|
132
|
+
# Find the existing model, or create one
|
|
133
|
+
new_model = @@identity_map.find(data['_id']) { @model.new_model(data.symbolize_keys, new_options, :loaded) }
|
|
118
134
|
|
|
119
|
-
|
|
135
|
+
# puts "ADD: #{new_model.attributes.inspect}"
|
|
136
|
+
@model.insert(index, new_model)
|
|
137
|
+
end
|
|
120
138
|
|
|
121
139
|
$loading_models = false
|
|
122
140
|
end
|
|
@@ -148,10 +166,6 @@ module Persistors
|
|
|
148
166
|
# When a model is added to this collection, we call its "changed"
|
|
149
167
|
# method. This should trigger a save.
|
|
150
168
|
def added(model, index)
|
|
151
|
-
unless defined?($loading_models) && $loading_models
|
|
152
|
-
model.persistor.changed
|
|
153
|
-
end
|
|
154
|
-
|
|
155
169
|
if model.persistor
|
|
156
170
|
# Tell the persistor it was added
|
|
157
171
|
model.persistor.add_to_collection
|
|
@@ -164,7 +178,7 @@ module Persistors
|
|
|
164
178
|
model.persistor.remove_from_collection
|
|
165
179
|
end
|
|
166
180
|
|
|
167
|
-
if $loading_models
|
|
181
|
+
if defined?($loading_models) && $loading_models
|
|
168
182
|
return
|
|
169
183
|
else
|
|
170
184
|
puts "delete #{channel_name} - #{model.attributes[:_id]}"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Persistors
|
|
2
2
|
# Implements the base persistor functionality.
|
|
3
3
|
class Base
|
|
4
|
-
def loaded
|
|
4
|
+
def loaded(initial_state=nil)
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
def changed(attribute_name)
|
|
@@ -15,10 +15,10 @@ module Persistors
|
|
|
15
15
|
changed(attribute_name)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def event_added(event, scope_provider, first)
|
|
18
|
+
def event_added(event, scope_provider, first, first_for_event)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def event_removed(event,
|
|
21
|
+
def event_removed(event, last, last_for_event)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
require 'volt/models/persistors/store'
|
|
2
|
-
|
|
2
|
+
require 'volt/models/persistors/store_state'
|
|
3
3
|
|
|
4
4
|
module Persistors
|
|
5
5
|
class ModelStore < Store
|
|
6
|
+
include StoreState
|
|
7
|
+
|
|
6
8
|
ID_CHARS = [('a'..'z'), ('A'..'Z'), ('0'..'9')].map {|v| v.to_a }.flatten
|
|
7
9
|
|
|
8
10
|
attr_reader :model
|
|
@@ -51,7 +53,8 @@ module Persistors
|
|
|
51
53
|
|
|
52
54
|
# Called when the model changes
|
|
53
55
|
def changed(attribute_name=nil)
|
|
54
|
-
|
|
56
|
+
promise = Promise.new
|
|
57
|
+
|
|
55
58
|
ensure_setup
|
|
56
59
|
|
|
57
60
|
path_size = @model.path.size
|
|
@@ -60,13 +63,21 @@ module Persistors
|
|
|
60
63
|
@model.attributes[:"#{@model.path[-4].singularize}_id"] = source._id
|
|
61
64
|
end
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
@tasks.call('StoreTasks', 'save', collection, self_attributes) do |errors|
|
|
67
|
+
puts "SAVE GOT: #{errors.inspect}"
|
|
68
|
+
if errors.size == 0
|
|
69
|
+
promise.resolve
|
|
70
|
+
else
|
|
71
|
+
promise.reject(errors)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
65
74
|
end
|
|
75
|
+
|
|
76
|
+
return promise
|
|
66
77
|
end
|
|
67
78
|
|
|
68
|
-
def event_added(event, scope_provider, first)
|
|
69
|
-
if
|
|
79
|
+
def event_added(event, scope_provider, first, first_for_event)
|
|
80
|
+
if first_for_event && event == :changed
|
|
70
81
|
ensure_setup
|
|
71
82
|
end
|
|
72
83
|
end
|
|
@@ -65,7 +65,6 @@ class QueryListener
|
|
|
65
65
|
@stores.each do |store|
|
|
66
66
|
store.add(index, data)
|
|
67
67
|
end
|
|
68
|
-
puts "Added: #{index} - #{data.inspect}"
|
|
69
68
|
end
|
|
70
69
|
|
|
71
70
|
def removed(ids)
|
|
@@ -76,7 +75,6 @@ class QueryListener
|
|
|
76
75
|
|
|
77
76
|
def changed(model_id, data)
|
|
78
77
|
$loading_models = true
|
|
79
|
-
puts "From Backend: UPDATE: #{model_id} with #{data.inspect}"
|
|
80
78
|
Persistors::ModelStore.changed(model_id, data)
|
|
81
79
|
$loading_models = false
|
|
82
80
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# StoreState provides method for a store to track its loading state.
|
|
2
|
+
module StoreState
|
|
3
|
+
|
|
4
|
+
# Called when a collection loads
|
|
5
|
+
def loaded(initial_state=nil)
|
|
6
|
+
change_state_to(initial_state || :not_loaded)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def state
|
|
10
|
+
@state
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Called from the QueryListener when the data is loaded
|
|
14
|
+
def change_state_to(new_state)
|
|
15
|
+
@state = new_state
|
|
16
|
+
|
|
17
|
+
# Trigger changed on the 'state' method
|
|
18
|
+
@model.trigger_for_methods!('changed', :state, :loaded?)
|
|
19
|
+
|
|
20
|
+
if @state == :loaded && @fetch_callbacks
|
|
21
|
+
# Trigger each waiting fetch
|
|
22
|
+
@fetch_callbacks.compact.each {|fc| fc.call(@model) }
|
|
23
|
+
@fetch_callbacks = nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
data/lib/volt/models/url.rb
CHANGED
|
@@ -60,7 +60,7 @@ class URL
|
|
|
60
60
|
host_with_port = @host
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
path, params = @router.
|
|
63
|
+
path, params = @router.params_to_url(@params.deep_cur.to_h)
|
|
64
64
|
|
|
65
65
|
new_url = "#{@scheme}://#{host_with_port}#{(path || @path).chomp('/')}"
|
|
66
66
|
|
|
@@ -127,7 +127,7 @@ class URL
|
|
|
127
127
|
query_hash = self.query_hash
|
|
128
128
|
|
|
129
129
|
# Get the params that are in the route
|
|
130
|
-
query_hash.merge!(@router.
|
|
130
|
+
query_hash.merge!(@router.url_to_params(@path))
|
|
131
131
|
|
|
132
132
|
# Loop through the .params we already have assigned.
|
|
133
133
|
assign_from_old(@params, query_hash)
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Validations
|
|
2
|
+
class Length
|
|
3
|
+
def self.validate(model, field_name, args)
|
|
4
|
+
errors = {}
|
|
5
|
+
value = model.send(field_name)
|
|
6
|
+
if !value || value.size < args
|
|
7
|
+
errors[field_name] = ["must be at least #{args} chars"]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
return errors
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# require 'volt/models/validations/errors'
|
|
2
|
+
require 'volt/models/validations/length'
|
|
3
|
+
|
|
4
|
+
# Include in any class to get validation logic
|
|
5
|
+
module Validations
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def validate(field_name, options)
|
|
8
|
+
@validations ||= {}
|
|
9
|
+
@validations[field_name] = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validations
|
|
13
|
+
@validations
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.included(base)
|
|
18
|
+
base.send :extend, ClassMethods
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Sometimes we want to skip checking a field until some event
|
|
22
|
+
# has happened (usually a field has been typed in or blurred)
|
|
23
|
+
def exclude_from_errors!(field_name)
|
|
24
|
+
@exclude_from_errors ||= {}
|
|
25
|
+
@exclude_from_errors[field_name] = true
|
|
26
|
+
|
|
27
|
+
@include_in_errors.delete(field_name) if @include_in_errors
|
|
28
|
+
|
|
29
|
+
trigger_for_methods!('changed', :errors, :marked_errors)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Once a field is ready, we can use include_in_errors! to start
|
|
33
|
+
# showing its errors.
|
|
34
|
+
def mark_field!(field_name, trigger_changed=true)
|
|
35
|
+
@marked_fields ||= {}
|
|
36
|
+
@marked_fields[field_name] = true
|
|
37
|
+
|
|
38
|
+
if trigger_changed
|
|
39
|
+
trigger_for_methods!('changed', :errors, :marked_errors)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def marked_errors
|
|
44
|
+
errors(true)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def errors(marked_only=false)
|
|
48
|
+
errors = {}
|
|
49
|
+
|
|
50
|
+
validations = self.class.validations
|
|
51
|
+
|
|
52
|
+
if validations
|
|
53
|
+
# Merge into errors, combining any error arrays
|
|
54
|
+
merge = Proc.new do |new_errors|
|
|
55
|
+
errors.merge!(new_errors) do |key, new_val, old_val|
|
|
56
|
+
new_val + old_val
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Run through each validation
|
|
61
|
+
validations.each_pair do |field_name, options|
|
|
62
|
+
if marked_only
|
|
63
|
+
# When marked only, skip any validations on non-marked fields
|
|
64
|
+
next unless @marked_fields && @marked_fields[field_name]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
options.each_pair do |validation, args|
|
|
68
|
+
# Call the specific validator, then merge the results back
|
|
69
|
+
# into one large errors hash.
|
|
70
|
+
case validation
|
|
71
|
+
when :length
|
|
72
|
+
merge.call(Length.validate(self, field_name, args))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# puts "ERROR: #{errors.inspect}"
|
|
79
|
+
|
|
80
|
+
return errors
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/volt/models.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
require 'volt/extra_core/extra_core'
|
|
2
2
|
require 'volt/reactive/reactive_value'
|
|
3
3
|
require 'volt/models/model'
|
|
4
|
+
require 'volt/models/cursor'
|
|
4
5
|
require 'volt/models/persistors/store_factory'
|
|
5
6
|
require 'volt/models/persistors/array_store'
|
|
6
7
|
require 'volt/models/persistors/model_store'
|
|
7
8
|
require 'volt/models/persistors/params'
|
|
8
9
|
require 'volt/models/persistors/flash'
|
|
9
10
|
require 'volt/models/persistors/local_store'
|
|
10
|
-
|
|
@@ -20,14 +20,16 @@ class AttributeBinding < BaseBinding
|
|
|
20
20
|
# Run the initial update (render)
|
|
21
21
|
update
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
if @value.reactive?
|
|
24
|
+
@update_listener = @value.on('changed') { update }
|
|
25
|
+
|
|
26
|
+
# Bind so when this value updates, we update
|
|
27
|
+
case @attribute_name
|
|
28
|
+
when 'value'
|
|
29
|
+
element.on('input.attrbind') { changed }
|
|
30
|
+
when 'checked'
|
|
31
|
+
element.on('change.attrbind') {|event| changed(event) }
|
|
32
|
+
end
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -40,7 +42,9 @@ class AttributeBinding < BaseBinding
|
|
|
40
42
|
current_value = element.is(':checked')
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
# puts "ASSIGN #{current_value}"
|
|
43
46
|
@value.cur = current_value
|
|
47
|
+
# puts "ASSIGNED"
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
def element
|
|
@@ -48,11 +52,10 @@ class AttributeBinding < BaseBinding
|
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
def update
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
55
|
+
# puts "UPDATE GET VAL"
|
|
56
|
+
value = @value.cur
|
|
57
|
+
# puts "UPDATE GOT"
|
|
58
|
+
# puts "UPDATE1 to #{value.inspect}"
|
|
56
59
|
|
|
57
60
|
if @attribute_name == 'checked'
|
|
58
61
|
update_checked
|
|
@@ -91,6 +94,7 @@ class AttributeBinding < BaseBinding
|
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
def remove
|
|
97
|
+
# puts "REMOVE #{self.inspect}"
|
|
94
98
|
# Unbind events, leave the element there since attribute bindings
|
|
95
99
|
# aren't responsible for it being there.
|
|
96
100
|
case @attribute_name
|
|
@@ -100,6 +104,12 @@ class AttributeBinding < BaseBinding
|
|
|
100
104
|
element.off('change.attrbind', nil)
|
|
101
105
|
end
|
|
102
106
|
|
|
107
|
+
# Value is a reactive template, remove it
|
|
108
|
+
if @value && @value.reactive?
|
|
109
|
+
@value.remove
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
103
113
|
if @update_listener
|
|
104
114
|
@update_listener.remove
|
|
105
115
|
@update_listener = nil
|
|
@@ -108,7 +118,12 @@ class AttributeBinding < BaseBinding
|
|
|
108
118
|
# Clear any references
|
|
109
119
|
@target = nil
|
|
110
120
|
@context = nil
|
|
111
|
-
@
|
|
121
|
+
@getter = nil
|
|
122
|
+
@value = nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def remove_anchors
|
|
126
|
+
raise "attribute bindings do not have anchors, can not remove them"
|
|
112
127
|
end
|
|
113
128
|
|
|
114
129
|
|
|
@@ -26,7 +26,7 @@ class BaseBinding
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def remove
|
|
29
|
-
section.remove
|
|
29
|
+
section.remove if @section
|
|
30
30
|
|
|
31
31
|
# Clear any references
|
|
32
32
|
@target = nil
|
|
@@ -43,7 +43,7 @@ class BaseBinding
|
|
|
43
43
|
# Run right away
|
|
44
44
|
update
|
|
45
45
|
else
|
|
46
|
-
|
|
46
|
+
@page.draw_cycle.queue(self)
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -4,30 +4,34 @@ require 'volt/page/bindings/template_binding'
|
|
|
4
4
|
# and do not pass their context through
|
|
5
5
|
class ComponentBinding < TemplateBinding
|
|
6
6
|
# The context for a component binding can be either the controller, or the
|
|
7
|
-
# component arguments (@
|
|
7
|
+
# component arguments (@arguments), with the $page as the context. This gives
|
|
8
8
|
# components access to the page collections.
|
|
9
|
-
def render_template(full_path,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
9
|
+
# def render_template(full_path, controller_path)
|
|
10
|
+
# # TODO: at the moment a :body section and a :title will both initialize different
|
|
11
|
+
# # controllers. Maybe we should have a way to tie them together?
|
|
12
|
+
# controller_class, action = get_controller(controller_path)
|
|
13
|
+
#
|
|
14
|
+
# model_with_parent = {parent: @context}.merge(@arguments || {})
|
|
15
|
+
#
|
|
16
|
+
# if controller_class
|
|
17
|
+
# # The user provided a controller, pass in the model as an argument (in a
|
|
18
|
+
# # sub-context)
|
|
19
|
+
# args = []
|
|
20
|
+
# args << SubContext.new(model_with_parent) if @arguments
|
|
21
|
+
#
|
|
22
|
+
# current_context = controller_class.new(*args)
|
|
23
|
+
# @controller = current_context
|
|
24
|
+
#
|
|
25
|
+
# # Trigger the action
|
|
26
|
+
# @controller.send(action) if @controller.respond_to?(action)
|
|
27
|
+
# else
|
|
28
|
+
# # There is not a controller
|
|
29
|
+
# current_context = SubContext.new(model_with_parent, @page)
|
|
30
|
+
# @controller = nil
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @current_template = TemplateRenderer.new(@page, @target, current_context, @binding_name, full_path)
|
|
34
|
+
#
|
|
35
|
+
# call_ready
|
|
36
|
+
# end
|
|
33
37
|
end
|
|
@@ -21,6 +21,7 @@ class ContentBinding < BaseBinding
|
|
|
21
21
|
|
|
22
22
|
# Exception values display the exception as a string
|
|
23
23
|
value = value.to_s
|
|
24
|
+
# puts "UPDATE C: #{value.inspect} on #{self.inspect}"
|
|
24
25
|
|
|
25
26
|
# Update the html in this section
|
|
26
27
|
# TODO: Move the formatter into another class.
|