ClientServer Validation

Derek Jones edited this page Jul 5, 2012 · 13 revisions
Clone this wiki locally

Category:Core | Category:Core::Community | Category:Core::Validation

Information

Implementing client-side and server-side validation usually requires a significant duplication of effort. Despite following the same logic rules, the code usually must be developed in two different environments - e.g. javascript and php.

For those who are too lazy to do the same work twice (like I am), here is a crude prototype of a validation library (subclass of built-in Validation class) that would do this work for you.

Usage

The usage is simple:

  1. Add a method to your controller, where you set up your validation rules.
  2. Call this method twice per form life-cycle: once before rendering the form (html), and again after it has been submitted.

Example

  1. Set up validation:

    function _setup_validation()
    {
        $this->load->library('validation');
        
        $fields = array(
            'email'=>'Email',
            'password'=>'Password',
            'password2'=>'Confirm Password',
            'firstname' => 'First Name',
            'lastname' => 'Last Name',
            'address1'=>'Address 1',
            'city'=>'City',
            'zipcode'=>'Zip'
        );
        
        $rules = array(
            'email'=>'required|valid_email',
            'password'=>'min_length[5]',
            'password2'=>'required|matches[password]',
            'firstname' => 'required',
            'lastname' => 'required',
            'address1'=>'required',
            'city'=>'min_length[2]',
            'zipcode'=>'exact_length[5]'
        );
        
        $this->validation->set_fields($fields);
        $this->validation->set_rules($rules);
    }
  1. When rendering the form, define your validation rules and then call special function from our Validation (sub)class to generate a corresponding js script:

    function index()
    {
        $this->_setup_validation();
                
        $data['javascript'] = $this->validation->javascript();
        $this->load->view('register', $data);
    }
  1. In your view, use 'javascript' variable to inject the contents of our validation (js) script:

<html>
    <head>
        <title>Account Registration</title>
        <?= @$javascript ?>
    </head>
    ...
  1. Once the form has been submitted, process it as usual:

    function submit()
    {
        $this->_setup_validation();
        
        if ($this->validation->run() == false)
            return $this->index();

        $this->load->view('sucess');
    }

Here's MY_Validation.php in its entirety:


<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

// ------------------------------------------------------------------------

/**
 * Validation Class extension
 *
 * @package        CodeIgniter
 * @subpackage    Libraries
 * @category    Validation
 * @author        AK
 * @link        http://codeigniter.com/user_guide/libraries/validation.html
 */

    /**
     * Set Fields
     *
     * This function takes an array of field names as input
     * and generates class variables with the same name, which will
     * either be blank or contain the $_POST value corresponding to it
     *
     * @access    public
     * @param    string
     * @param    string
     * @return    void
     */
class MY_Validation extends CI_Validation {

    function MY_Validation()
    {
        parent::CI_Validation();
    }



    /**
     * JavaScript
     *
     * This function provides default implementation of the built-in CI validation rules in javascript.
     * The function generates a client-side js script, complete with <script>...</script> html tags, 
     * suitable for inclusion in the document header. Additionally, custom rules can be added by 
     * defining global js functions, or extending Validation js object.
     *
     * @access    public
     * @param    string - custom error message (optional)
     * @param    string - name of a js error callback function (optional) 
     * @return    string - js
     */
     
