volt 0.9.5.pre4 → 0.9.5.pre5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +13 -5
- data/app/volt/assets/css/{notices.css.scss → notices.scss} +0 -0
- data/app/volt/models/active_volt_instance.rb +1 -1
- data/app/volt/tasks/live_query/live_query.rb +11 -3
- data/app/volt/tasks/store_tasks.rb +14 -17
- data/lib/volt/cli.rb +22 -0
- data/lib/volt/cli/asset_compile.rb +63 -63
- data/lib/volt/cli/base_index_renderer.rb +26 -0
- data/lib/volt/cli/generate.rb +1 -1
- data/lib/volt/config.rb +1 -0
- data/lib/volt/controllers/model_controller.rb +37 -1
- data/lib/volt/extra_core/array.rb +22 -0
- data/lib/volt/models/array_model.rb +7 -1
- data/lib/volt/models/errors.rb +1 -1
- data/lib/volt/models/field_helpers.rb +36 -21
- data/lib/volt/models/model.rb +16 -0
- data/lib/volt/models/validations/validations.rb +21 -6
- data/lib/volt/models/validators/type_validator.rb +35 -3
- data/lib/volt/page/bindings/content_binding.rb +1 -1
- data/lib/volt/page/bindings/event_binding.rb +40 -16
- data/lib/volt/page/document_events.rb +8 -6
- data/lib/volt/reactive/reactive_array.rb +18 -1
- data/lib/volt/server/forking_server.rb +7 -1
- data/lib/volt/server/html_parser/attribute_scope.rb +26 -0
- data/lib/volt/server/html_parser/component_view_scope.rb +30 -22
- data/lib/volt/server/middleware/default_middleware_stack.rb +6 -1
- data/lib/volt/server/rack/asset_files.rb +5 -3
- data/lib/volt/server/rack/opal_files.rb +35 -23
- data/lib/volt/server/rack/sprockets_helpers_setup.rb +71 -0
- data/lib/volt/server/template_handlers/view_processor.rb +1 -2
- data/lib/volt/utils/promise_extensions.rb +1 -1
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +0 -2
- data/lib/volt/volt/client_setup/browser.rb +11 -0
- data/spec/apps/kitchen_sink/Gemfile +37 -14
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +3 -0
- data/spec/apps/kitchen_sink/app/main/controllers/events_controller.rb +26 -0
- data/spec/apps/kitchen_sink/app/main/views/events/index.html +30 -0
- data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +3 -0
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +1 -6
- data/spec/apps/kitchen_sink/app/main/views/{yield-component → yield_component}/index.html +0 -0
- data/spec/extra_core/array_spec.rb +26 -0
- data/spec/integration/bindings_spec.rb +9 -0
- data/spec/integration/event_spec.rb +19 -0
- data/spec/models/array_model_spec.rb +13 -0
- data/spec/models/field_helpers_spec.rb +2 -2
- data/spec/models/validations_spec.rb +31 -0
- data/spec/models/validators/type_validator_spec.rb +47 -1
- data/spec/reactive/reactive_array_spec.rb +46 -0
- data/spec/server/forking_server_spec.rb +27 -0
- data/spec/server/html_parser/view_scope_spec.rb +44 -0
- data/spec/server/rack/asset_files_spec.rb +2 -2
- data/templates/project/Gemfile.tt +8 -0
- data/templates/project/config/app.rb.tt +2 -1
- data/volt.gemspec +1 -1
- metadata +31 -5
@@ -8,4 +8,26 @@ class Array
|
|
8
8
|
def to_h
|
9
9
|
Hash[self]
|
10
10
|
end
|
11
|
+
|
12
|
+
|
13
|
+
# Converts an array to a sentence
|
14
|
+
def to_sentence(options={})
|
15
|
+
conjunction = options.fetch(:conjunction, 'and')
|
16
|
+
comma = options.fetch(:comma, ',')
|
17
|
+
oxford = options.fetch(:oxford, true) # <- true is the right value
|
18
|
+
|
19
|
+
case size
|
20
|
+
when 0
|
21
|
+
''
|
22
|
+
when 1
|
23
|
+
self[0].to_s
|
24
|
+
when 2
|
25
|
+
self.join(" #{conjunction} ")
|
26
|
+
else
|
27
|
+
str = self[0..-2].join(comma + ' ')
|
28
|
+
str += comma if oxford
|
29
|
+
str += " #{conjunction} " + self[-1].to_s
|
30
|
+
str
|
31
|
+
end
|
32
|
+
end
|
11
33
|
end
|
@@ -241,6 +241,7 @@ module Volt
|
|
241
241
|
def new_model(*args)
|
242
242
|
Volt::Model.class_at_path(options[:path]).new(*args)
|
243
243
|
end
|
244
|
+
alias_method :new, :new_model
|
244
245
|
|
245
246
|
def new_array_model(*args)
|
246
247
|
Volt::ArrayModel.class_at_path(options[:path]).new(*args)
|
@@ -306,6 +307,11 @@ module Volt
|
|
306
307
|
name.pluralize
|
307
308
|
end
|
308
309
|
|
310
|
+
alias_method :reactive_count, :count
|
311
|
+
def count(&block)
|
312
|
+
all.reactive_count(&block)
|
313
|
+
end
|
314
|
+
|
309
315
|
private
|
310
316
|
# called form <<, append, and create. If a hash is passed in, it converts
|
311
317
|
# it to a model. Then it takes the model and inserts it into the ArrayModel
|
@@ -381,7 +387,7 @@ module Volt
|
|
381
387
|
end
|
382
388
|
|
383
389
|
# We need to setup the proxy methods below where they are defined.
|
384
|
-
proxy_with_load :[], :size, :last, :reverse, :all, :to_a, :empty?, :present?, :blank?
|
390
|
+
proxy_with_load :[], :size, :length, :last, :reverse, :all, :to_a, :empty?, :present?, :blank?
|
385
391
|
|
386
392
|
end
|
387
393
|
end
|
data/lib/volt/models/errors.rb
CHANGED
@@ -22,34 +22,45 @@ module FieldHelpers
|
|
22
22
|
end
|
23
23
|
|
24
24
|
FIELD_CASTS = {
|
25
|
-
String
|
26
|
-
Fixnum
|
27
|
-
Numeric
|
28
|
-
Float
|
29
|
-
Time
|
30
|
-
TrueClass
|
31
|
-
FalseClass
|
25
|
+
String => :to_s.to_proc,
|
26
|
+
Fixnum => lambda {|val| NUMERIC_CAST[:Integer, val] },
|
27
|
+
Numeric => lambda {|val| NUMERIC_CAST[:Float, val] },
|
28
|
+
Float => lambda {|val| NUMERIC_CAST[:Float, val] },
|
29
|
+
Time => nil,
|
30
|
+
TrueClass => nil,
|
31
|
+
FalseClass => nil,
|
32
|
+
NilClass => nil,
|
33
|
+
Volt::Boolean => nil
|
32
34
|
}
|
33
|
-
VALID_FIELD_CLASSES = FIELD_CASTS.keys
|
34
35
|
|
35
36
|
|
36
37
|
module ClassMethods
|
37
38
|
# field lets you declare your fields instead of using the underscore syntax.
|
38
39
|
# An optional class restriction can be passed in.
|
39
|
-
def field(name,
|
40
|
-
if
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
def field(name, klasses = nil, options = {})
|
41
|
+
if klasses
|
42
|
+
klasses = [klasses].flatten
|
43
|
+
|
44
|
+
unless klasses.any? {|kl| FIELD_CASTS.key?(kl) }
|
45
|
+
klass_names = FIELD_CASTS.keys.map(&:to_s).join(', ')
|
46
|
+
msg = "valid field types is currently limited to #{klass_names}, you passed: #{klasses.inspect}"
|
47
|
+
fail FieldHelpers::InvalidFieldClass, msg
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add NilClass as an allowed type unless allow_nil: false was passed.
|
51
|
+
unless options[:allow_nil] == false
|
52
|
+
klasses << NilClass
|
53
|
+
end
|
44
54
|
end
|
45
55
|
|
46
56
|
self.fields_data ||= {}
|
47
|
-
self.fields_data[name] = [
|
57
|
+
self.fields_data[name] = [klasses, options]
|
48
58
|
|
49
|
-
if
|
50
|
-
# Add type validation, execpt for String, since anything can be
|
51
|
-
|
52
|
-
|
59
|
+
if klasses
|
60
|
+
# Add type validation, execpt for String, since anything can be cast to
|
61
|
+
# a string.
|
62
|
+
unless klasses.include?(String)
|
63
|
+
validate name, type: klasses
|
53
64
|
end
|
54
65
|
end
|
55
66
|
|
@@ -59,10 +70,14 @@ module FieldHelpers
|
|
59
70
|
|
60
71
|
define_method(:"#{name}=") do |val|
|
61
72
|
# Check if the value assigned matches the class restriction
|
62
|
-
if
|
73
|
+
if klasses
|
63
74
|
# Cast to the right type
|
64
|
-
|
65
|
-
|
75
|
+
klasses.each do |kl|
|
76
|
+
if (func = FIELD_CASTS[kl])
|
77
|
+
# Cast on the first available caster
|
78
|
+
val = func[val]
|
79
|
+
break
|
80
|
+
end
|
66
81
|
end
|
67
82
|
end
|
68
83
|
|
data/lib/volt/models/model.rb
CHANGED
@@ -354,6 +354,22 @@ module Volt
|
|
354
354
|
to_h.to_json
|
355
355
|
end
|
356
356
|
|
357
|
+
# Update tries to update the model and returns
|
358
|
+
def update(attrs)
|
359
|
+
old_attrs = @attributes.dup
|
360
|
+
Model.no_change_tracking do
|
361
|
+
assign_all_attributes(attrs, false)
|
362
|
+
|
363
|
+
validate!.then do |errs|
|
364
|
+
if errs && errs.present?
|
365
|
+
# Revert wholesale
|
366
|
+
@attributes = old_attrs
|
367
|
+
Promise.new.resolve(errs)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
357
373
|
private
|
358
374
|
def run_initial_setup(initial_setup)
|
359
375
|
# Save the changes
|
@@ -42,12 +42,15 @@ module Volt
|
|
42
42
|
|
43
43
|
if run_in_actions.size == 0 || run_in_actions.include?(action)
|
44
44
|
@instance_validations = {}
|
45
|
+
@instance_custom_validations = []
|
45
46
|
|
46
47
|
instance_exec(action, &block)
|
47
48
|
|
48
49
|
result = run_validations(@instance_validations)
|
50
|
+
result.merge!(run_custom_validations(@instance_custom_validations))
|
49
51
|
|
50
52
|
@instance_validations = nil
|
53
|
+
@instance_custom_validations = nil
|
51
54
|
|
52
55
|
result
|
53
56
|
end
|
@@ -55,9 +58,19 @@ module Volt
|
|
55
58
|
end
|
56
59
|
end
|
57
60
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
+
# Called on the model inside of a validations block. Allows the user to
|
62
|
+
# control if validations should be run.
|
63
|
+
def validate(field_name = nil, options = nil, &block)
|
64
|
+
if block
|
65
|
+
# Setup a custom validation inside of the current validations block.
|
66
|
+
if field_name || options
|
67
|
+
fail 'validate should be passed a field name and options or a block, not both.'
|
68
|
+
end
|
69
|
+
@instance_custom_validations << block
|
70
|
+
else
|
71
|
+
@instance_validations[field_name] ||= {}
|
72
|
+
@instance_validations[field_name].merge!(options)
|
73
|
+
end
|
61
74
|
end
|
62
75
|
|
63
76
|
def self.included(base)
|
@@ -200,10 +213,12 @@ module Volt
|
|
200
213
|
promise
|
201
214
|
end
|
202
215
|
|
203
|
-
def run_custom_validations
|
216
|
+
def run_custom_validations(custom_validations = nil)
|
217
|
+
# Default to running the class level custom validations
|
218
|
+
custom_validations ||= self.class.custom_validations
|
219
|
+
|
204
220
|
promise = Promise.new.resolve(nil)
|
205
|
-
|
206
|
-
custom_validations = self.class.custom_validations
|
221
|
+
|
207
222
|
if custom_validations
|
208
223
|
custom_validations.each do |custom_validation|
|
209
224
|
# Add to the promise chain
|
@@ -1,17 +1,49 @@
|
|
1
1
|
# Enforces a type on a field. Typically setup from ```field :name, Type```
|
2
2
|
module Volt
|
3
|
+
# Volt::Boolean can be used if you want a boolean type
|
4
|
+
class Boolean
|
5
|
+
end
|
6
|
+
|
3
7
|
class TypeValidator
|
4
8
|
def self.validate(model, field_name, args)
|
5
9
|
errors = {}
|
6
10
|
value = model.get(field_name)
|
7
11
|
|
8
|
-
type_restriction = args.is_a?(Hash) ? args[:type] : args
|
12
|
+
type_restriction = args.is_a?(Hash) ? (args[:type] || args[:types]) : args
|
9
13
|
|
10
|
-
|
14
|
+
# Make into an array of 1 if its not already an array.
|
15
|
+
type_restrictions = [type_restriction].flatten
|
16
|
+
|
17
|
+
valid_type = false
|
18
|
+
type_restrictions.each do |type_rest|
|
19
|
+
if value.is_a?(type_rest)
|
20
|
+
valid_type = true
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
unless valid_type
|
11
26
|
if args.is_a?(Hash) && args[:message]
|
12
27
|
message = args[:message]
|
13
28
|
else
|
14
|
-
|
29
|
+
type_msgs = type_restrictions.map do |type_rest|
|
30
|
+
if [Fixnum, Float, Numeric].include?(type_rest)
|
31
|
+
"a number"
|
32
|
+
elsif type_rest == NilClass
|
33
|
+
# we don't mention the nil restriction
|
34
|
+
nil
|
35
|
+
elsif type_rest == Volt::Boolean
|
36
|
+
['true', 'false']
|
37
|
+
elsif type_rest == TrueClass
|
38
|
+
'true'
|
39
|
+
elsif type_rest == FalseClass
|
40
|
+
'false'
|
41
|
+
else
|
42
|
+
"a #{type_rest.to_s}"
|
43
|
+
end
|
44
|
+
end.flatten
|
45
|
+
|
46
|
+
message = "must be #{type_msgs.compact.to_sentence(conjunction: 'or')}"
|
15
47
|
end
|
16
48
|
|
17
49
|
errors[field_name] = [message]
|
@@ -5,6 +5,9 @@ module Volt
|
|
5
5
|
class JSEvent
|
6
6
|
attr_reader :js_event
|
7
7
|
|
8
|
+
# The Volt controller that dispatched the event.
|
9
|
+
attr_accessor :controller
|
10
|
+
|
8
11
|
def initialize(js_event)
|
9
12
|
@js_event = js_event
|
10
13
|
end
|
@@ -43,31 +46,52 @@ module Volt
|
|
43
46
|
end
|
44
47
|
|
45
48
|
|
46
|
-
handler = proc do |js_event|
|
49
|
+
handler = proc do |js_event, *args|
|
47
50
|
event = JSEvent.new(js_event)
|
48
51
|
event.prevent_default! if event_name == 'submit'
|
49
52
|
|
50
|
-
#
|
51
|
-
# pass
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
# When the event is triggered via ```trigger(..)``` in a controller,
|
54
|
+
# it will pass its self as the first argument. We set that to
|
55
|
+
# ```controller``` on the event, so it can be easily accessed.
|
56
|
+
if args[0].is_a?(Volt::ModelController)
|
57
|
+
args = args.dup
|
58
|
+
event.controller = args.shift
|
59
|
+
end
|
57
60
|
|
58
|
-
|
59
|
-
# # handled
|
60
|
-
# if result.is_a?(Promise) && !result.next
|
61
|
-
# result.fail do |err|
|
62
|
-
# Volt.logger.error("EventBinding Error: promise returned from event binding #{@event_name} was rejected")
|
63
|
-
# Volt.logger.error(err)
|
64
|
-
# end
|
65
|
-
# end
|
61
|
+
args << event
|
66
62
|
|
63
|
+
self.class.call_handler_proc(@context, call_proc, event, args)
|
67
64
|
end
|
65
|
+
|
68
66
|
@listener = browser.events.add(@event_name, self, handler)
|
69
67
|
end
|
70
68
|
|
69
|
+
def self.call_handler_proc(context, call_proc, event, args)
|
70
|
+
# When the EventBinding is compiled, it converts a passed in string to
|
71
|
+
# get a Method:
|
72
|
+
#
|
73
|
+
# Example:
|
74
|
+
# <a e-awesome="some_method">...</a>
|
75
|
+
#
|
76
|
+
# The call_proc will be passed in as: Proc.new { method(:some_method) }
|
77
|
+
#
|
78
|
+
# So first we call the call_proc, then that returns a method (or proc),
|
79
|
+
# which we call passing in the arguments based on the arity.
|
80
|
+
#
|
81
|
+
# If the e- binding has arguments passed to it, we just use those.
|
82
|
+
result = context.instance_exec(event, &call_proc)
|
83
|
+
# Trim args to match arity
|
84
|
+
|
85
|
+
# The proc returned a
|
86
|
+
if result && result.is_a?(Method)
|
87
|
+
args = args[0...result.arity]
|
88
|
+
|
89
|
+
result.call(*args)
|
90
|
+
end
|
91
|
+
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
71
95
|
# Remove the event binding
|
72
96
|
def remove
|
73
97
|
browser.events.remove(@event_name, self)
|
@@ -14,10 +14,12 @@ module Volt
|
|
14
14
|
|
15
15
|
that = self
|
16
16
|
|
17
|
+
document_handler = proc do |*args|
|
18
|
+
handle(event, *args)
|
19
|
+
end
|
20
|
+
|
17
21
|
`
|
18
|
-
$('body').on(event,
|
19
|
-
that.$handle(event, e, e.target || e.originalEvent.target);
|
20
|
-
});
|
22
|
+
$('body').on(event, #{document_handler});
|
21
23
|
`
|
22
24
|
|
23
25
|
end
|
@@ -26,8 +28,8 @@ module Volt
|
|
26
28
|
@events[event][binding.binding_name][binding.object_id] = handler
|
27
29
|
end
|
28
30
|
|
29
|
-
def handle(event_name, event,
|
30
|
-
element = `$(
|
31
|
+
def handle(event_name, event, *args)
|
32
|
+
element = `$(event.target || event.originalEvent.target)`
|
31
33
|
|
32
34
|
loop do
|
33
35
|
# Lookup the handler, make sure to not assume the group
|
@@ -43,7 +45,7 @@ module Volt
|
|
43
45
|
if handlers
|
44
46
|
handlers.values.each do |handler|
|
45
47
|
# Call each handler for this object
|
46
|
-
handler.call(event)
|
48
|
+
handler.call(event, *args)
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -93,10 +93,27 @@ module Volt
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
def last
|
97
|
+
self[-1]
|
98
|
+
end
|
99
|
+
|
96
100
|
# TODO: Handle a range
|
97
101
|
def [](index)
|
98
102
|
# Handle a negative index, depend on size
|
99
|
-
|
103
|
+
if index < 0
|
104
|
+
# Depend on size by calling .size, since we are looking up reverse
|
105
|
+
# indexes
|
106
|
+
|
107
|
+
# cache size lookup
|
108
|
+
size = self.size
|
109
|
+
|
110
|
+
index = size + index
|
111
|
+
|
112
|
+
# In this case, we're looking back past 0 (going backwards), so we get
|
113
|
+
# nil. Since we're depending on @size_dep (because we called .size),
|
114
|
+
# it will invalidate when the size changes.
|
115
|
+
return nil if index < 0
|
116
|
+
end
|
100
117
|
|
101
118
|
# Get or create the dependency
|
102
119
|
dep = (@array_deps[index] ||= Dependency.new)
|
@@ -257,8 +257,14 @@ module Volt
|
|
257
257
|
|
258
258
|
def start_change_listener
|
259
259
|
sync_mod_time
|
260
|
+
|
261
|
+
options = {}
|
262
|
+
if ENV['POLL_FS']
|
263
|
+
options[:force_polling] = true
|
264
|
+
end
|
265
|
+
|
260
266
|
# Setup the listeners for file changes
|
261
|
-
@listener = Listen.to("#{@server.app_path}/") do |modified, added, removed|
|
267
|
+
@listener = Listen.to("#{@server.app_path}/", options) do |modified, added, removed|
|
262
268
|
Thread.new do
|
263
269
|
# Run the reload in a new thread
|
264
270
|
reload(modified + added + removed)
|