source: Dev/branches/rest-dojo-ui/client/dojox/validate/check.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 12.5 KB
Line 
1define(["dojo/_base/kernel", "dojo/_base/lang", "./_base"],
2 function(kernel, lang, validate){
3kernel.experimental("dojox.validate.check");
4
5/*=====
6
7        validate = dojox.validate;
8
9=====*/
10/**
11        FIXME: How much does this overlap with dojox.form.Manager and friends?
12
13        Procedural API Description
14
15                The main aim is to make input validation expressible in a simple format.
16                You define profiles which declare the required and optional fields and any constraints they might have.
17                The results are provided as an object that makes it easy to handle missing and invalid input.
18
19        Usage
20
21                var results = dojox.validate.check(form, profile);
22
23        Profile Object
24
25                var profile = {
26                        // filters change the field value and are applied before validation.
27                        trim: ["tx1", "tx2"],
28                        uppercase: ["tx9"],
29                        lowercase: ["tx5", "tx6", "tx7"],
30                        ucfirst: ["tx10"],
31                        digit: ["tx11"],
32
33                        // required input fields that are blank will be reported missing.
34                        // required radio button groups and drop-down lists with no selection will be reported missing.
35                        // checkbox groups and selectboxes can be required to have more than one value selected.
36                        // List required fields by name and use this notation to require more than one value: {checkboxgroup: 2}, {selectboxname: 3}.
37                        required: ["tx7", "tx8", "pw1", "ta1", "rb1", "rb2", "cb3", "s1", {"doubledip":2}, {"tripledip":3}],
38
39                        // dependant/conditional fields are required if the target field is present and not blank.
40                        // At present only textbox, password, and textarea fields are supported.
41                        dependencies:   {
42                                cc_exp: "cc_no",
43                                cc_type: "cc_no"
44                        },
45
46                        // Fields can be validated using any boolean valued function.
47                        // Use arrays to specify parameters in addition to the field value.
48                        constraints: {
49                                field_name1: myValidationFunction,
50                                field_name2: dojox.validate.isInteger,
51                                field_name3: [myValidationFunction, additional parameters],
52                                field_name4: [dojox.validate.isValidDate, "YYYY.MM.DD"],
53                                field_name5: [dojox.validate.isEmailAddress, false, true]
54                        },
55
56                        // Confirm is a sort of conditional validation.
57                        // It associates each field in its property list with another field whose value should be equal.
58                        // If the values are not equal, the field in the property list is reported as Invalid. Unless the target field is blank.
59                        confirm: {
60                                email_confirm: "email",
61                                pw2: "pw1"
62                        }
63                };
64
65        Results Object
66
67                isSuccessful(): Returns true if there were no invalid or missing fields, else it returns false.
68                hasMissing():  Returns true if the results contain any missing fields.
69                getMissing():  Returns a list of required fields that have values missing.
70                isMissing(field):  Returns true if the field is required and the value is missing.
71                hasInvalid():  Returns true if the results contain fields with invalid data.
72                getInvalid():  Returns a list of fields that have invalid values.
73                isInvalid(field):  Returns true if the field has an invalid value.
74
75*/
76
77validate.check = function(/*HTMLFormElement*/form, /*Object*/profile){
78        // summary: validates user input of an HTML form based on input profile
79        //
80        // description:
81        //      returns an object that contains several methods summarizing the results of the validation
82        //
83        // form: form to be validated
84        // profile: specifies how the form fields are to be validated
85        // {trim:Array, uppercase:Array, lowercase:Array, ucfirst:Array, digit:Array,
86        //      required:Array, dependencies:Object, constraints:Object, confirm:Object}
87
88        // Essentially private properties of results object
89        var missing = [];
90        var invalid = [];
91
92        // results object summarizes the validation
93        var results = {
94                isSuccessful: function() {return ( !this.hasInvalid() && !this.hasMissing() );},
95                hasMissing: function() {return ( missing.length > 0 );},
96                getMissing: function() {return missing;},
97                isMissing: function(elemname) {
98                        for(var i = 0; i < missing.length; i++){
99                                if(elemname == missing[i]){ return true; }
100                        }
101                        return false;
102                },
103                hasInvalid: function() {return ( invalid.length > 0 );},
104                getInvalid: function() {return invalid;},
105                isInvalid: function(elemname){
106                        for(var i = 0; i < invalid.length; i++){
107                                if(elemname == invalid[i]){ return true; }
108                        }
109                        return false;
110                }
111        };
112
113        var _undef = function(name,object){
114                return (typeof object[name] == "undefined");
115        };
116
117        // Filters are applied before fields are validated.
118        // Trim removes white space at the front and end of the fields.
119        if(profile.trim instanceof Array){
120                for(var i = 0; i < profile.trim.length; i++){
121                        var elem = form[profile.trim[i]];
122                        if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
123                        elem.value = elem.value.replace(/(^\s*|\s*$)/g, "");
124                }
125        }
126        // Convert to uppercase
127        if(profile.uppercase instanceof Array){
128                for(var i = 0; i < profile.uppercase.length; i++){
129                        var elem = form[profile.uppercase[i]];
130                        if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
131                        elem.value = elem.value.toUpperCase();
132                }
133        }
134        // Convert to lowercase
135        if(profile.lowercase instanceof Array){
136                for (var i = 0; i < profile.lowercase.length; i++){
137                        var elem = form[profile.lowercase[i]];
138                        if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
139                        elem.value = elem.value.toLowerCase();
140                }
141        }
142        // Uppercase first letter
143        if(profile.ucfirst instanceof Array){
144                for(var i = 0; i < profile.ucfirst.length; i++){
145                        var elem = form[profile.ucfirst[i]];
146                        if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
147                        elem.value = elem.value.replace(/\b\w+\b/g, function(word) { return word.substring(0,1).toUpperCase() + word.substring(1).toLowerCase(); });
148                }
149        }
150        // Remove non digits characters from the input.
151        if(profile.digit instanceof Array){
152                for(var i = 0; i < profile.digit.length; i++){
153                        var elem = form[profile.digit[i]];
154                        if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
155                        elem.value = elem.value.replace(/\D/g, "");
156                }
157        }
158
159        // See if required input fields have values missing.
160        if(profile.required instanceof Array){
161                for(var i = 0; i < profile.required.length; i++){
162                        if(!lang.isString(profile.required[i])){ continue; }
163                        var elem = form[profile.required[i]];
164                        // Are textbox, textarea, or password fields blank.
165                        if(!_undef("type", elem)
166                                && (elem.type == "text" || elem.type == "textarea" || elem.type == "password" || elem.type == "file")
167                                && /^\s*$/.test(elem.value)){
168                                missing[missing.length] = elem.name;
169                        }
170                        // Does drop-down box have option selected.
171                        else if(!_undef("type", elem) && (elem.type == "select-one" || elem.type == "select-multiple")
172                                                && (elem.selectedIndex == -1
173                                                || /^\s*$/.test(elem.options[elem.selectedIndex].value))){
174                                missing[missing.length] = elem.name;
175                        }
176                        // Does radio button group (or check box group) have option checked.
177                        else if(elem instanceof Array){
178                                var checked = false;
179                                for(var j = 0; j < elem.length; j++){
180                                        if (elem[j].checked) { checked = true; }
181                                }
182                                if(!checked){
183                                        missing[missing.length] = elem[0].name;
184                                }
185                        }
186                }
187        }
188
189        // See if checkbox groups and select boxes have x number of required values.
190        if(profile.required instanceof Array){
191                for (var i = 0; i < profile.required.length; i++){
192                        if(!lang.isObject(profile.required[i])){ continue; }
193                        var elem, numRequired;
194                        for(var name in profile.required[i]){
195                                elem = form[name];
196                                numRequired = profile.required[i][name];
197                        }
198                        // case 1: elem is a check box group
199                        if(elem instanceof Array){
200                                var checked = 0;
201                                for(var j = 0; j < elem.length; j++){
202                                        if(elem[j].checked){ checked++; }
203                                }
204                                if(checked < numRequired){
205                                        missing[missing.length] = elem[0].name;
206                                }
207                        }
208                        // case 2: elem is a select box
209                        else if(!_undef("type", elem) && elem.type == "select-multiple" ){
210                                var selected = 0;
211                                for(var j = 0; j < elem.options.length; j++){
212                                        if (elem.options[j].selected && !/^\s*$/.test(elem.options[j].value)) { selected++; }
213                                }
214                                if(selected < numRequired){
215                                        missing[missing.length] = elem.name;
216                                }
217                        }
218                }
219        }
220
221        // Dependent fields are required when the target field is present (not blank).
222        // Todo: Support dependent and target fields that are radio button groups, or select drop-down lists.
223        // Todo: Make the dependency based on a specific value of the target field.
224        // Todo: allow dependent fields to have several required values, like {checkboxgroup: 3}.
225        if(lang.isObject(profile.dependencies)){
226                // properties of dependencies object are the names of dependent fields to be checked
227                for(name in profile.dependencies){
228                        var elem = form[name];  // the dependent element
229                        if(_undef("type", elem)){continue;}
230                        if(elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; } // limited support
231                        if(/\S+/.test(elem.value)){ continue; } // has a value already
232                        if(results.isMissing(elem.name)){ continue; }   // already listed as missing
233                        var target = form[profile.dependencies[name]];
234                        if(target.type != "text" && target.type != "textarea" && target.type != "password"){ continue; }        // limited support
235                        if(/^\s*$/.test(target.value)){ continue; }     // skip if blank
236                        missing[missing.length] = elem.name;    // ok the dependent field is missing
237                }
238        }
239
240        // Find invalid input fields.
241        if(lang.isObject(profile.constraints)){
242                // constraint properties are the names of fields to bevalidated
243                for(name in profile.constraints){
244                        var elem = form[name];
245                        if(!elem) {continue;}
246                       
247                        // skip if blank - its optional unless required, in which case it
248                        // is already listed as missing.
249                        if(!_undef("tagName",elem)
250                                && (elem.tagName.toLowerCase().indexOf("input") >= 0
251                                        || elem.tagName.toLowerCase().indexOf("textarea") >= 0)
252                                && /^\s*$/.test(elem.value)){
253                                continue;
254                        }
255                       
256                        var isValid = true;
257                        // case 1: constraint value is validation function
258                        if(lang.isFunction(profile.constraints[name])){
259                                isValid = profile.constraints[name](elem.value);
260                        }else if(lang.isArray(profile.constraints[name])){
261                               
262                                // handle nested arrays for multiple constraints
263                                if(lang.isArray(profile.constraints[name][0])){
264                                        for (var i=0; i<profile.constraints[name].length; i++){
265                                                isValid = validate.evaluateConstraint(profile, profile.constraints[name][i], name, elem);
266                                                if(!isValid){ break; }
267                                        }
268                                }else{
269                                        // case 2: constraint value is array, first elem is function,
270                                        // tail is parameters
271                                        isValid = validate.evaluateConstraint(profile, profile.constraints[name], name, elem);
272                                }
273                        }
274                       
275                        if(!isValid){
276                                invalid[invalid.length] = elem.name;
277                        }
278                }
279        }
280
281        // Find unequal confirm fields and report them as Invalid.
282        if(lang.isObject(profile.confirm)){
283                for(name in profile.confirm){
284                        var elem = form[name];  // the confirm element
285                        var target = form[profile.confirm[name]];
286                        if (_undef("type", elem) || _undef("type", target) || (elem.type != "text" && elem.type != "textarea" && elem.type != "password")
287                                ||(target.type != elem.type)
288                                ||(target.value == elem.value)  // it's valid
289                                ||(results.isInvalid(elem.name))// already listed as invalid
290                                ||(/^\s*$/.test(target.value))) // skip if blank - only confirm if target has a value
291                        {
292                                continue;
293                        }
294                        invalid[invalid.length] = elem.name;
295                }
296        }
297        return results; // Object
298};
299
300//TODO: evaluateConstraint doesn't use profile or fieldName args?
301validate.evaluateConstraint=function(profile, /*Array*/constraint, fieldName, elem){
302        // summary:
303        //      Evaluates dojo.validate.check() constraints that are specified as array
304        //      arguments
305        //
306        // description: The arrays are expected to be in the format of:
307        //      constraints:{
308        //              fieldName: [functionToCall, param1, param2, etc.],
309        //              fieldName: [[functionToCallFirst, param1],[functionToCallSecond,param2]]
310        //      }
311        //
312        //  This function evaluates a single array function in the format of:
313        //      [functionName, argument1, argument2, etc]
314        //
315        //  The function will be parsed out and evaluated against the incoming parameters.
316        //
317        // profile: The dojo.validate.check() profile that this evaluation is against.
318        // constraint: The single [] array of function and arguments for the function.
319        // fieldName: The form dom name of the field being validated.
320        // elem: The form element field.
321       
322        var isValidSomething = constraint[0];
323        var params = constraint.slice(1);
324        params.unshift(elem.value);
325        if(typeof isValidSomething != "undefined"){
326                return isValidSomething.apply(null, params);
327        }
328        return false; // Boolean
329};
330
331return validate.check;
332});
Note: See TracBrowser for help on using the repository browser.