SitePoint Sponsor

User Tag List

Results 1 to 8 of 8
  1. #1
    SitePoint Guru tictike's Avatar
    Join Date
    Apr 2008
    Location
    Canada
    Posts
    863
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    more elegant ways to validate forms

    The last time I validated a form I looped through all the elements looking for a particular class name and if found I performed the appropriate validation. Take this snippet for example:

    Code JavaScript:
    if(/req/.test(elements[i].className) && (elements[i].value == "" || /^\s+$/.test(elements[i].value))){
    			elements[i].className += " required";
    			elements[i].value = "This field is required."
    			return false;
    		}
    		if(/nonum/.test(elements[i].className) && /\d+/.test(elements[i].value)){
    			elements[i].className += " required";
    			alert("A name cannot contain numbers. Please enter again.");
    			return false;
    		}

    I'm just looking for ideas of more elegant techniques of form validation when writing code from scratch.

  2. #2
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,702
    Mentioned
    101 Post(s)
    Tagged
    4 Thread(s)
    I've just whipped up the following, based on some good practices that are going around.

    Code javascript:
    var form = document.getElementById('myForm');
    form.onsubmit = validate;
    function validate() {
        var form = this;
        form.validates = true;
        form.validates = validateRequired(form);
        return form.validates;
    }
    function validateRequired(form) {
        var validates = form.validates;
        var els = getElementsByClassName(form, 'required');
        Array.forEach(els, function (el) {
            if (isEmpty(el.value)) {
                addError(el, 'This field is required.');
                validates = false;
            } else {
                removeError(el);
            }
        });
        return validates;
    }

    Now some of that needs to be expanded on, such as the isEmpty function, and the addError and removeError functions

    Code javascript:
    function isEmpty(text) {
        if (text === '') {
            return true;
        }
        if (/^\s+$/.test(text)) {
            return true;
        }
        return false;
    }
    function addError(el, message) {
        var error = document.createElement('span');
        if (!el.error) {
            var error = document.createElement('span');
            el.error = error;
    	appendSibling(el, error);
        }
        updateText(error, message);
        addClass(el, 'error');
        addClass(error, 'error');
    }
    function removeError(el) {
            removeClass(el, 'error');
        if (el.error) {
            el.parentNode.removeChild(el.error);
            el.error = null;
        }
    }
    function appendSibling(el, newEl) {
        if (el.nextSibling) {
            el.parentNode.insertBefore(newEl, el.nextSibling);
        } else {
            el.parentNode.appendChild(newEl, el);
        }
    }
    function updateText(el, text) {
        while (el.hasChildNodes()) {
            el.removeChild(el.firstChild);
        }
        el.appendChild(document.createTextNode(text));
    }

    And then finally there are the common functions for browser compatibility and other related code, such as getElementsByClassName, the Array.forEach method, and the class handling functions.

    Code javascript:
    function getElementsByClassName(node, classname) {
    	if (node.getElementsByClassName) {
    		return node.getElementsByClassName(classname);
    		} else {
    		return getElementsByClass(classname, node);
    	}
    }
    function getElementsByClass(searchClass, node, tag) {
    	node = node || document;
    	tag = tag || '*';
    	var classElements = [],
    	    els = node.getElementsByTagName(tag),
    	    elsLen = els.length,
    	    pattern = new RegExp("(^|\\s)" + searchClass + "(\\s|$)");
    	for (i = 0; i < elsLen; i += 1) {
    		if (pattern.test(els[i].className)) {
    			classElements.push(els[i]);
    		}
    	}
    	return classElements;
    }

    Code javascript:
    if (!Array.prototype.forEach)
    {
      Array.prototype.forEach = function(fun /*, thisp*/)
      {
        var len = this.length;
        if (typeof fun != "function")
          throw new TypeError();
     
        var thisp = arguments[1];
        for (var i = 0; i < len; i++)
        {
          if (i in this)
            fun.call(thisp, this[i], i, this);
        }
      };
    }

    Code javascript:
    function hasClass(ele,cls) {
        return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
    }
     
    function addClass(ele,cls) {
        if (!this.hasClass(ele,cls)) ele.className += " "+cls;
    }
     
    function removeClass(ele,cls) {
        if (hasClass(ele,cls)) {
            var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
            ele.className=ele.className.replace(reg,' ');
        }
    }
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  3. #3
    SitePoint Guru tictike's Avatar
    Join Date
    Apr 2008
    Location
    Canada
    Posts
    863
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    based on some good practices that are going around.
    What are the good practices? For example is it best practices to search for a specific class name and apply the proper validation to fields with that class name?
    Last edited by tictike; Nov 4, 2008 at 23:27.

  4. #4
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,702
    Mentioned
    101 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by tictike View Post
    What are the good practices? For example is it best practices to search for a specific class name and apply the proper validation to fields with that class name?
    That's one of them. The other way of doing it is to obtain all of the form elements and then process them for each of the types of validation.

    By doing it based on the class name we're performing all of the similar validation checks at the same time, which can allow the interpreter to more easily cache certain parts of the process.

    The functions are kept quite small, which combined with well names functions helps to ensure that you can tell what's going on at a glance.

    Instead of using a for loop to process the array, the array forEach method is used. If it's an actual array then you can use someArray.forEach(fn)
    In this particular case it's not an actual array but instead an array-like collection, so it can be processed just as easily by using Array.forEach(someArray, fn)
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  5. #5
    SitePoint Guru tictike's Avatar
    Join Date
    Apr 2008
    Location
    Canada
    Posts
    863
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    In one of the examples above you're using Array.prototype. What is prototype?

    In some Douglas Crockford videos he says that javascript is a prototypal language and if you can learn to think like that you'll be a better js programmer. what does that mean?

  6. #6
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,702
    Mentioned
    101 Post(s)
    Tagged
    4 Thread(s)
    The Array object is the construct from which all arrays are created, and similarly for the Function, String, Number, Boolean, and Date objects. All of these are specialisations of an Object object.

    The compatibility code for the Array forEach method comes from https://developer.mozilla.org/En/Cor...:Array:forEach

    Any object can have methods added on to them individually, but if you want a method to be accessible from newly created objects, then through the prototype property you to add and/or modify the default methods that each object of that type has.

    In this way it's possible to create a trim method that can be applied to any string

    Code javascript:
    String.prototype.trim = function () {
        var text = this;
        // trim text in some way
        return text;
    }
    ...
    var aString = ' Some words  ';
    var newString = aString.trim(); // "Some words"

    Because the prototype methods are available to all scripts, you must be very careful when extending any well known methods because other scripts than your own will also use those new methods, with dire consequences if the expected behaviour differs from the expected.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  7. #7
    Function Curry'er JimmyP's Avatar
    Join Date
    Aug 2007
    Location
    Brighton, UK
    Posts
    2,006
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pmw57 View Post
    Because the prototype methods are available to all scripts, you must be very careful when extending any well known methods because other scripts than your own will also use those new methods, with dire consequences if the expected behaviour differs from the expected.
    Hence why I'm against the Prototype library...
    James Padolsey
    末末末末末末末末末末末末末末末末末末末
    Awesome JavaScript Zoomer (demo here)
    'Ajaxy' - Ajax integration solution (demo here)

  8. #8
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,702
    Mentioned
    101 Post(s)
    Tagged
    4 Thread(s)
    A refinement to allow for multiple error handling (always important) is to remove all existing errors before validation.

    Code javascript:
    function validate() {
        var form = this;
        form.validates = true;
        removeErrors(form);
        form.validates = validateRequired(form);
        form.validates = validateNonNumeric(form);
        return form.validates;
    }
    ...
    function removeErrors(form) {
        Array.forEach(form.elements, function (el) {
            if (el.error) {
                removeError(el.error);
            }
            el.error = null;
        });
    }
    function removeError(el) {
        removeClass(el, 'error');
        el.parentNode.removeChild(el);
    }

    So that later validations don't cause potential problems by inadvertently removing other error messages.

    Code javascript:
    function validateRequired(form) {
        var validates = form.validates;
        var els = getElementsByClassName(form, 'required');
        Array.forEach(els, function (el) {
            if (isEmpty(el.value)) {
                addError(el, 'This field is required.', form);
                validates = false;
            }
        });
        return validates;
    }

    As I was adding the validateNonNumeric function it was easy to see that it did basically the same thing as the validateRequired function, so it's possible to modify the validate function so that information about each validation is stored in an array, so that only the one validation function is used, which helps to reduce the scope for problems to occur.

    Code javascript:
    function validate() {
        var form = this;
        removeErrors(form);
        var validations = [
            {'className': 'required', 'fn': isEmpty,
             'error': 'This field is required.'},
            {'className': 'nonNumeric', 'fn': hasNumbers,
             'error': 'A name cannot contain numbers. Please enter again.'}
        ];
        return performValidations(validations, form);
    }
    function performValidations(validations, form) {
        var validates = true;
            validations.forEach(function (validation) {
    	var els = getElementsByClassName(form, validation.className);
            Array.forEach(els, function (el) {
                if (validation.fn(el.value)) {
                    addError(el, validation.error, form);
                    validates = false;
                }
            });
        });
        return validates;
    }

    This may prove to be a more flexible solution.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •