Creating custom validation rules

Webform

Let's say you have written a custom PHP validator for the Webform Validation module that looks like this (assuming your custom module is named potpourri):

potpourri.module

/**
 * Implementation of hook_webform_validation_validators().
 */
function potpourri_webform_validation_validators() {
  return array(
    'phonefield' => array(
      'name' => "Phonefield",
      'component_types' => array(
        'textfield',
      ),
            'description' => 'Check phone number'
    )
  );
}

/**
 * Implementation of hook_webform_validation_validate().
 */
function potpourri_webform_validation_validate($validator_name, $items, $components, $rule) {
  if ($items) {
        $errors = array();
    switch ($validator_name) {
      case 'phonefield':
                foreach ($items as $key => $val) {
          if ($val && (!isTelefon($val))) {
            $errors[$key] = t("%value is not a telephone.", array('%value' => $val));
          }
        }
        return $errors;
        break;
    }
  }
}

/**
 * Validation callback function.
 */
function isTelefon($text) {
    return preg_match('/^(\+){0,1}(\([0-9]+\))?[0-9_\-\/]{6,}$/', str_replace(' ', '', $text));
}

This would be enough if you want just server side validation. If you want to support Clientside Validation there are a few more things you need to do.

First, you need to implement hook_clientside_validation_rule_alter(). In this example it would look like this:

potpourri.module

/**
 * Implementation of hook_clientside_validation_rule_alter().
 */
function potpourri_clientside_validation_rule_alter (&$js_rules, $element, $context) {
  switch ($context['type']) {
    case 'webform':
      if ($context['rule']['validator'] == 'phonefield') {
        drupal_add_js(drupal_get_path('module', 'potpourri') . '/phone_validator.js');
        $title = variable_get('clientside_validation_prefix', '') . $element['element_title'] . variable_get('clientside_validation_suffix', '');
        $js_rules[$element['element_name']]['phoneField'] = TRUE;
        $js_rules[$element['element_name']]['messages']['phoneField'] = theme('clientside_error', array('message' => 'Field !name must be a valid phone number.', 'placeholders' => array('!name' => $title)));
      }
      break;
  }
}

Finally, you need to create the phone_validator.js file in the map of you module. In this file you have to write the javascript validation:

phone_validator.js for Drupal 7

//jQuery wrapper
(function ($) {
  //Define a Drupal behaviour with a custom name
  Drupal.behaviors.potpourriAddPhoneValidator = {
    attach: function (context) {
      //Add an eventlistener to the document reacting on the
      //'clientsideValidationAddCustomRules' event.
      $(document).bind('clientsideValidationAddCustomRules', function(event){
        //Add your custom method with the 'addMethod' function of jQuery.validator
        //http://docs.jquery.com/Plugins/Validation/Validator/addMethod#namemethodmessage
        jQuery.validator.addMethod("phoneField", function(value, element, param) {
          //return true if valid, false if invalid
          var regexp = /^(\+){0,1}(\([0-9]+\))?[0-9_\-\/]{6,}$/
          return regexp.test(value.replace(' ', ''));
          //Enter a default error message.
        }, jQuery.format('Value must a valid phone number'));
      });
    }
  }
})(jQuery);

phone_validator.js for Drupal 6

//Define a Drupal behaviour with a custom name
Drupal.behaviors.potpourriAddPhoneValidator = {
  attach: function (context) {
    //Add an eventlistener to the document reacting on the
    //'clientsideValidationAddCustomRules' event.
    $(document).bind('clientsideValidationAddCustomRules', function(event){
      //Add your custom method with the 'addMethod' function of jQuery.validator
      //http://docs.jquery.com/Plugins/Validation/Validator/addMethod#namemethodmessage
      jQuery.validator.addMethod("phoneField", function(value, element, param) {
        //return true if valid, false if invalid
        var regexp = /^(\+){0,1}(\([0-9]+\))?[0-9_\-\/]{6,}$/
        return regexp.test(value.replace(/\s/g, ''));
        //Enter a default error message.
      }, jQuery.format('Value must a valid phone number'));
    });
  }
}

Note that the first parameter to the addMethod function is the same as the array key used in potpourri_clientside_validation_rule_alter.

And that's how you create a custom validator for Webform Validation!

Form API Validation

For the Form API Validation module it's basically the same principle.
So, again, let's say you have written a custom PHP validator for the Form API Validation module that looks like this (again assuming your custom module is named potpourri):

potpourri.module

