Opened 12 years ago

Closed 12 years ago

#3463 closed defect (fixed)

[dojox.validate] validate.check doesn't validate checkbox groups

Reported by: guest Owned by: tk
Priority: high Milestone:
Component: Dojox Version: 0.4.2rc1
Keywords: validate checkbox Cc:
Blocked By: Blocking:

Description

If you define a checkbox group as a required field for dojo.validate.check it always validates as filled in.

In the /tests/validate/test_validate.js file, the form is generated programatically, and it works in this case. But, if you make a real form, it fails. In looking through the source, I noticed that it is checking to see if the form element is "instanceof Array". This validates to false for checkbox groups. Instead, the dojo.lang.isArrayLike function should be used.

Attachments (1)

test_validate.html (1.8 KB) - added by guest 12 years ago.

Download all attachments as: .zip

Change History (8)

Changed 12 years ago by guest

Attachment: test_validate.html added

comment:1 Changed 12 years ago by Adam Peller

Owner: changed from dylan to dante

comment:2 Changed 12 years ago by Adam Peller

Component: ValidateDojox

comment:3 Changed 12 years ago by dante

Summary: validate.check doesn't validate checkbox groups[dojox.validate] validate.check doesn't validate checkbox groups

comment:4 Changed 12 years ago by tk

Is this even valid for DojoX?

I would test it but... the documentation isnt that great incode on how to do it... there appears to be a .check test in the test files... but I dont see it being ran looking at the test objectives on the left...

-Karl

comment:5 Changed 12 years ago by tk

The above test file fails in 0.9 at this check...

	// See if required input fields have values missing.
	if(profile.required instanceof Array){

		for(var i = 0; i < profile.required.length; i++){ 
			if(!dojo.isString(profile.required[i])){ continue; }
			var elem = form[profile.required[i]];
			// Are textbox, textarea, or password fields blank.

			//breaks here.... object expected due to the cb being an array of objects....
			if(!dj_undef("type", elem) 
				&& (elem.type == "text" || elem.type == "textarea" || elem.type == "password" || elem.type == "file") 
				&& /^s*$/.test(elem.value)){	
				missing[missing.length] = elem.name;
			}
			// Does drop-down box have option selected.
			else if(!dj_undef("type", elem) && (elem.type == "select-one" || elem.type == "select-multiple") 
						&& (elem.selectedIndex == -1 
						|| /^s*$/.test(elem.options[elem.selectedIndex].value))){
				missing[missing.length] = elem.name;
			}
			// Does radio button group (or check box group) have option checked.
			else if(elem instanceof Array){

				var checked = false;
				for(var j = 0; j < elem.length; j++){
					if (elem[j].checked) { checked = true; }
				}
				if(!checked){	
					missing[missing.length] = elem[0].name;
				}
			}
		}
	}

I'm not sure if this was changed when Dante ported it or not (didnt have time to check) but this fails to to the following profile object

			var f = {
				cb: [ {
						type: "checkbox",
						value: "v0",
						name: "cb",
						checked: false
					},
					{
						type: "checkbox",
						value: "v1",
						name: "cb",
						checked: false
					},
					{
						type: "checkbox",
						value: "v2",
						name: "cb",
						checked: false
					} ]
				};
			var profile = { required: ["cb"] };

and the CB item being an array of objects... is this profile object built incorrectly then or do we need to rewrite the entire way that these checks are completed to allow for arrays of objects or single objects depending?

-Karl

comment:6 Changed 12 years ago by tk

Owner: changed from dante to tk
Status: newassigned

Rewrite of dojox.validate.check function, problem came from dj_undef not existing... again, will patch this when I get home if nobody beats me to it. Maybe we should consider reimp'ing dj_undef... its a great short hand and would reduce code size in many places I think...

