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.
@@ -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 )