/**
 * Implements hook_fapi_validation_rules().
 */
function potpourri_fapi_validation_rules() {
  return array(
    'check_empty' => array(
      'callback' => 'potpourri_validation_check_empty',
      'error_msg' => t('%field cannot be empty'),
    ),
  );
}

/**
 * Validation callback function.
 */
function potpourri_validation_check_empty($value) {
  return !empty($value);
}

Now, if you want to support Clientside Validation you need to implement hook_clientside_validation_rule_alter(). In this example it would look like this:

/**
 * Implements hook_clientside_validation_rule_alter()
 */
function potpourri_clientside_validation_rule_alter(&$js_rules, $element, $context) {
  switch ($context['type']) {
    case 'fapi':
      if ($context['rule']['callback'] == 'potpourri_validation_check_empty') {
        _potpourri_set_not_empty($element['#name'], $element['#title'], $js_rules);
      }
      break;
  }
}

function _potpourri_set_not_empty($name, $title, &$js_rules){
  drupal_add_js(drupal_get_path('module', 'potpourri') . '/potpourri_validation.js');
  $title = variable_get('clientside_validation_prefix', '') . $title . variable_get('clientside_validation_suffix', '');
  $js_rules[$name]['notEmpty'] = TRUE;
  $js_rules[$name]['messages']['notEmpty'] = t('Must Enter a value in !field.', array('!field' => $title));
}

Finally, you need to create the potpourri_validation.js file in the map of you module. In this file you have to write the javascript validation:

potpourri_validation.js for Drupal 7

//jQuery wrapper
(function ($) {
  //Define a Drupal behaviour with a custom name
  Drupal.behaviors.potpourriAddNotEmptyValidator = {
    attach: function (context) {
      //Add an eventlistener to the document reacting on the
      //'clientsideValidationAddCustomRules' event.
      $(document).bind('clientsideValidationAddCustomRules', function(event){
        //Add your custom method with the 'addMethod' function of jQuery.validator
        //http://docs.jquery.com/Plugins/Validation/Validator/addMethod#namemethodmessage
        jQuery.validator.addMethod("notEmpty", function(value, element, param) {
          return value !== '';
        }, jQuery.format('Field can not be empty'));
      });
    }
  }
})(jQuery);

potpourri_validation.js for Drupal 6

//Define a Drupal behaviour with a custom name
Drupal.behaviors.potpourriAddNotEmptyValidator = function (context) {
  //Add an eventlistener to the document reacting on the
  //'clientsideValidationAddCustomRules' event.
  $(document).bind('clientsideValidationAddCustomRules', function(event){
    //Add your custom method with the 'addMethod' function of jQuery.validator
    //http://docs.jquery.com/Plugins/Validation/Validator/addMethod#namemethodmessage
    jQuery.validator.addMethod("notEmpty", function(value, element, param) {
      return value !== '';
    }, jQuery.format('Field can not be empty'));
  });
}

And that's how you create a custom validator for Form API Validation!

Validation for #element_validate

If you have written a custom PHP validator for Form API's #element_validate that looks like this (assuming your custom module is named potpourri):

potpourri.module

/**
 * Implements hook_menu().
 */
function potpourri_menu() {
  return array(
    'potpourri_form' => array(
      'title' => t('Test Clientside Validation'),
      'page callback' => 'drupal_get_form',
      'page arguments' => array('potpourri_form'),
      'access arguments' => array('access content'),
    ),
  );
}

/**
 * Form callback.
 */
