Thursday, May 19, 2011

CodeIgniter - form_validation Errors with array Field Names

ok, another problem with Code Igniter. When the field name is an array, and there is validation performed on the  post values, if one of the fields has an array, this array will be applied to all the form_error().

e.g. You are building a list of questions as a survey and have this validation.
$this->form_validation->set_rules(‘questions[]’, ’Question Text', ‘trim|required|xss_clean’);

And in your view, you have
foreach ($questions as $qn)
{
  ...
  echo form_input(array('name'=>'question_text[]'));
  echo form_error(‘questions[]’);
  ...
}

In this case, if one of the questions text is empty, the "required" error will be triggered. And since the validation object only have 1 error param (print_r($this->_field_data) to know what I'm talking about), this same error will be applied to all the form_error();

So this is what I did (reference to Code Igniter 2.x)

1. In system/libraries/Form_validation.php (or you can subclass it, though this method is quite long and may not be so straight forward)
Look for :
function _execute($row, $rules, $postdata = NULL, $cycles = 0)
 {
  // If the $_POST data is an array we will run a recursive call
  if (is_array($postdata))
  {
   foreach ($postdata as $key => $val)
   {
    $this->_execute($row, $rules, $val, $cycles);
    $cycles++;
   }

   return;
  } 

Insert this after:
if ( $row['is_array'] )
  {
   $this->_field_data[$row['field']]['error'][$cycles] = '';
  }

Then look for this:
$this->_field_data[$row['field']]['error'] = $message;

We need to comment that out and new logic to handle the error array, so replace that line with this:
//$this->_field_data[$row['field']]['error'] = $message;
if ( $row['is_array'] )
    {
     $this->_field_data[$row['field']]['error'][$cycles] = $message;
    } else 
    {
     $this->_field_data[$row['field']]['error'] = $message;
    }

Step 2. create a file in /application/libraries/MY_Form_validation.php
class MY_Form_validation extends CI_Form_validation
{
 function __construct($config = array())
 {
  parent::__construct($config);
 }
 function error($field = '', $prefix = '', $suffix = '')
 {
  if ( ! empty($this->_field_data[$field]) 
   && $this->_field_data[$field]['is_array'])
  {
   return ($prefix?$prefix:$this->_error_prefix)
   .array_shift($this->_field_data[$field]['error'])
   .($suffix?$suffix:$this->_error_suffix);
  }
  
  return parent::error($field, $prefix, $suffix);
 }
}

Done!

I'll explain.

Step 1 turns the validation object error parameter into an array, and insert the error message only when there is a validation error. Else it creates an empty error array element.

Step 2 is to display the error message if it exists. Basically it uses the same logic of popping out the first error message (which may be a blank) like when we do a set_value('question[]);

So that's it.

May have bugs, use at your own risk.

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi

    I came to the same problem but your solution didn't work exactly as it should. Basically if the error is related to the N-th field it should be presented on the N-th position in your case it's coming as the first one.

    Here is my small modification:

    // Save the error message
    if ( $row['is_array'] ){
      for($i=0;$i<$cycles; $i++)
        if(!isset($this->_field_data[$row['field']]['error'][$i]))
          $this->_field_data[$row['field']]['error'][$i] = '';
      $this->_field_data[$row['field']]['error'][$cycles] = $message;
    }
    else
      $this->_field_data[$row['field']]['error'] = $message;

    Additionally the modified _execute function could be also in MY_Form_Validation. It's better to leave original CI library files unchanged, right?

    ReplyDelete
  3. Trying out your solution, but I have found that:

    $this->_field_data[$row['field']]['error'] = $message;

    Exists twice in Form_validation.php on line 528 and 676. Do we add the logic in both locations?

    ReplyDelete