canjs-rails 0.1.0
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.
- data/.gitignore +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/canjs-rails.gemspec +23 -0
- data/lib/canjs-rails.rb +1 -0
- data/lib/canjs/rails.rb +7 -0
- data/lib/canjs/rails/engine.rb +6 -0
- data/lib/canjs/rails/version.rb +6 -0
- data/vendor/assets/javascripts/can.construct.proxy.js +60 -0
- data/vendor/assets/javascripts/can.construct.super.js +44 -0
- data/vendor/assets/javascripts/can.control.plugin.js +245 -0
- data/vendor/assets/javascripts/can.control.view.js +88 -0
- data/vendor/assets/javascripts/can.fixture.js +1020 -0
- data/vendor/assets/javascripts/can.jquery.js +2995 -0
- data/vendor/assets/javascripts/can.jquery.min.js +52 -0
- data/vendor/assets/javascripts/can.observe.attributes.js +293 -0
- data/vendor/assets/javascripts/can.observe.backup.js +368 -0
- data/vendor/assets/javascripts/can.observe.delegate.js +359 -0
- data/vendor/assets/javascripts/can.observe.setter.js +58 -0
- data/vendor/assets/javascripts/can.observe.validations.js +374 -0
- data/vendor/assets/javascripts/can.view.modifiers.js +292 -0
- data/vendor/assets/javascripts/download_canjs.sh +15 -0
- metadata +108 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
(function(can, window, undefined){
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
// ** - 'this' will be the deepest item changed
|
6
|
+
// * - 'this' will be any changes within *, but * will be the
|
7
|
+
// this returned
|
8
|
+
|
9
|
+
// tells if the parts part of a delegate matches the broken up props of the event
|
10
|
+
// gives the prop to use as 'this'
|
11
|
+
// - parts - the attribute name of the delegate split in parts ['foo','*']
|
12
|
+
// - props - the split props of the event that happened ['foo','bar','0']
|
13
|
+
// - returns - the attribute to delegate too ('foo.bar'), or null if not a match
|
14
|
+
var matches = function(parts, props){
|
15
|
+
//check props parts are the same or
|
16
|
+
var len = parts.length,
|
17
|
+
i =0,
|
18
|
+
// keeps the matched props we will use
|
19
|
+
matchedProps = [],
|
20
|
+
prop;
|
21
|
+
|
22
|
+
// if the event matches
|
23
|
+
for(i; i< len; i++){
|
24
|
+
prop = props[i]
|
25
|
+
// if no more props (but we should be matching them)
|
26
|
+
// return null
|
27
|
+
if( typeof prop !== 'string' ) {
|
28
|
+
return null;
|
29
|
+
} else
|
30
|
+
// if we have a "**", match everything
|
31
|
+
if( parts[i] == "**" ) {
|
32
|
+
return props.join(".");
|
33
|
+
} else
|
34
|
+
// a match, but we want to delegate to "*"
|
35
|
+
if (parts[i] == "*"){
|
36
|
+
// only do this if there is nothing after ...
|
37
|
+
matchedProps.push(prop);
|
38
|
+
}
|
39
|
+
else if( prop === parts[i] ) {
|
40
|
+
matchedProps.push(prop);
|
41
|
+
} else {
|
42
|
+
return null;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
return matchedProps.join(".");
|
46
|
+
},
|
47
|
+
// gets a change event and tries to figure out which
|
48
|
+
// delegates to call
|
49
|
+
delegate = function(event, prop, how, newVal, oldVal){
|
50
|
+
// pre-split properties to save some regexp time
|
51
|
+
var props = prop.split("."),
|
52
|
+
delegates = (this._observe_delegates || []).slice(0),
|
53
|
+
delegate,
|
54
|
+
attr,
|
55
|
+
matchedAttr,
|
56
|
+
hasMatch,
|
57
|
+
valuesEqual;
|
58
|
+
event.attr = prop;
|
59
|
+
event.lastAttr = props[props.length -1 ];
|
60
|
+
|
61
|
+
// for each delegate
|
62
|
+
for(var i =0; delegate = delegates[i++];){
|
63
|
+
|
64
|
+
// if there is a batchNum, this means that this
|
65
|
+
// event is part of a series of events caused by a single
|
66
|
+
// attrs call. We don't want to issue the same event
|
67
|
+
// multiple times
|
68
|
+
// setting the batchNum happens later
|
69
|
+
if((event.batchNum && delegate.batchNum === event.batchNum) || delegate.undelegated ){
|
70
|
+
continue;
|
71
|
+
}
|
72
|
+
|
73
|
+
// reset match and values tests
|
74
|
+
hasMatch = undefined;
|
75
|
+
valuesEqual = true;
|
76
|
+
|
77
|
+
// for each attr in a delegate
|
78
|
+
for(var a =0 ; a < delegate.attrs.length; a++){
|
79
|
+
|
80
|
+
attr = delegate.attrs[a];
|
81
|
+
|
82
|
+
// check if it is a match
|
83
|
+
if(matchedAttr = matches(attr.parts, props)){
|
84
|
+
hasMatch = matchedAttr;
|
85
|
+
}
|
86
|
+
// if it has a value, make sure it's the right value
|
87
|
+
// if it's set, we should probably check that it has a
|
88
|
+
// value no matter what
|
89
|
+
if(attr.value && valuesEqual /* || delegate.hasValues */){
|
90
|
+
valuesEqual = attr.value === ""+this.attr(attr.attr)
|
91
|
+
} else if (valuesEqual && delegate.attrs.length > 1){
|
92
|
+
// if there are multiple attributes, each has to at
|
93
|
+
// least have some value
|
94
|
+
valuesEqual = this.attr(attr.attr) !== undefined
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
// if there is a match and valuesEqual ... call back
|
99
|
+
|
100
|
+
if(hasMatch && valuesEqual) {
|
101
|
+
// how to get to the changed property from the delegate
|
102
|
+
var from = prop.replace(hasMatch+".","");
|
103
|
+
|
104
|
+
// if this event is part of a batch, set it on the delegate
|
105
|
+
// to only send one event
|
106
|
+
if(event.batchNum ){
|
107
|
+
delegate.batchNum = event.batchNum
|
108
|
+
}
|
109
|
+
|
110
|
+
// if we listen to change, fire those with the same attrs
|
111
|
+
// TODO: the attrs should probably be using from
|
112
|
+
if( delegate.event === 'change' ){
|
113
|
+
arguments[1] = from;
|
114
|
+
event.curAttr = hasMatch;
|
115
|
+
delegate.callback.apply(this.attr(hasMatch), can.makeArray( arguments));
|
116
|
+
} else if(delegate.event === how ){
|
117
|
+
|
118
|
+
// if it's a match, callback with the location of the match
|
119
|
+
delegate.callback.apply(this.attr(hasMatch), [event,newVal, oldVal, from]);
|
120
|
+
} else if(delegate.event === 'set' &&
|
121
|
+
how == 'add' ) {
|
122
|
+
// if we are listening to set, we should also listen to add
|
123
|
+
delegate.callback.apply(this.attr(hasMatch), [event,newVal, oldVal, from]);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
}
|
128
|
+
};
|
129
|
+
|
130
|
+
can.extend(can.Observe.prototype,{
|
131
|
+
/**
|
132
|
+
* @function can.Observe.prototype.delegate
|
133
|
+
* @parent can.Observe.delegate
|
134
|
+
* @plugin can/observe/delegate
|
135
|
+
*
|
136
|
+
* `delegate( selector, event, handler(ev,newVal,oldVal,from) )` listen for changes
|
137
|
+
* in a child attribute from the parent. The child attribute
|
138
|
+
* does not have to exist.
|
139
|
+
*
|
140
|
+
*
|
141
|
+
* // create an observable
|
142
|
+
* var observe = can.Observe({
|
143
|
+
* foo : {
|
144
|
+
* bar : "Hello World"
|
145
|
+
* }
|
146
|
+
* })
|
147
|
+
*
|
148
|
+
* //listen to changes on a property
|
149
|
+
* observe.delegate("foo.bar","change", function(ev, prop, how, newVal, oldVal){
|
150
|
+
* // foo.bar has been added, set, or removed
|
151
|
+
* this //->
|
152
|
+
* });
|
153
|
+
*
|
154
|
+
* // change the property
|
155
|
+
* observe.attr('foo.bar',"Goodbye Cruel World")
|
156
|
+
*
|
157
|
+
* ## Types of events
|
158
|
+
*
|
159
|
+
* Delegate lets you listen to add, set, remove, and change events on property.
|
160
|
+
*
|
161
|
+
* __add__
|
162
|
+
*
|
163
|
+
* An add event is fired when a new property has been added.
|
164
|
+
*
|
165
|
+
* var o = new can.Control({});
|
166
|
+
* o.delegate("name","add", function(ev, value){
|
167
|
+
* // called once
|
168
|
+
* can.$('#name').show()
|
169
|
+
* })
|
170
|
+
* o.attr('name',"Justin")
|
171
|
+
* o.attr('name',"Brian");
|
172
|
+
*
|
173
|
+
* Listening to add events is useful for 'setup' functionality (in this case
|
174
|
+
* showing the <code>#name</code> element.
|
175
|
+
*
|
176
|
+
* __set__
|
177
|
+
*
|
178
|
+
* Set events are fired when a property takes on a new value. set events are
|
179
|
+
* always fired after an add.
|
180
|
+
*
|
181
|
+
* o.delegate("name","set", function(ev, value){
|
182
|
+
* // called twice
|
183
|
+
* can.$('#name').text(value)
|
184
|
+
* })
|
185
|
+
* o.attr('name',"Justin")
|
186
|
+
* o.attr('name',"Brian");
|
187
|
+
*
|
188
|
+
* __remove__
|
189
|
+
*
|
190
|
+
* Remove events are fired after a property is removed.
|
191
|
+
*
|
192
|
+
* o.delegate("name","remove", function(ev){
|
193
|
+
* // called once
|
194
|
+
* $('#name').text(value)
|
195
|
+
* })
|
196
|
+
* o.attr('name',"Justin");
|
197
|
+
* o.removeAttr('name');
|
198
|
+
*
|
199
|
+
* ## Wildcards - matching multiple properties
|
200
|
+
*
|
201
|
+
* Sometimes, you want to know when any property within some part
|
202
|
+
* of an observe has changed. Delegate lets you use wildcards to
|
203
|
+
* match any property name. The following listens for any change
|
204
|
+
* on an attribute of the params attribute:
|
205
|
+
*
|
206
|
+
* var o = can.Control({
|
207
|
+
* options : {
|
208
|
+
* limit : 100,
|
209
|
+
* offset: 0,
|
210
|
+
* params : {
|
211
|
+
* parentId: 5
|
212
|
+
* }
|
213
|
+
* }
|
214
|
+
* })
|
215
|
+
* o.delegate('options.*','change', function(){
|
216
|
+
* alert('1');
|
217
|
+
* })
|
218
|
+
* o.delegate('options.**','change', function(){
|
219
|
+
* alert('2');
|
220
|
+
* })
|
221
|
+
*
|
222
|
+
* // alerts 1
|
223
|
+
* // alerts 2
|
224
|
+
* o.attr('options.offset',100)
|
225
|
+
*
|
226
|
+
* // alerts 2
|
227
|
+
* o.attr('options.params.parentId',6);
|
228
|
+
*
|
229
|
+
* Using a single wildcard (<code>*</code>) matches single level
|
230
|
+
* properties. Using a double wildcard (<code>**</code>) matches
|
231
|
+
* any deep property.
|
232
|
+
*
|
233
|
+
* ## Listening on multiple properties and values
|
234
|
+
*
|
235
|
+
* Delegate lets you listen on multiple values at once. The following listens
|
236
|
+
* for first and last name changes:
|
237
|
+
*
|
238
|
+
* var o = new can.Observe({
|
239
|
+
* name : {first: "Justin", last: "Meyer"}
|
240
|
+
* })
|
241
|
+
*
|
242
|
+
* o.bind("name.first,name.last",
|
243
|
+
* "set",
|
244
|
+
* function(ev,newVal,oldVal,from){
|
245
|
+
*
|
246
|
+
* })
|
247
|
+
*
|
248
|
+
* ## Listening when properties are a particular value
|
249
|
+
*
|
250
|
+
* Delegate lets you listen when a property is __set__ to a specific value:
|
251
|
+
*
|
252
|
+
* var o = new can.Observe({
|
253
|
+
* name : "Justin"
|
254
|
+
* })
|
255
|
+
*
|
256
|
+
* o.bind("name=Brian",
|
257
|
+
* "set",
|
258
|
+
* function(ev,newVal,oldVal,from){
|
259
|
+
*
|
260
|
+
* })
|
261
|
+
*
|
262
|
+
* @param {String} selector The attributes you want to listen for changes in.
|
263
|
+
*
|
264
|
+
* Selector should be the property or
|
265
|
+
* property names of the element you are searching. Examples:
|
266
|
+
*
|
267
|
+
* "name" - listens to the "name" property changing
|
268
|
+
* "name, address" - listens to "name" or "address" changing
|
269
|
+
* "name address" - listens to "name" or "address" changing
|
270
|
+
* "address.*" - listens to property directly in address
|
271
|
+
* "address.**" - listens to any property change in address
|
272
|
+
* "foo=bar" - listens when foo is "bar"
|
273
|
+
*
|
274
|
+
* @param {String} event The event name. One of ("set","add","remove","change")
|
275
|
+
* @param {Function} handler(ev,newVal,oldVal,prop) The callback handler
|
276
|
+
* called with:
|
277
|
+
*
|
278
|
+
* - newVal - the new value set on the observe
|
279
|
+
* - oldVal - the old value set on the observe
|
280
|
+
* - prop - the prop name that was changed
|
281
|
+
*
|
282
|
+
* @return {jQuery.Delegate} the delegate for chaining
|
283
|
+
*/
|
284
|
+
delegate : function(selector, event, handler){
|
285
|
+
selector = can.trim(selector);
|
286
|
+
var delegates = this._observe_delegates || (this._observe_delegates = []),
|
287
|
+
attrs = [];
|
288
|
+
|
289
|
+
// split selector by spaces
|
290
|
+
selector.replace(/([^\s=]+)=?([^\s]+)?/g, function(whole, attr, value){
|
291
|
+
attrs.push({
|
292
|
+
// the attribute name
|
293
|
+
attr: attr,
|
294
|
+
// the attribute's pre-split names (for speed)
|
295
|
+
parts: attr.split('.'),
|
296
|
+
// the value associated with this prop
|
297
|
+
value: value
|
298
|
+
})
|
299
|
+
});
|
300
|
+
|
301
|
+
// delegates has pre-processed info about the event
|
302
|
+
delegates.push({
|
303
|
+
// the attrs name for unbinding
|
304
|
+
selector : selector,
|
305
|
+
// an object of attribute names and values {type: 'recipe',id: undefined}
|
306
|
+
// undefined means a value was not defined
|
307
|
+
attrs : attrs,
|
308
|
+
callback : handler,
|
309
|
+
event: event
|
310
|
+
});
|
311
|
+
if(delegates.length === 1){
|
312
|
+
this.bind("change",delegate)
|
313
|
+
}
|
314
|
+
return this;
|
315
|
+
},
|
316
|
+
/**
|
317
|
+
* @function can.Observe.prototype.undelegate
|
318
|
+
* @parent can.Observe.delegate
|
319
|
+
*
|
320
|
+
* `undelegate( selector, event, handler )` removes a delegated event handler from an observe.
|
321
|
+
*
|
322
|
+
* observe.undelegate("name","set", handler )
|
323
|
+
*
|
324
|
+
* @param {String} selector the attribute name of the object you want to undelegate from.
|
325
|
+
* @param {String} event the event name
|
326
|
+
* @param {Function} handler the callback handler
|
327
|
+
* @return {jQuery.Delegate} the delegate for chaining
|
328
|
+
*/
|
329
|
+
undelegate : function(selector, event, handler){
|
330
|
+
selector = can.trim(selector);
|
331
|
+
|
332
|
+
var i =0,
|
333
|
+
delegates = this._observe_delegates || [],
|
334
|
+
delegateOb;
|
335
|
+
if(selector){
|
336
|
+
while(i < delegates.length){
|
337
|
+
delegateOb = delegates[i];
|
338
|
+
if( delegateOb.callback === handler ||
|
339
|
+
(!handler && delegateOb.selector === selector) ){
|
340
|
+
delegateOb.undelegated = true;
|
341
|
+
delegates.splice(i,1)
|
342
|
+
} else {
|
343
|
+
i++;
|
344
|
+
}
|
345
|
+
}
|
346
|
+
} else {
|
347
|
+
// remove all delegates
|
348
|
+
delegates = [];
|
349
|
+
}
|
350
|
+
if(!delegates.length){
|
351
|
+
//can.removeData(this, "_observe_delegates");
|
352
|
+
this.unbind("change",delegate)
|
353
|
+
}
|
354
|
+
return this;
|
355
|
+
}
|
356
|
+
});
|
357
|
+
// add helpers for testing ..
|
358
|
+
can.Observe.prototype.delegate.matches = matches;
|
359
|
+
})(this.can, this )
|
@@ -0,0 +1,58 @@
|
|
1
|
+
(function(can, window, undefined){
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Like [can.camelize|camelize], but the first part is also capitalized
|
5
|
+
* @param {String} s
|
6
|
+
* @return {String} the classized string
|
7
|
+
*/
|
8
|
+
can.classize = function( s , join) {
|
9
|
+
// this can be moved out ..
|
10
|
+
// used for getter setter
|
11
|
+
var parts = s.split(can.undHash),
|
12
|
+
i = 0;
|
13
|
+
for (; i < parts.length; i++ ) {
|
14
|
+
parts[i] = can.capitalize(parts[i]);
|
15
|
+
}
|
16
|
+
|
17
|
+
return parts.join(join || '');
|
18
|
+
}
|
19
|
+
|
20
|
+
var classize = can.classize,
|
21
|
+
proto = can.Observe.prototype,
|
22
|
+
old = proto.__set;
|
23
|
+
|
24
|
+
proto.__set = function(prop, value, current, success, error){
|
25
|
+
// check if there's a setter
|
26
|
+
var cap = classize(prop),
|
27
|
+
setName = "set" + cap,
|
28
|
+
errorCallback = function( errors ) {
|
29
|
+
var stub = error && error.call(self, errors);
|
30
|
+
|
31
|
+
// if 'setter' is on the page it will trigger
|
32
|
+
// the error itself and we dont want to trigger
|
33
|
+
// the event twice. :)
|
34
|
+
if(stub !== false){
|
35
|
+
can.trigger(self, "error", [prop, errors], true);
|
36
|
+
}
|
37
|
+
|
38
|
+
return false;
|
39
|
+
},
|
40
|
+
self = this;
|
41
|
+
|
42
|
+
// if we have a setter
|
43
|
+
if ( this[setName] &&
|
44
|
+
// call the setter, if returned value is undefined,
|
45
|
+
// this means the setter is async so we
|
46
|
+
// do not call update property and return right away
|
47
|
+
( value = this[setName](value,
|
48
|
+
function(){ old.call(self,prop, value, current, success, errorCallback) },
|
49
|
+
errorCallback ) ) === undefined ) {
|
50
|
+
return;
|
51
|
+
}
|
52
|
+
|
53
|
+
old.call(self,prop, value, current, success, errorCallback);
|
54
|
+
|
55
|
+
return this;
|
56
|
+
};
|
57
|
+
|
58
|
+
})(this.can, this )
|
@@ -0,0 +1,374 @@
|
|
1
|
+
(function(can, window, undefined){
|
2
|
+
//validations object is by property. You can have validations that
|
3
|
+
//span properties, but this way we know which ones to run.
|
4
|
+
// proc should return true if there's an error or the error message
|
5
|
+
var validate = function(attrNames, options, proc) {
|
6
|
+
// normalize argumetns
|
7
|
+
if(!proc){
|
8
|
+
proc = options;
|
9
|
+
options = {};
|
10
|
+
}
|
11
|
+
|
12
|
+
options = options || {};
|
13
|
+
attrNames = can.makeArray(attrNames)
|
14
|
+
|
15
|
+
// run testIf if it exists
|
16
|
+
if(options.testIf && !options.testIf.call(this)){
|
17
|
+
return;
|
18
|
+
}
|
19
|
+
|
20
|
+
var self = this;
|
21
|
+
can.each(attrNames, function(attrName) {
|
22
|
+
// Add a test function for each attribute
|
23
|
+
if(!self.validations[attrName]){
|
24
|
+
self.validations[attrName] = [];
|
25
|
+
}
|
26
|
+
|
27
|
+
self.validations[attrName].push(function(newVal){
|
28
|
+
// if options has a message return that, otherwise, return the error
|
29
|
+
var res = proc.call(this, newVal, attrName);
|
30
|
+
return res === undefined ? undefined : (options.message || res);
|
31
|
+
})
|
32
|
+
});
|
33
|
+
};
|
34
|
+
|
35
|
+
var old = can.Observe.prototype.__set;
|
36
|
+
can.Observe.prototype.__set = function(prop, value, current, success, error){
|
37
|
+
var self = this,
|
38
|
+
validations = self.constructor.validations,
|
39
|
+
errorCallback = function( errors ) {
|
40
|
+
var stub = error && error.call(self, errors);
|
41
|
+
|
42
|
+
// if 'setter' is on the page it will trigger
|
43
|
+
// the error itself and we dont want to trigger
|
44
|
+
// the event twice. :)
|
45
|
+
if(stub !== false){
|
46
|
+
can.trigger(self, "error", [prop, errors], true);
|
47
|
+
}
|
48
|
+
|
49
|
+
return false;
|
50
|
+
};
|
51
|
+
|
52
|
+
old.call(self, prop, value, current, success, errorCallback);
|
53
|
+
|
54
|
+
if (validations && validations[prop]){
|
55
|
+
var errors = self.errors(prop);
|
56
|
+
errors && errorCallback(errors)
|
57
|
+
}
|
58
|
+
|
59
|
+
return this;
|
60
|
+
}
|
61
|
+
|
62
|
+
can.each([ can.Observe, can.Model ], function(clss){
|
63
|
+
// in some cases model might not be defined quite yet.
|
64
|
+
if(clss === undefined){
|
65
|
+
return;
|
66
|
+
}
|
67
|
+
var oldSetup = clss.setup;
|
68
|
+
|
69
|
+
can.extend(clss, {
|
70
|
+
setup : function(superClass){
|
71
|
+
oldSetup.apply(this, arguments);
|
72
|
+
if (!this.validations || superClass.validations === this.validations) {
|
73
|
+
this.validations = {};
|
74
|
+
}
|
75
|
+
},
|
76
|
+
/**
|
77
|
+
* @function can.Observe.static.validate
|
78
|
+
* @parent can.Observe.validations
|
79
|
+
* `validate(attrNames, [options,] validateProc(value, attrName) )` validates each of the
|
80
|
+
* specified attributes with the given `validateProc` function. The function
|
81
|
+
* should return a value if there is an error. By default, the return value is
|
82
|
+
* the error message. Validations should be set in the Constructor's static init method.
|
83
|
+
*
|
84
|
+
* The following example validates that a person's age is a number:
|
85
|
+
*
|
86
|
+
* Person = can.Observe({
|
87
|
+
* init : function(){
|
88
|
+
* this.validate(["age"], function(val){
|
89
|
+
* if( typeof val === 'number' ){
|
90
|
+
* return "must be a number"
|
91
|
+
* }
|
92
|
+
* })
|
93
|
+
* }
|
94
|
+
* },{})
|
95
|
+
*
|
96
|
+
*
|
97
|
+
* The error message can be overwritten with `options` __message__ property:
|
98
|
+
*
|
99
|
+
* Person = can.Observe({
|
100
|
+
* init : function(){
|
101
|
+
* this.validate(
|
102
|
+
* "age",
|
103
|
+
* {message: "must be a number"},
|
104
|
+
* function(val){
|
105
|
+
* if( typeof val === 'number' ){
|
106
|
+
* return true
|
107
|
+
* }
|
108
|
+
* })
|
109
|
+
* }
|
110
|
+
* },{})
|
111
|
+
*
|
112
|
+
* @param {Array|String} attrNames Attribute name(s) to to validate
|
113
|
+
*
|
114
|
+
* @param {Object} [options] Options for the
|
115
|
+
* validations. Valid options include 'message' and 'testIf'.
|
116
|
+
*
|
117
|
+
* @param {Function} validateProc(value,attrName) Function used to validate each
|
118
|
+
* given attribute. Returns nothing if valid and an error message
|
119
|
+
* otherwise. Function is called in the instance context and takes the
|
120
|
+
* `value` and `attrName` to validate.
|
121
|
+
*
|
122
|
+
*/
|
123
|
+
validate: validate,
|
124
|
+
|
125
|
+
/**
|
126
|
+
* @attribute can.Observe.static.validationMessages
|
127
|
+
* @parent can.Observe.validations
|
128
|
+
*
|
129
|
+
* `validationMessages` has the default validation error messages that will be returned by the builtin
|
130
|
+
* validation methods. These can be overwritten by assigning new messages
|
131
|
+
* to `can.Observe.validationMessages` in your application setup.
|
132
|
+
*
|
133
|
+
* The following messages (with defaults) are available:
|
134
|
+
*
|
135
|
+
* * format - "is invalid"
|
136
|
+
* * inclusion - "is not a valid option (perhaps out of range)"
|
137
|
+
* * lengthShort - "is too short"
|
138
|
+
* * lengthLong - "is too long"
|
139
|
+
* * presence - "can't be empty"
|
140
|
+
* * range - "is out of range"
|
141
|
+
*
|
142
|
+
* It is important to steal can/observe/validations before
|
143
|
+
* overwriting the messages, otherwise the changes will
|
144
|
+
* be lost once steal loads it later.
|
145
|
+
*
|
146
|
+
* ## Example
|
147
|
+
*
|
148
|
+
* can.Observe.validationMessages.format = "is invalid dummy!"
|
149
|
+
*/
|
150
|
+
validationMessages : {
|
151
|
+
format : "is invalid",
|
152
|
+
inclusion : "is not a valid option (perhaps out of range)",
|
153
|
+
lengthShort : "is too short",
|
154
|
+
lengthLong : "is too long",
|
155
|
+
presence : "can't be empty",
|
156
|
+
range : "is out of range"
|
157
|
+
},
|
158
|
+
|
159
|
+
/**
|
160
|
+
* @function can.Observe.static.validateFormatOf
|
161
|
+
* @parent can.Observe.validations
|
162
|
+
*
|
163
|
+
* `validateFormatOf(attrNames, regexp, options)` validates where the values of
|
164
|
+
* specified attributes are of the correct form by
|
165
|
+
* matching it against the regular expression provided.
|
166
|
+
*
|
167
|
+
* init : function(){
|
168
|
+
* this.validateFormatOf(["email"],/[\w\.]+@]w+\.\w+/,{
|
169
|
+
* message : "invalid email"
|
170
|
+
* })
|
171
|
+
* }
|
172
|
+
*
|
173
|
+
* @param {Array|String} attrNames Attribute name(s) to to validate
|
174
|
+
* @param {RegExp} regexp Regular expression used to match for validation
|
175
|
+
* @param {Object} [options] Options for the validations. Valid options include 'message' and 'testIf'.
|
176
|
+
*/
|
177
|
+
validateFormatOf: function(attrNames, regexp, options) {
|
178
|
+
validate.call(this, attrNames, options, function(value) {
|
179
|
+
if( (typeof value != 'undefined' && value != '')
|
180
|
+
&& String(value).match(regexp) == null ) {
|
181
|
+
return this.constructor.validationMessages.format;
|
182
|
+
}
|
183
|
+
});
|
184
|
+
},
|
185
|
+
|
186
|
+
/**
|
187
|
+
* @function can.Observe.static.validateInclusionOf
|
188
|
+
* @parent can.Observe.validations
|
189
|
+
*
|
190
|
+
* Validates whether the values of the specified attributes are available in a particular
|
191
|
+
* array.
|
192
|
+
*
|
193
|
+
* init : function(){
|
194
|
+
* this.validateInclusionOf(["salutation"],["Mr.","Mrs.","Dr."])
|
195
|
+
* }
|
196
|
+
*
|
197
|
+
* @param {Array|String} attrNames Attribute name(s) to to validate
|
198
|
+
* @param {Array} inArray Array of options to test for inclusion
|
199
|
+
* @param {Object} [options] Options for the validations. Valid options include 'message' and 'testIf'.
|
200
|
+
*/
|
201
|
+
validateInclusionOf: function(attrNames, inArray, options) {
|
202
|
+
validate.call(this, attrNames, options, function(value) {
|
203
|
+
if(typeof value == 'undefined'){
|
204
|
+
return;
|
205
|
+
}
|
206
|
+
|
207
|
+
if(can.grep(inArray, function(elm) { return (elm == value); }).length == 0){
|
208
|
+
return this.constructor.validationMessages.inclusion;
|
209
|
+
}
|
210
|
+
});
|
211
|
+
},
|
212
|
+
|
213
|
+
/**
|
214
|
+
* @function can.Observe.static.validateLengthOf
|
215
|
+
* @parent can.Observe.validations
|
216
|
+
*
|
217
|
+
* Validates that the specified attributes' lengths are in the given range.
|
218
|
+
*
|
219
|
+
* init : function(){
|
220
|
+
* this.validateInclusionOf(["suffix"],3,5)
|
221
|
+
* }
|
222
|
+
*
|
223
|
+
* @param {Number} min Minimum length (inclusive)
|
224
|
+
* @param {Number} max Maximum length (inclusive)
|
225
|
+
* @param {Object} [options] Options for the validations. Valid options include 'message' and 'testIf'.
|
226
|
+
*/
|
227
|
+
validateLengthOf: function(attrNames, min, max, options) {
|
228
|
+
validate.call(this, attrNames, options, function(value) {
|
229
|
+
if((typeof value == 'undefined' && min > 0) || value.length < min){
|
230
|
+
return this.constructor.validationMessages.lengthShort + " (min=" + min + ")";
|
231
|
+
} else if(typeof value != 'undefined' && value.length > max){
|
232
|
+
return this.constructor.validationMessages.lengthLong + " (max=" + max + ")";
|
233
|
+
}
|
234
|
+
});
|
235
|
+
},
|
236
|
+
|
237
|
+
/**
|
238
|
+
* @function can.Observe.static.validatePresenceOf
|
239
|
+
* @parent can.Observe.validations
|
240
|
+
*
|
241
|
+
* Validates that the specified attributes are not blank.
|
242
|
+
*
|
243
|
+
* init : function(){
|
244
|
+
* this.validatePresenceOf(["name"])
|
245
|
+
* }
|
246
|
+
*
|
247
|
+
* @param {Array|String} attrNames Attribute name(s) to to validate
|
248
|
+
* @param {Object} [options] Options for the validations. Valid options include 'message' and 'testIf'.
|
249
|
+
*/
|
250
|
+
validatePresenceOf: function(attrNames, options) {
|
251
|
+
validate.call(this, attrNames, options, function(value) {
|
252
|
+
if(typeof value == 'undefined' || value == "" || value === null){
|
253
|
+
return this.constructor.validationMessages.presence;
|
254
|
+
}
|
255
|
+
});
|
256
|
+
},
|
257
|
+
|
258
|
+
/**
|
259
|
+
* @function can.Observe.static.validateRangeOf
|
260
|
+
* @parent can.Observe.validations
|
261
|
+
*
|
262
|
+
* Validates that the specified attributes are in the given numeric range.
|
263
|
+
*
|
264
|
+
* init : function(){
|
265
|
+
* this.validateRangeOf(["age"],21, 130);
|
266
|
+
* }
|
267
|
+
*
|
268
|
+
* @param {Array|String} attrNames Attribute name(s) to to validate
|
269
|
+
* @param {Number} low Minimum value (inclusive)
|
270
|
+
* @param {Number} hi Maximum value (inclusive)
|
271
|
+
* @param {Object} [options] (optional) Options for the validations. Valid options include 'message' and 'testIf'.
|
272
|
+
*/
|
273
|
+
validateRangeOf: function(attrNames, low, hi, options) {
|
274
|
+
validate.call(this, attrNames, options, function(value) {
|
275
|
+
if(typeof value != 'undefined' && value < low || value > hi){
|
276
|
+
return this.constructor.validationMessages.range + " [" + low + "," + hi + "]";
|
277
|
+
}
|
278
|
+
});
|
279
|
+
}
|
280
|
+
});
|
281
|
+
});
|
282
|
+
|
283
|
+
can.extend(can.Observe.prototype, {
|
284
|
+
|
285
|
+
/**
|
286
|
+
* @function can.Observe.prototype.errors
|
287
|
+
* @parent can.Observe.validations
|
288
|
+
*
|
289
|
+
* Runs the validations on this observe. You can
|
290
|
+
* also pass it an array of attributes to run only those attributes.
|
291
|
+
* It returns nothing if there are no errors, or an object
|
292
|
+
* of errors by attribute.
|
293
|
+
*
|
294
|
+
* To use validations, it's suggested you use the
|
295
|
+
* observe/validations plugin.
|
296
|
+
*
|
297
|
+
* can.Observe("Task",{
|
298
|
+
* init : function(){
|
299
|
+
* this.validatePresenceOf("dueDate")
|
300
|
+
* }
|
301
|
+
* },{});
|
302
|
+
*
|
303
|
+
* var task = new Task(),
|
304
|
+
* errors = task.errors()
|
305
|
+
*
|
306
|
+
* errors.dueDate[0] //-> "can't be empty"
|
307
|
+
*
|
308
|
+
* @param {Array|String} [attrs] An optional list of attributes to get errors for:
|
309
|
+
*
|
310
|
+
* task.errors(['dueDate','name']);
|
311
|
+
*
|
312
|
+
* Or it can take a single attr name like:
|
313
|
+
*
|
314
|
+
* task.errors('dueDate')
|
315
|
+
*
|
316
|
+
* @param {Object} [newVal] An optional new value to test setting
|
317
|
+
* on the observe. If `newVal` is provided,
|
318
|
+
* it returns the errors on the observe if `newVal` was set.
|
319
|
+
*
|
320
|
+
* @return {Object} an object of attributeName : [errors] like:
|
321
|
+
*
|
322
|
+
* task.errors() // -> {dueDate: ["cant' be empty"]}
|
323
|
+
*
|
324
|
+
* or `null` if there are no errors.
|
325
|
+
*/
|
326
|
+
errors: function( attrs , newVal) {
|
327
|
+
// convert attrs to an array
|
328
|
+
if ( attrs ) {
|
329
|
+
attrs = can.isArray(attrs) ? attrs : [attrs];
|
330
|
+
}
|
331
|
+
|
332
|
+
var errors = {},
|
333
|
+
self = this,
|
334
|
+
attr,
|
335
|
+
// helper function that adds error messages to errors object
|
336
|
+
// attr - the name of the attribute
|
337
|
+
// funcs - the validation functions
|
338
|
+
addErrors = function( attr, funcs ) {
|
339
|
+
can.each(funcs, function( func ) {
|
340
|
+
var res = func.call(self, isTest ? ( self.__convert ?
|
341
|
+
self.__convert(attr,newVal) :
|
342
|
+
newVal ): self[attr]);
|
343
|
+
if ( res ) {
|
344
|
+
if (!errors[attr] ) {
|
345
|
+
errors[attr] = [];
|
346
|
+
}
|
347
|
+
errors[attr].push(res);
|
348
|
+
}
|
349
|
+
|
350
|
+
});
|
351
|
+
},
|
352
|
+
validations = this.constructor.validations,
|
353
|
+
isTest = attrs && attrs.length === 1 && arguments.length === 2;
|
354
|
+
|
355
|
+
// go through each attribute or validation and
|
356
|
+
// add any errors
|
357
|
+
can.each(attrs || validations || {}, function( funcs, attr ) {
|
358
|
+
// if we are iterating through an array, use funcs
|
359
|
+
// as the attr name
|
360
|
+
if ( typeof attr == 'number' ) {
|
361
|
+
attr = funcs;
|
362
|
+
funcs = validations[attr];
|
363
|
+
}
|
364
|
+
// add errors to the
|
365
|
+
addErrors(attr, funcs || []);
|
366
|
+
});
|
367
|
+
|
368
|
+
// return errors as long as we have one
|
369
|
+
return can.isEmptyObject(errors) ? null : isTest ? errors[attrs[0]] : errors;
|
370
|
+
}
|
371
|
+
|
372
|
+
});
|
373
|
+
|
374
|
+
})(this.can, this )
|