views:

229

answers:

2

I am using Symfony 1.3.6 on Ubuntu.

I have a form with a lot of fields on it - rather than showing all the fields in one go (which may intimidate the user), I want to break up the form into stages, so that a user can fill in only the fields displayed, at each step/stage (kinda like a wizard).

In order to do that, I need to write custom methods for the form, e.g.:

validateStep1();
validateStep2();
...
validate StepN();

Where in each of the functions above, I only validate a subset of the available widgets - i.e. I validate only the widgets displayed to the user in that step.

In order to do that, it would be useful if I could call an isValid() method on widgets, however, I have looked at the sfWidget classes and there is no such isValid() method at the widget level.

I don't want to hard code validation for each widget I am using, since this is not DRY

Does anyone know how I can check individual widgets in a form to see whether the user entered value(s) are valid?

+1  A: 

The function isValid() doesn't really do anything, except check if the associated form has been bound and that the total number of validator errors is 0. The actual validation is done during the "binding" stage ($form->bind()).

After binding, validator errors are stored in each field of the form(sfFormField). So, to get the individual errors of form fields, you can do something like this:

<?php
  foreach ($form as $formField) { // $formField is an instance of sfFormField
    if ($formField->hasError()) {
      // do something
    }
  }
?>

Or, since in your case you need to deal with only a restricted set of fields, try to iterate over an array of field names instead:

<?php
  $fieldNames = array('name', 'email', 'address');
  foreach ($fieldNames as $fieldName) {
    if ($form[$fieldName]->hasError()) {
      // do something
    }
  }
?>

This can easily be adapted into a function, say validateFields($fieldNames) that meets DRY expectations.

Check the documentation for sfFormField to see what other information you can get from a field.

mganjoo
+2  A: 

I would utilize a different method to implement multi part forms in Symfony. Hopefully, the following shell is enough to get you started.

Step 1: Add a stage widget to your form

public function configure()
{
  $this->setWidget('stage', new sfWidgetFormInputHidden(array('default' => 1));
  $this->setValidator('stage', new sfValidatorFormInteger(array('min' => 1, 'max' => $maxStages, 'required' => true));
}

Step 2: Add some information about the stages to your form

protected $stages = array(
  1 => array('stage1field1', 'stage1field2',
  2 => array('stage2field1', ... //etc for as many stages you have
);

Step 3: Add a configure as stage method to your form

public function configureAsStage($currentStage)
{
  foreach($this->stages as $stage => $field)
  {
    if ($currentStage > $stage)
    {
      $this->setWidget($stage, new sfWidgetFormInputHidden()); //so these values carry through
    }
    if ($stage > $currentStage)
    {
       unset($this[$stage]); //we don't want this stage here yet
    }
  }
}

Step 4: Override doBind

You might need to override bind() directly, I forget.

public function doBind(array $taintedValues)
{
  $cleanStage = $this->getValidator('stage')->clean($taintedValues['stage']);
  $this->configureAsStage($cleanStage);
  parent::doBind($taintedValues);
}

Step 5: Add some auxiliary methods to the form

public function advanceStage()
{
  if ($this->isValid())
  {
    $this->values['stage'] += 1;
    $this->taintedValues['stage'] += 1;
    $this->resetFormFields();
  }
}

public function isLastStage()
{
  return $this->getValue('stage') == count($this->stages);
}

Step 6: Call configureAsStage/advanceStage as necessary in your action

public function executeNew(sfWebRequest $request)
{
  $form = new MultiStageForm($record);
  $form->configureAsStep(1);
}

public function executeCreate(sfWebRequest $request)
{
  $record = new Record();
  $form = new MultiStageForm($record);
  $form->bind($request[$form->getName()]);
  if ($form->isValid())
  {
    if ($form->isLastStage())
    {
      $form->save();
      //redirect or whatever you do here
    }
    $form->advanceStage(); 
  }
  //render form
}

I made this up completely on the fly. I think it should work, but I haven't tested it so there may be some mistakes!

jeremy
+1: Definitely, more Symfonic way of doing things
morpheous
If you find yourself doing this in multiple forms, this code could be placed in BaseForm.
jeremy