1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('system/string');
  9 
 10 SC.VALIDATE_OK = YES;
 11 SC.VALIDATE_NO_CHANGE = NO;
 12 
 13 /**
 14   @class
 15   
 16   Validators provide a way for you to implement simple form field validation
 17   and transformation.  To use a validator, simply name the validator in the
 18   "validate" attribute in your text field.  For example, if you want to
 19   validate a field using the PhoneNumberValidator use this:
 20 
 21       <input value="1234567890" validate="phone-number" />
 22 
 23   Validators get notified at three points.  You can implement one or all
 24   of these methods to support validation.  All of the validate methods except
 25   for validateKeypress behave the same way.  You are passed a form, field,
 26   and possibly the oldValue.  You are expected to return Validator.OK or
 27   an error string.  Inside this method you typically do one of all of the
 28   following:
 29 
 30   1. You can simply validate the field value and return OK or an error str
 31   2. You can modify the field value (for example, you could format the
 32      string to match some predefined format).
 33   3. If you need to roundtrip the server first to perform validation, you can
 34      return Validator.OK, then save the form and field info until after the
 35      roundtrip.  On return, if there is a problem, first verify the field
 36      value has not changed and then call form.errorFor(field,str) ;
 37 
 38   @extends SC.Object
 39   @since SproutCore 1.0
 40 */
 41 SC.Validator = SC.Object.extend(
 42 /** @scope SC.Validator.prototype */ {
 43 
 44   // ..........................................
 45   // OBJECT VALUE CONVERSION
 46   //
 47   // The following methods are used to convert the string value of a field
 48   // to and from an object value.  The default implementations return
 49   // the string, but you can override this to provide specific behaviors. 
 50   // For example, you might add or remove a dollar sign or convert the 
 51   // value to a number.
 52   
 53 /**
 54   Returns the value to set in the field for the passed object value.  
 55   
 56   The form and view to be set MAY (but will not always) be passed also.  You
 57   should override this method to help convert an input object into a value
 58   that can be displayed by the field.  For example, you might convert a 
 59   date to a property formatted string or a number to a properly formatted
 60   value.
 61   
 62   @param {Object} object The object to transform
 63   @param {SC.FormView} form The form this field belongs to. (optional)
 64   @param {SC.View} view The view the value is required for.
 65   @returns {Object} a value (usually a string) suitable for display
 66 */
 67   fieldValueForObject: function(object, form, view) { return object; },
 68   
 69   /**
 70     Returns the object value for the passed string.
 71     
 72     The form and view MAY (but wil not always) be passed also.  You should
 73     override this method to convert a field value, such as string, into an
 74     object value suitable for consumption by the rest of the app.  For example
 75     you may convert a string into a date or a number.
 76     
 77     @param {String} value the field value.  (Usually a String).
 78     @param {SC.FormView} form The form this field belongs to. (optional)
 79     @param {SC.View} view The view this value was pulled from.
 80     @returns {Object} an object suitable for consumption by the app.
 81   */
 82   objectForFieldValue: function(value, form, view) { return value; },
 83   
 84   // ..........................................
 85   // VALIDATION PRIMITIVES
 86   //
 87 
 88   /**
 89     Validate the field value.  
 90     
 91     You can implement standard behavior for your validator by using the validate()
 92     and validateError() methods.  validate() should return NO if the field is not
 93     valid, YES otherwise.  If you return NO from this method, then the validateError()
 94     method will be called so you can generate an error object describing the specific problem.
 95 
 96     @param {SC.FormView} form the form this view belongs to
 97     @param {SC.View} field the field to validate.  Responds to fieldValue.
 98     @returns {Boolean} YES if field is valid.
 99   */
100   validate: function(form, field) { return true; },
101 
102   /**
103     Returns an error object if the field is invalid.
104   
105     This is the other standard validator method that can be used to implement basic validation.
106     Return an error object explaining why the field is not valid.  It will only be called if
107     validate() returned NO.
108     
109     The default implementation of this method returns a generic error message with the loc
110     string "Invalid.Generate({fieldValue})".  You can simply define this loc string in
111     strings.js if you prefer or you can override this method to provide a more specific error message.
112   
113     @param {SC.FormView} form the form this view belongs to
114     @param {SC.View} field the field to validate.  Responds to fieldValue.
115     @returns {SC.Error} an error object
116   */
117   validateError: function(form, field) { 
118     return SC.$error(
119       SC.String.loc("Invalid.General(%@)", field.get('fieldValue')),
120       field.get('fieldKey')) ; 
121   },
122 
123   // ..........................................
124   // VALIDATION API
125   //
126 
127   /**
128     Invoked just before the user ends editing of the field.
129 
130     This is a primitive validation method.  You can implement the two higher-level
131     methods (validate() and validateError()) if you prefer.
132     
133     The default implementation calls your validate() method and then validateError()
134     if validate() returns NO.  This method should return SC.VALIDATE_OK if validation
135     succeeded or an error object if it fails.
136   
137     @param {SC.FormView} form the form for the field
138     @param {SC.View} field the field to validate
139     @param {Object} oldValue: the value of the field before the change
140 
141     @returns SC.VALIDATE_OK or an error object.
142   
143   */
144   validateChange: function(form, field, oldValue) { 
145     return this.validate(form,field) ? SC.VALIDATE_OK : this.validateError(form, field);
146   },
147 
148   /**
149     Invoked just before the form is submitted.
150   
151     This method gives your validators one last chance to perform validation
152     on the form as a whole.  The default version does the same thing as the 
153     validateChange() method.
154   
155     @param {SC.FormView} form the form for the field
156     @param {SC.View} field the field to validate
157 
158     @returns SC.VALIDATE_OK or an error object.
159   
160   */  
161   validateSubmit: function(form, field) { 
162     return this.validate(form,field) ? SC.VALIDATE_OK : this.validateError(form, field);
163   },
164 
165   /**
166     Invoked 1ms after the user types a key (if a change is allowed).  
167   
168     You can use this validate the new partial string and return an error if 
169     needed. The default will validate a partial only if there was already an 
170     error. This allows the user to try to get it right before you bug them.
171   
172     Unlike the other methods, you should return SC.VALIDATE_NO_CHANGE if you
173     did not actually validate the partial string.  If you return 
174     SC.VALIDATE_OK then any showing errors will be hidden.
175   
176     @param {SC.FormView} form the form for the field
177     @param {SC.View} field the field to validate
178 
179     @returns SC.VALIDATE_OK, SC.VALIDATE_NO_CHANGE or an error object.
180   */  
181   validatePartial: function(form, field) { 
182     if (!field.get('isValid')) {
183       return this.validate(form,field) ? SC.VALIDATE_OK : this.validateError(form, field);
184     } else return SC.VALIDATE_NO_CHANGE ;
185   },
186   
187   /**
188     Invoked when the user presses a key.  
189   
190     This method is used to restrict the letters and numbers the user is 
191     allowed to enter.  You should not use this method to perform full 
192     validation on the field.  Instead use validatePartial().
193   
194     @param {SC.FormView} form the form for the field
195     @param {SC.View} field the field to validate
196     @param {String} char the characters being added
197     
198     @returns {Boolean} YES if allowed, NO otherwise
199   */
200   validateKeyDown: function(form, field,charStr) { return true; },
201 
202   // .....................................
203   // OTHER METHODS
204 
205   /**
206     Called on all validators when they are attached to a field.  
207   
208     You can use this to do any setup that you need.  The default does nothing.
209     
210     @param {SC.FormView} form the form for the field
211     @param {SC.View} field the field to validate
212   */
213   attachTo: function(form,field) { },
214 
215   /**
216     Called on a validator just before it is removed from a field.  You can 
217     tear down any setup you did for the attachTo() method.
218     
219     @param {SC.FormView} form the form for the field
220     @param {SC.View} field the field to validate
221   */
222   detachFrom: function(form, field) {}
223 
224 }) ;
225 
226 SC.Validator.mixin(/** @scope SC.Validator */ {
227 
228   /**
229     Return value when validation was performed and value is OK.
230   */
231   OK: true, 
232   
233   /**
234     Return value when validation was not performed.
235   */
236   NO_CHANGE: false,  
237 
238   /**
239     Invoked by a field whenever a validator is attached to the field.
240     
241     The passed validatorKey can be a validator instance, a validator class
242     or a string naming a validator. To make your validator
243     visible, you should name your validator under the SC.Validator base.
244     for example SC.Validator.Number would get used for the 'number' 
245     validator key.
246   
247     This understands validatorKey strings in the following format:
248 
249     * 'key' or 'multiple_words' will find validators Key and MultipleWords
250 
251     * if you want to share a single validator among multiple fields (for
252       example to validate that two passwords are the same) set a name inside
253       brackets. i.e. 'password[pwd]'.
254 
255     @param {SC.FormView} form the form for the field
256     @param {SC.View} field the field to validate
257     @param {Object} validatorKey the key to validate
258     
259     @returns {SC.Validator} validator instance or null
260   */  
261   findFor: function(form,field, validatorKey) {
262     
263     // Convert the validator into a validator instance.
264     var validator ;
265     if (!validatorKey) return ; // nothing to do...
266     
267     if (validatorKey instanceof SC.Validator) {
268       validator = validatorKey ;
269     } else if (validatorKey.isClass) {
270       validator = validatorKey.create() ;
271       
272     } else if (SC.typeOf(validatorKey) === SC.T_STRING) {
273 
274       // extract optional key name
275       var name = null ;
276       var m = validatorKey.match(/^(.+)\[(.*)\]/) ;
277       if (m) {
278         validatorKey = m[1] ; name = m[2]; 
279       }
280       
281       // convert the validatorKey name into a class.
282       validatorKey = SC.String.classify(validatorKey);
283       var validatorClass = SC.Validator[validatorKey] ;
284       if (SC.none(validatorClass)) {
285         throw new Error("validator %@ not found for %@".fmt(validatorKey, field));
286       } else if (name) {
287 
288         // if a key was also passed, then find the validator in the list of
289         // validators for the form.  Otherwise, just create a new instance.
290         if (!form) {
291           throw new Error("named validator (%@) could not be found for field %@ because the field does not belong to a form".fmt(name,field));
292         }
293         
294         if (!form._validatorHash) form._validatorHash = {} ;
295         validator = (name) ? form._validatorHash[name] : null ;
296         if (!validator) validator = validatorClass.create() ;
297         if (name) form._validatorHash[name] = validator ;
298       } else validator = validatorClass.create() ;
299     } 
300     
301     return validator ;
302   },
303   
304   /**
305     Convenience class method to call the fieldValueForObject() instance
306     method you define in your subclass.
307   */
308   fieldValueForObject: function(object, form, field) {
309     if (this.prototype && this.prototype.fieldValueForObject) {
310       return this.prototype.fieldValueForObject(object,form,field) ;
311     }
312     else return null ;
313   },
314   
315   /**
316     Convenience class method to call the objectForFieldValue() instance
317     method you define in your subclass.
318   */
319   objectForFieldValue: function(value, form, field) {
320     if (this.prototype && this.prototype.objectForFieldValue) {
321       return this.prototype.objectForFieldValue(value,form,field) ;
322     }
323     else return null ;
324   }
325   
326 });
327