    function javascript($alert_msg='', $alert_func='null')
    {
        if(!$this->_fields || !$this->_rules)
            return '<script type="text/javascript">function validation_run(f) {}</script>';
    
    
        // client-side javascript implementation of CI built-in validation rules.
        $script = '
<script type="text/javascript">
String.prototype.trim = function() { return this.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); }
var Validator = function(f) { this.form = f; }
Validator.prototype.required = function(str) { return str.search(/\S/) > -1; }
Validator.prototype.matches = function(str, field) { return (str == this.form.elements[field].value); } // FIX! change "field" from input name to input ref?

Validator.prototype.max_length = function(str, val) { return (str.length <= val); }
Validator.prototype.min_length = function(str, val) { return (str.length >= val); }
Validator.prototype.exact_length = function(str, val) { return (str.length == val); }
Validator.prototype.valid_email = function(str) { return str.search(/^([\w\+\-]+)(\.[\w\+\-]+)*@([a-z\d\-]+\.)+[a-z]{2,6}$/i) > -1; }
Validator.prototype.valid_ip = function(ip) { var segments = ip.split("."); for (var i in segs) if(segs[i].length>3 || segs[i]>255 || segs[i].search(/\D/)>-1) return false; return true; }
Validator.prototype.alpha = function(str) { return str.search(/[^a-z]/i) == -1; }
Validator.prototype.alpha_numeric = function(str) { return str.search(/[^a-z0-9]/i) == -1; }
Validator.prototype.alpha_dash = function(str) { return str.search(/[^\w-]/i) == -1; }
Validator.prototype.numeric = function(str) { return ! isNaN(str); }
Validator.prototype.integer = function(str) { return ! (isNaN(str) || str.indexOf(".") > -1); }
Validator.prototype.valid_base64 = function(str) { return str.search(/[^a-zA-Z0-9\/\+=]/) == -1; }
Validator.prototype.validate = function (rules, callback) { try {
    if (!rules.length) return true;    
    var res, errors=[];
    for (var i in rules) {
        var item = rules[i];
        var field = this.form.elements[item.input];    
        var rule_list = item.rule.split("|");

        for (var r in rule_list) {
            var re = /(callback_|validate_)?(\w+)(?:\[(.+)\])?/i.exec&#40;rule_list[r]&#41;;
            var func = re[2];
            //if (!re[1]) re[1] = "";
            if (!this[func]) {
                try { func = eval&#40;func&#41;; } catch (e2) { } 
                res = (typeof(func) == "function") ? func(field.value, re[3]) : false;
            } else {
                res = this[func](field.value, re[3]);
            }
        }
        if (!res && item.msg) {
            errors.push([item.msg, item.input]);
        }
    } } catch (e) { alert&#40;e&#41;; }    
    if (errors.length) {
        // show errors
        return callback ? callback(errors) : display_alert&#40;errors&#41;;
    }
    return true;
}
';
        // default alert message
        if ($alert_msg == '')
            $alert_msg = 'Please fix the following errors:';


        // default implementation of the validation action
        $script .= '
function display_alert&#40;errors&#41; {
    var str = "";
    for (var i in errors)
        str += "\n- " + errors[i][0];
    alert&#40;"'. addslashes($alert_msg&#41; .'" + str);
    return false;
}
';
        // Load the language file containing error messages
        $this->CI->lang->load('validation');
        
        $params = null;

        foreach ($this->_rules as $field => $rules)
        {        
            //Explode out the rules!
            $ex = explode('|', $rules);
            $messages = array();
            
            foreach ($ex as $rule)
            {
                $param = FALSE;
                if (preg_match("/(.*?)\[(.*?)\]/", $rule, $match))
                {
                    $rule    = $match[1];
                    $param    = $match[2];
                }
                
                if ( ! isset($this->_error_messages[$rule]))
                {
                    if (FALSE === ($line = $this->CI->lang->line($rule)))
                    {
                        $line = 'Unable to access an error message corresponding to your field name.';
                    }                        
                }
                else
                {
                    $line = $this->_error_messages[$rule];
                }
                
                // Build the error message
                $mfield = ( ! isset($this->_fields[$field])) ? $field : $this->_fields[$field];
                $mparam = ( ! isset($this->_fields[$param])) ? $param : $this->_fields[$param];
                $messages[] = sprintf($line, $mfield, $mparam);
            }
            

                    
            $params[] = '{input:"'.$field.'",rule:"'.$rules.'",name:"'
                    .( isset($this->_fields[$field]) ? addslashes($this->_fields[$field]) : $field ).'",msg:"'.join(' ', $messages).'"}';
        }
        
        $script .= "\nfunction validation_run(f) {\n\tvar rules = [\n\t\t" . join(",\n\t\t", $params) . "\n\t];\n\treturn new Validator(f).validate(rules,". $alert_func .");\n}\n&lt;/script&gt;";

        return $script;
    }
}

?&gt;