Single form validation set
From Joomla! Documentation
This page explains how to use the same form validation rules for both client-side and server-side validation. By default Joomla! uses PHP form validation rules for the server-side validation and JavaScript rules for the client-side validation. This means we need to duplicate all the rules we want to check in both PHP and JavaScript. Time to do things different.
One more advantage of using one set of validation rules, you can use the same text for all error messages.
The backend side does not need any special code as it always uses the PHP validation rules, so this works by default.
We are going to create/modify a few files:
- enquiry.xml (form definition)
- validate.js (transport)
- validate.json.php (controller)
- validate.php (model)
- edit.php (form)
- en-GB.com_helloworld.ini (language)
The process[edit]
The way this works is by following this process:
- User loads a page with an edit form
- After filling out the form, the user clicks on the submit button
- The function Joomla.submitbutton = function(task) catches the submit action
- This function will call the validateForm() function in the validate.js file
- The validateForm() will get the needed data from the form and pass it on to the sendForm() function
- Now we are ready for sending, the sendForm() will send the data to the server to have the data validated
- In case the answer from the server contains a problem with the validation, the message will be shown to the user and the form is not submitted
- In case the answer from the server contains no problems, the form will be submitted.
The files[edit]
enquiry.xml[edit]
Let's start with the XML file, enquiry.xml, for the form, this will have 5 fields:
<?xml version="1.0" encoding="utf-8"?>
<form>
<fieldset>
<field name="enquiry_id"
type="hidden" />
<field name="title"
type="text"
label="COM_HELLOWORLD_ENQUIRY_TITLE_LABEL"
maxlength="50"
required="true" />
<field name="publish_up"
type="text"
label="COM_HELLOWORLD_ENQUIRY_PUBLISH_UP_LABEL"
hint="dd-mm-jjjj"
required="true"
validate="hello.date"
message="COM_HELLOWORLD_FORM_NO_VALID_DATE_MESSAGE"/>
<field name="publish_down"
type="text"
label="COM_HELLOWORLD_ENQUIRY_PUBLISH_DOWN_LABEL"
hint="dd-mm-jjjj"
field="publish_up"
validate="hello.dategreater"
required="true"
message="COM_HELLOWORLD_FORM_NO_VALID_GREATERDATE_MESSAGE"/>
<field name="phone"
type="tel"
label="COM_HELLOWORLD_PHONE"
validate="tel"
required="false"
message="COM_HELLOWORLD_FORM_NO_VALID_PHONE_MESSAGE"/>
</fieldset>
</form>
Here we have 5 fields: enquiry_id, title, publish_up, publish_down and phone.
- title: This field has no validation rule.
- publish_up: This field has a validation rule of hello.date.
- publish_down: This field has a validation rule of hello.greaterdate.
- phone: This field has a validation rule of phone.
validate.js[edit]
This file contains the code that sends the validation request to the server.
function validateForm(form, xml, group, token) {
// Load all the form fields
var filter = '[name^=jform';
if (group) {
filter += '\\[' + group + '\\]';
}
filter += ']';
var fields = jQuery('#' + form + ' ' + filter);
// Collect the fields and their values
var data = [];
fields.each(function (key, field) {
data.push(field.name + '=' + encodeURIComponent(field.value));
});
// Send the form with the field info
return sendForm(data, xml, group, token);
}
/**
* Validate the fields on the server
*
* @param array data An array with the data to validate.
* @param string xml The name of the XML file to validate against.
* @param string group The name of the group the fields belong to.
* @param string token The security token.
*/
function sendForm(data, xml, group, token) {
// Send the data to the server for validation
return jQuery.ajax({
async: true,
url: 'index.php',
dataType: 'json',
cache: false,
method: 'post',
data: 'option=com_helloworld&task=validate.validateform&xml=' + xml + '&group=' + group + "&" + data.join('&') + '&format=json&' + token + '=1',
success: function (data) {
if (data) {
var msg = '';
jQuery(data).each(function (index, item) {
if (item.message.length > 0) {
var msgtype = item.type;
msg =
{
msgtype: [item.message]
};
}
});
// Render the message
if (jQuery(msg).size() > 0) {
Joomla.renderMessages(msg);
}
}
},
error: function (request, status, error) {
var msg = {
error: [request.status + ' ' + request.statusText + '<br />' + request.responseText]
};
// Render the message
Joomla.renderMessages(msg);
}
});
}
validate.json.php[edit]
This is the controller that receives the AJAX request and passes the data to the model to be validated.
defined('_JEXEC') or die;
/**
* Validate controller.
*
* @package Helloworld
* @since 1.0
*/
class HelloworldControllerValidate extends JControllerForm
{
/**
* Add a new lead.
*
* @return string JSON encoded string.
*
* @since 1.0
*/
public function validateForm()
{
// Check for request forgeries.
JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
$model = $this->getModel();
$data = $this->input->post->get('jform', array(), 'array');
$xml = $this->input->post->getCmd('xml', '');
$group = $this->input->post->getCmd('group', '');
$return = array(
'message' => '',
'type' => '',
'continue' => true
);
// Validate the posted data.
// Sometimes the form needs some posted data, such as for plugins and modules.
$form = new JForm($xml);
$form->addFormPath(JPATH_SITE . '/components/com_helloworld/models/forms');
$form->loadFile($xml);
if (!$form)
{
$return['message'] = $form->getErrors();
$return['type'] = 'error';
$return['continue'] = false;
}
else
{
// Test whether the data is valid.
$validData = $model->validate($form, $data, $group);
// Check for validation errors.
if ($validData === false)
{
// Get the validation messages.
$errors = $model->getErrors();
// Push the validation messages out to the user.
for ($i = 0, $n = count($errors); $i < $n && $i < 1; $i++)
{
if ($errors[$i] instanceof Exception)
{
$return['message'] = $errors[$i]->getMessage();
$return['type'] = 'warning';
$return['continue'] = false;
}
else
{
$return['message'] = $errors[$i];
$return['type'] = 'warning';
$return['continue'] = false;
}
}
}
}
echo json_encode($return);
jexit();
}
}
@todo: Use JResponseJson to return the results.
validate.php[edit]
The model is simply there to proxy the request to Joomla.
defined('_JEXEC') or die;
/**
* Validate form model.
*
* @package Helloworld
* @since 1.0
*/
class HelloworldModelValidate extends JModelForm
{
/**
* Method to get the contact form.
* The base form is loaded from XML and then an event is fired
*
* @param array $data An optional array of data for the form to interrogate.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return JForm A JForm object on success, false on failure
*
* @since 1.0
*/
public function getForm($data = array(), $loadData = true)
{
return false;
}
}
edit.php[edit]
The form that shows the actual form. In our template file we will have the following form:
<form action="<?php echo JRoute::_('index.php?option=com_helloworld&view=enquiry&layout=edit&id=' . $this->item->enquiry_id, false); ?>" method="post" name="item-form" id="item-form">
<div>
<?php echo $this->form->getLabel('title'); ?>
<div class="controls">
<?php echo $this->form->getInput('title'); ?>
</div>
</div>
<div>
<?php echo $this->form->getLabel('publish_up'); ?>
<div class="controls">
<?php echo $this->form->getInput('publish_up'); ?>
</div>
</div>
<div>
<?php echo $this->form->getLabel('publish_down'); ?>
<div class="controls">
<?php echo $this->form->getInput('publish_down'); ?>
</div>
</div>
<input type="hidden" name="task" value="enquiry.submit">
<input type="hidden" name="enquiry_id" value="<?php echo $this->item->enquiry_id; ?>">
<?php echo JHtml::_('form.token'); ?>
</form>
<script type="text/javascript">
Joomla.submitbutton = function(task)
{
if (task == 'enquiry.cancel')
{
Joomla.submitform(task, document.getElementById('item-form'));
}
else
{
jQuery.when(validateForm('item-form', 'enquiry', '', '<?php echo JSession::getFormToken(); ?>')).done(function(result)
{
if (result.continue)
{
Joomla.submitform(task, document.getElementById('item-form'));
}
});
}
}
</script>
The validation rules[edit]
All custom validation rules are stored in the administrator/components/com_helloworld/models/rules folder. So we have one place where the custom rules live. The default rules, Joomla! knows where to find them, the custom rules Joomla! does not know so the following line of code should be added to your component entry file:
JForm::addRulePath('administrator/components/com_helloworld/models/rules');
hello.date[edit]
defined('_JEXEC') or die;
/**
* Form Rule class that checks for a valid date value.
*
* @package Helloworld
* @since 1.0
*/
class HelloFormRuleDate extends JFormRule
{
/**
* Method to test the field is not empty.
*
* @param SimpleXMLElement $element The SimpleXMLElement object representing the <field /> tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control value. This acts as as an array container for the field.
* For example if the field has name="foo" and the group value is set to "bar" then the
* full field name would end up being "bar[foo]".
* @param JRegistry $input An optional JRegistry object with the entire data set to validate against the entire form.
* @param JForm $form The form object for which the field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 11.1
*/
public function test(SimpleXMLElement $element, $value, $group = null, JRegistry $input = null, JForm $form = null)
{
try
{
JDate::getInstance($value);
}
catch (Exception $e)
{
return false;
}
return true;
}
}
hello.greaterdate[edit]
defined('_JEXEC') or die;
/**
* Form Rule class that checks for a date is later than another date.
*
* @package Helloworld
* @since 1.0
*/
class HelloFormRuleDategreater extends JFormRule
{
/**
* Method to test the field is not empty.
*
* @param SimpleXMLElement $element The SimpleXMLElement object representing the <field /> tag for the form field object.
* @param mixed $value The form field value to validate.
* @param string $group The field name group control value. This acts as as an array container for the field.
* For example if the field has name="foo" and the group value is set to "bar" then the
* full field name would end up being "bar[foo]".
* @param JRegistry $input An optional JRegistry object with the entire data set to validate against the entire form.
* @param JForm $form The form object for which the field is being tested.
*
* @return boolean True if the value is valid, false otherwise.
*
* @since 11.1
*/
public function test(SimpleXMLElement $element, $value, $group = null, JRegistry $input = null, JForm $form = null)
{
try
{
// Check for a valid date
$date1 = JDate::getInstance($value);
// Check if the second date is after the first date
$field = (string) $element['field'];
$fvalue = $input->get($group . '.' . $field, false);
if ($value)
{
try
{
$date2 = JDate::getInstance($fvalue);
if ($date1 < $date2)
{
return false;
}
}
catch (Exception $e)
{
return false;
}
}
}
catch (Exception $e)
{
return false;
}
return true;
}
}
Language file[edit]
In the previous code snippets a number of language strings are used, do not forget them to add them to the appropriate language files. You need to add the language strings to the frontend language file but also to the backend language file in case you want to use the same validation rules in the backend. The other option is to use only the frontend language file but load this language file in the backend as well.