2011-04-07

Dynamic Assets: Part III - Entry Forms

Now that we have instantiated our dynamically defined objects, we can get to the meat of our project: displaying a form based on the dynamic object.

My team uses Zend to build our forms, but the technique can easily be adapted for other frameworks. Since Zend_Form affords adding fields one at a time, we can easily build a form by looping through an Asset's field list:
$asset = $assetFactory->build('some_type');

$form = new Zend_Form();
// ... Set up our form action, method, etc. ...

$typeField = new Zend_Form_Element_Hidden('__asset_type');
$typeField->setValue($asset->getType())
    ->setRequired(true);
$form->addElement($typeField);

foreach ($asset->listFields() as $fieldName => $field) {
    if (!$field->isSubAsset()) {
        $fieldElement = new AssetFieldFormElement($field, $asset->$fieldName);
        $form->addElement($fieldElement);
    }
}

// ... Add a submit button, other form housekeeping ...
There are two interesting bits to the above snippet. Firstly, we intentionally skip rendering sub-asset fields as part of the form. We found it was easier to make the user create a parent asset before allowing them to assign sub-assets to it. This simplifies the form generation, display, validation and data persistence.

The second interesting bit is the introduction of a new class: AssetFieldFormElement. We give this class the field object for which to build form element(s), and the value of the field.

This class is where the heavy-lifting is done. The basic idea is to read the field information, instantiate a proper Zend_Form_Element object (or set of objects) and set the properties of that element. The rest of the class is mainly methods to pass through calls to things like "addValidator()", "render()", etc.

Using this class, we can turn any given AssetField object into a wide variety of HTML form elements. The simplest is a basic text box:
$field = new AssetField();
$field->setName("first_name");
$element = new AssetFieldFormElement($field, "Zaphod");
Rendered HTML:
<label>First Name</label> <input name="first_name" type="text" value="Zaphod" />

 
If the field has a list of options, it renders as a drop-down. For things like the "date" type, we have a form element which will automatically attach a date-picker to the text box.

In a lot of cases, a single field will render as multiple form elements. For instance, sometimes a field's value can be an array of strings:
$field = new AssetField();
$field->setName("favorite_restaurants")
    ->setCollection(true);
$element = new AssetFieldFormElement($field, array("Milliways", "Stavro Mueller Beta"));
Rendered HTML:
<label>Favorite Restaurants</label>
<input name="favorite_restaurants[]" type="text" value="Milliways" />
<input name="favorite_restaurants[]" type="text" value="Stavro Mueller Beta" />
<input name="favorite_restaurants[]" type="text" value="" /><a href="#" class="adder" rel="favorite_restaurants">[+]</a>




[+]
A bit of Javascript on the "[+]" link allows the user to add more text boxes as needed (we can use the AssetField's "max" property to limit that if need be.)

Hang on, what if we we have a list of options for the user to choose from, but they can only choose each option once, and they need to be able to add their own if necessary? Not a problem:
$field = new AssetField();
$field->setName("drinks")
    ->setCollection(true)
    ->setUnique(true)
    ->setOptions(array("jynnan tonnyx", "jinond-o-nicks", "Ouisghian Zodah"))
    ->setOther("Something else?");
$element = new AssetFieldFormElement($field, array("jinond-o-nicks", "Pan Galactic Gargleblaster"));
Rendered HTML:
<label>Drinks</label>
<input name="drinks[]" type="checkbox" value="jynnan tonnyx" /> jynnan tonnyx
<input name="drinks[]" type="checkbox" value="jinond-o-nicks" checked="true" /> jinond-o-nicks
<input name="drinks[]" type="checkbox" value="Ouisghian Zodah" /> Ouisghian Zodah
<label>Something else?</label> <input name="drinks[]" type="text" value="Pan Galactic Gargleblaster" />


 jynnan tonnyx
 jinond-o-nicks
 Ouisghian Zodah
 
A wide variety of form effects can be achieved through combining different asset field flags.

The other function that AssetFieldFormElement performs is adding appropriate validators to each form element. This allows us to use `$form->isValid($params)` just as we do with our application's static forms.

This is the last of the posts on dynamic class definition. I hope it was helpful.

No comments:

Post a Comment