dojox.validate.check = function(/*HTMLFormElement*/form, /*Object*/profile){
	// summary: validates user input of an HTML form based on input profile
	//
	// description:
	//	returns an object that contains several methods summarizing the results of the validation
	//
	// form: form to be validated
	// profile: specifies how the form fields are to be validated
	// {trim:Array, uppercase:Array, lowercase:Array, ucfirst:Array, digit:Array,
	//	required:Array, dependencies:Object, constraints:Object, confirm:Object}

	// Port of dj_undef to fix ticket #3463
	var _undef = function(/*String*/ name, /*Object?*/ object) {
		//summary: Returns true if 'name' is defined on 'object' (or globally if 'object' is null).
		//description: Note that 'defined' and 'exists' are not the same concept.
		return (typeof(object[name]) == "undefined");	// Boolean
	};

	// Essentially private properties of results object
	var missing = [];
	var invalid = [];
	// results object summarizes the validation
	var results = {
		isSuccessful: function() {return ( !this.hasInvalid() && !this.hasMissing() );},
		hasMissing: function() {return ( missing.length > 0 );},
		getMissing: function() {return missing;},
		isMissing: function(elemname) {
			for(var i = 0; i < missing.length; i++){
				if(elemname == missing[i]){ return true; }
			}
			return false;
		},
		hasInvalid: function() {return ( invalid.length > 0 );},
		getInvalid: function() {return invalid;},
		isInvalid: function(elemname){
			for(var i = 0; i < invalid.length; i++){
				if(elemname == invalid[i]){ return true; }
			}
			return false;
		}
	};

	// Filters are applied before fields are validated.
	// Trim removes white space at the front and end of the fields.
	if(profile.trim instanceof Array){
		for(var i = 0; i < profile.trim.length; i++){
			var elem = form[profile.trim[i]];
			if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
			elem.value = elem.value.replace(/(^s*|s*$)/g, "");
		}
	}

	// Convert to uppercase
	if(profile.uppercase instanceof Array){
		for(var i = 0; i < profile.uppercase.length; i++){
			var elem = form[profile.uppercase[i]];
			if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
			elem.value = elem.value.toUpperCase();
		}
	}

	// Convert to lowercase
	if(profile.lowercase instanceof Array){
		for (var i = 0; i < profile.lowercase.length; i++){
			var elem = form[profile.lowercase[i]];
			if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
			elem.value = elem.value.toLowerCase();
		}
	}

	// Uppercase first letter
	if(profile.ucfirst instanceof Array){
		for(var i = 0; i < profile.ucfirst.length; i++){
			var elem = form[profile.ucfirst[i]];
			if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
			elem.value = elem.value.replace(/w+/g, function(word) { return word.substring(0,1).toUpperCase() + word.substring(1).toLowerCase(); });
		}
	}

	// Remove non digits characters from the input.
	if(profile.digit instanceof Array){
		for(var i = 0; i < profile.digit.length; i++){
			var elem = form[profile.digit[i]];
			if(_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
			elem.value = elem.value.replace(/D/g, "");
		}
	}

	// See if required input fields have values missing.
	if(profile.required instanceof Array){

		for(var i = 0; i < profile.required.length; i++){ 
			if(!dojo.isString(profile.required[i])){ continue; }
			var elem = form[profile.required[i]];
			// Are textbox, textarea, or password fields blank.
			//breaks here.... object expected due to the cb being an array of objects....
			if(!_undef("type", elem) 
				&& (elem.type == "text" || elem.type == "textarea" || elem.type == "password" || elem.type == "file") 
				&& /^s*$/.test(elem.value)){	
				missing[missing.length] = elem.name;
			}
			// Does drop-down box have option selected.
			else if(!_undef("type", elem) && (elem.type == "select-one" || elem.type == "select-multiple") 
						&& (elem.selectedIndex == -1 
						|| /^s*$/.test(elem.options[elem.selectedIndex].value))){
				missing[missing.length] = elem.name;
			}
			// Does radio button group (or check box group) have option checked.
			else if(dojo.isArrayLike(elem)){

				var checked = false;
				for(var j = 0; j < elem.length; j++){
					if (elem[j].checked) { checked = true; }
				}
				if(!checked){	
					missing[missing.length] = elem[0].name;
				}
			}
		}
	}
	// See if checkbox groups and select boxes have x number of required values.
	if(profile.required instanceof Array){
		for (var i = 0; i < profile.required.length; i++){ 
			if(!dojo.isObject(profile.required[i])){ continue; }
			var elem, numRequired;
			for(var name in profile.required[i]){ 
				elem = form[name]; 
				numRequired = profile.required[i][name];
			}
			// case 1: elem is a check box group
			if(dojo.isArrayLike(elem)){
				var checked = 0;
				for(var j = 0; j < elem.length; j++){
					if(elem[j].checked){ checked++; }
				}
				if(checked < numRequired){	
					missing[missing.length] = elem[0].name;
				}
			}
			// case 2: elem is a select box
			else if(!_undef("type", elem) && elem.type == "select-multiple" ){
				var selected = 0;
				for(var j = 0; j < elem.options.length; j++){
					if (elem.options[j].selected && !/^s*$/.test(elem.options[j].value)) { selected++; }
				}
				if(selected < numRequired){	
					missing[missing.length] = elem.name;
				}
			}
		}
	}
	// Dependent fields are required when the target field is present (not blank).
	// Todo: Support dependent and target fields that are radio button groups, or select drop-down lists.
	// Todo: Make the dependency based on a specific value of the target field.
	// Todo: allow dependent fields to have several required values, like {checkboxgroup: 3}.
	if(dojo.isObject(profile.dependencies)){
		// properties of dependencies object are the names of dependent fields to be checked
		for(name in profile.dependencies){
			var elem = form[name];	// the dependent element
			if(_undef("type", elem)){continue;}
			if(elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; } // limited support
			if(/S+/.test(elem.value)){ continue; }	// has a value already
			if(results.isMissing(elem.name)){ continue; }	// already listed as missing
			var target = form[profile.dependencies[name]];
			if(target.type != "text" && target.type != "textarea" && target.type != "password"){ continue; }	// limited support
			if(/^s*$/.test(target.value)){ continue; }	// skip if blank
			missing[missing.length] = elem.name;	// ok the dependent field is missing
		}
	}
	// Find invalid input fields.
	if(dojo.isObject(profile.constraints)){
		// constraint properties are the names of fields to bevalidated
		for(name in profile.constraints){
			var elem = form[name];
			if(!elem) {continue;}
			
			// skip if blank - its optional unless required, in which case it
			// is already listed as missing.
			if(!_undef("tagName",elem) 
				&& (elem.tagName.toLowerCase().indexOf("input") >= 0
					|| elem.tagName.toLowerCase().indexOf("textarea") >= 0) 
				&& /^s*$/.test(elem.value)){ 
				continue; 
			}
			
			var isValid = true;
			// case 1: constraint value is validation function
			if(dojo.isFunction(profile.constraints[name])){
				isValid = profile.constraints[name](elem.value);
			}else if(dojo.isArray(profile.constraints[name])){
				
				// handle nested arrays for multiple constraints
				if(dojo.isArray(profile.constraints[name][0])){
					for (var i=0; i<profile.constraints[name].length; i++){
						isValid = dojox.validate.evaluateConstraint(profile, profile.constraints[name][i], name, elem);
						if(!isValid){ break; }
					}
				}else{
					// case 2: constraint value is array, first elem is function,
					// tail is parameters
					isValid = dojox.validate.evaluateConstraint(profile, profile.constraints[name], name, elem);
				}
			}
			
			if(!isValid){	
				invalid[invalid.length] = elem.name;
			}
		}
	}

	// Find unequal confirm fields and report them as Invalid.
	if(dojo.isObject(profile.confirm)){
		for(name in profile.confirm){
			var elem = form[name];	// the confirm element
			var target = form[profile.confirm[name]];
			if (_undef("type", elem) || _undef("type", target) || (elem.type != "text" && elem.type != "textarea" && elem.type != "password") 
				||(target.type != elem.type)
				||(target.value == elem.value)	// it's valid
				||(results.isInvalid(elem.name))// already listed as invalid
				||(/^s*$/.test(target.value)))	// skip if blank - only confirm if target has a value
			{
				continue; 
			}
			invalid[invalid.length] = elem.name;
		}
	}
	return results; // Object
};

comment:7 Changed 12 years ago by Karl Tiedt

Resolution: fixed
Status: assignedclosed

(In [10804]) just added check for _undef to actually check of a property of the object exists...

confirmed that it fixes #3463

Note: See TracTickets for help on using tickets.