function potpourri_form() {
  $form = array();
  $form['container'] = array(
    '#type' => 'item',
    '#title' => t('Container'),
    '#tree' => TRUE,
    '#element_validate' => array('_container_validate'),
    '#prefix' => '<div class="my-container">',
    '#suffix' => '</div>',
  );
  $form['container']['textfield_one'] = array(
    '#type' => 'textfield',
    '#title' => t('Textfield 1'),
    '#tree' => TRUE,
  );
  $form['container']['textfield_two'] = array(
    '#type' => 'textfield',
    '#title' => t('Textfield 2'),
    '#tree' => TRUE,
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

/**
 * Validation callback
 */
function _container_validate($element, &$form_state, $form) {
  if ($form_state['values']['container']['textfield_one'] == $form_state['values']['container']['textfield_two']) {
    form_set_error('container', t("The two fields cannot have the same value."));
  }
}

This would be enough if you want just server side validation. If you want to support Clientside Validation there's one more thing you need to do.

You need to implement hook_clientside_validation_rule_alter(). In this example it would look like this:

potpourri.module

/**
 * Implements hook_clientside_validation_rule_alter().
 */
function potpourri_clientside_validation_rule_alter(&$js_rules, $element, $context) {
  switch($context['type']) {
    case 'element_validate':
      if (in_array('_container_validate', $context['functions'])) {
        _clientside_validation_set_not_equal(
          $element['textfield_one']['#name'],
          $element['textfield_one']['#title'],
          array(
            'form_key' => $element['textfield_two']['#name'],
            'name' => $element['textfield_two']['#title']
          ),
          $js_rules,
          t("The two fields cannot have the same value")
        );
      }
      break;
  }
}

The function _clientside_validation_set_not_equal is provided in clientside_validation.module. If you want to write your own custom validators you can look at the examples above.
And that's how you create a custom validator for #element_validate!

Validation for #validate

If you have written a custom PHP validator for Form API's #validate that looks like this (assuming your custom module is named potpourri):

potpourri.module

/**
 * Implements hook_menu().
 */
function potpourri_menu() {
  return array(
    'potpourri_form' => array(
      'title' => t('Test Clientside Validation'),
      'page callback' => 'drupal_get_form',
      'page arguments' => array('potpourri_form'),
      'access arguments' => array('access content'),
    ),
  );
}

/**
 * Form callback.
 */
function potpourri_form() {
  $form = array();
  $form['container'] = array(
    '#type' => 'item',
    '#title' => t('Container'),
    '#tree' => TRUE,
    '#prefix' => '<div class="my-container">',
    '#suffix' => '</div>',
  );
  $form['container']['textfield_one'] = array(
    '#type' => 'textfield',
    '#title' => t('Textfield 1'),
    '#tree' => TRUE,
  );
  $form['container']['textfield_two'] = array(
    '#type' => 'textfield',
    '#title' => t('Textfield 2'),
    '#tree' => TRUE,
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  $form['#validate'][] =  'my_custom_validation';
  return $form;
}

/**
 * Validation callback
 */
function my_custom_validation($form, &$form_state, $form) {
  if ($form_state['values']['container']['textfield_one'] == $form_state['values']['container']['textfield_two']) {
    form_set_error('container', t("The two fields cannot have the same value."));
  }
}

This would be enough if you want just server side validation. If you want to support Clientside Validation there's one more thing you need to do.

You need to implement hook_clientside_validation_rule_alter(). In this example it would look like this:

potpourri.module

/**
 * Implements hook_clientside_validation_rule_alter().
 */
function potpourri_clientside_validation_rule_alter(&$js_rules, $element, $context) {
  switch($context['type']) {
    case 'form_validate':
      if (in_array('my_custom_validation', $context['functions'])) {
        _clientside_validation_set_not_equal(
          $element['container']['textfield_one']['#name'],
          $element['container']['textfield_one']['#title'],
          array(
            'form_key' => $element['container']['textfield_two']['#name'],
            'name' => $element['container']['textfield_two']['#title']
          ),
          $js_rules,
          t("The two fields cannot have the same value")
        );
      }
      break;
  }
}

The function _clientside_validation_set_not_equal is provided in clientside_validation.module. If you want to write your own custom validators you can look at the examples above.
And that's how you create a custom validator for #validate!

Field Validation (Drupal 7 only)

If you have written a custom PHP validator for the Field Validation module that looks like this (assuming your custom module is named potpourri):

potpourri.module

/**
 * Implements hook_field_validation_validators().
 */
function potpourri_field_validation_validators() {
  return array(
    'must_be_empty' => array(
      'name' => "Must be empty",
      'field_types' => array(
        'textfield',
      ),
      'custom_error' => TRUE,
      'description' => t('Verifies that a specified textfield remains empty - Recommended use case: used as an anti-spam measure by hiding the element with CSS'),
    ),	
  );
}

/**
 * Implementation of hook_webform_validation_validate().
 */
/**
 * Implements hook_field_validation_validate().
 */
function field_validation_field_validation_validate($validator_name, $rule, $entity, $langcode, $items, &$errors) {
  if (!empty($items)) {
    switch ($validator_name) {
      case "must_be_empty":
        foreach ($items as $delta => $item) {
          if ($item[$rule['col']]) {
            $errors[$rule['field_name']][$langcode][$delta][] = array(
              'error' => 'must_be_empty_'.$rule['ruleid'],
              'message' => t($rule['error_message']),
            );
          }
        }
        break;
    }
  }
}

This would be enough if you want just server side validation. If you want to support Clientside Validation there are a few more things you need to do.

First, you need to implement hook_clientside_validation_rule_alter(). In this example it would look like this:

potpourri.module

/**
 * Implementation of hook_clientside_validation_rule_alter().
 */
function potpourri_clientside_validation_rule_alter (&$js_rules, $element, $context) {
  switch ($context['type']) {
    case 'field_validation':
      if ($context['rule']['validator'] == 'must_be_empty') {
        drupal_add_js(drupal_get_path('module', 'potpourri') . '/must_be_empty_validator.js');
        $title = variable_get('clientside_validation_prefix', '') . $element['#title'] . variable_get('clientside_validation_suffix', '');
        $js_rules[$element['element_name']]['mustBeEmpty'] = TRUE;
        $js_rules[$element['element_name']]['messages']['mustBeEmpty'] = theme('clientside_error', array('message' => 'Field !name must be empty.', 'placeholders' => array('!name' => $title)));
      }
      break;
  }
}

Finally, you need to create the must_be_empty_validator.js file in the map of you module. In this file you have to write the javascript validation:

must_be_empty_validator.js

//jQuery wrapper
(function ($) {
  //Define a Drupal behaviour with a custom name
  Drupal.behaviors.potpourriAddMustBeEmptyValidator = {
    attach: function (context) {
      //Add an eventlistener to the document reacting on the
      //'clientsideValidationAddCustomRules' event.
      $(document).bind('clientsideValidationAddCustomRules', function(event){
        //Add your custom method with the 'addMethod' function of jQuery.validator
        //http://docs.jquery.com/Plugins/Validation/Validator/addMethod#namemethodmessage
        jQuery.validator.addMethod("mustBeEmpty", function(value, element, param) {
          //return true if valid, false if invalid
          return value === '';
          //Enter a default error message.
        }, jQuery.format('Value must be empty'));
      });
    }
  }
})(jQuery);

And that's how you create a custom validator for Field Validation!

Comments

mauro72’s picture

Hi,
Thank you for these great examples, it helped me a lot with a case I was working on.
I want to comment about an issue I had when testing your code. For "potpourri_validation.js for Drupal 6" section I found one error that I think is related with the way that the attach function is declared. In order to solve the problem I had to change:

//Define a Drupal behaviour with a custom name
Drupal.behaviors.potpourriAddNotEmptyValidator = {
  attach: function (context) {
//Define a Drupal behaviour with a custom name
Drupal.behaviors.potpourriAddNotEmptyValidator =  function (context) {
//because a "{" was removed, also an extra "}" located near to final line needs to be removed. 

Greetings.

chiddicks’s picture

I also found this to be the case, so I updated it.

stomerfull’s picture

hello , i'm not able to get it working

Here is my code inside a custom module :

/**
 * Implementation of hook_clientside_validation_rule_alter().
 */
function mycustommodule_clientside_validation_rule_alter (&$js_rules, $element, $context) {
  switch ($context['type']) {
    case 'webform':
      if ($context['rule']['validator'] == 'email') {
        drupal_add_js(drupal_get_path('module', 'mycustommodule') . '/mail_validator.js');
        $title = variable_get('clientside_validation_prefix', '') . $element['element_title'] . variable_get('clientside_validation_suffix', '');
        $js_rules[$element['element_name']]['email'] = TRUE;
        $js_rules[$element['element_name']]['messages']['email'] = theme('clientside_error', array('message' => 'Field !name must be a valid phone number.', 'placeholders' => array('!name' => $title)));
      }
      break;
  }
}

and the javascript

/

/jQuery wrapper
(function ($) {
  //Define a Drupal behaviour with a custom name
  Drupal.behaviors.potpourriAddPhoneValidator = {
    attach: function (context) {
      //Add an eventlistener to the document reacting on the
      //'clientsideValidationAddCustomRules' event.
      $(document).bind('clientsideValidationAddCustomRules', function(event){
        //Add your custom method with the 'addMethod' function of jQuery.validator
        //http://docs.jquery.com/Plugins/Validation/Validator/addMethod#namemethodmessage
        jQuery.validator.addMethod("email", function(value, element, param) {
         return value !== ''  ||  value === 'VOTRE EMAIL';
          //Enter a default error message.
        }, jQuery.format('Value must a valid phone number'));
      });
    }
  }
})(jQuery);

What am i doing wrong

Thank you for your help