Repeat element

Repeat element – Repeating a given Container several times

Overview

Repeat requires quickform.js and quickform-repeat.js files being included in the page to work.

HTML_QuickForm2_Container_Repeat is an element that accepts a 'prototype' Container (a Fieldset or a Group, but not another Repeat) and repeats it several times in the form. Repeated items can be dynamically added / removed via Javascript, server-side part automatically understands these changes. Server-side and client-side validation can be easily leveraged: rules added to prototype Container and its children are repeated as well.

repeat.php example file installed with the package shows how to use repeat elements with Fieldsets and Groups.

Adding elements to Repeat

HTML_QuickForm2_Container_Repeat has only one immediate child element: a prototype Container, either set using setPrototype() method or passed as 'prototype' key of $data parameter to Repeat's constructor.

appendChild(), insertBefore(), removeChild() are available for Repeat element as usual, but these are proxies for prototype's methods working with its children and will throw exceptions if prototype was not yet set. An exception will also be thrown if you attempt to add another Repeat element to a prototype.

Adding elements to Repeat

<?php
$fieldset 
= new HTML_QuickForm2_Container_Fieldset();
$repeat   = new HTML_QuickForm2_Container_Repeat();
$repeat->setPrototype($fieldset);

echo 
"repeat: " count($repeat) . "; fieldset: " count($fieldset) . "\n";

$repeat->addText('title');

echo 
"repeat: " count($repeat) . "; fieldset: " count($fieldset) . "\n";
?>

the above code outputs


repeat: 1; fieldset: 0
repeat: 1; fieldset: 1
    

Names of repeated elements

As is the case with groups, repeat element will change names of its children, additionally id attributes will be changed. Instead of prepending the repeat's name, however, a special "index template" '[:idx:]' will be appended to the name and _:idx: to the id. ':idx:' string in these will later be replaced by an actual index of the repeated item to produce unique names / ids.

Default names for repeated elements

<?php
$form 
= new HTML_QuickForm2('repeatFieldset');
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
    
'title' => array('foo''bar''key' => 'baz')
)));

$fieldset = new HTML_QuickForm2_Container_Fieldset();
$repeat   $form->addRepeat()->setPrototype($fieldset);

$repeat->addText('title', array('id' => 'title'));

$renderer $repeat->render(
    
HTML_QuickForm2_Renderer::factory('array')
);

$array $renderer->toArray();

foreach (
$array['elements'] as $fsArray) {
    echo 
$fsArray['attributes'] . "\n";
    echo 
"    " $fsArray['elements'][0]['html'] . "\n";
}
?>

outputs


 id="qfauto-0_:idx:" class="repeatItem repeatPrototype"
    <input type="text" id="title_:idx:" name="title[:idx:]" />
 id="qfauto-0_0" class="repeatItem"
    <input type="text" id="title_0" name="title[0]" value="foo" />
 id="qfauto-0_1" class="repeatItem"
    <input type="text" id="title_1" name="title[1]" value="bar" />
 id="qfauto-0_key" class="repeatItem"
    <input type="text" id="title_key" name="title[key]" value="baz" />
   

There are a few things worth noting in this output:

  • Keys for repeated items are discovered automatically from data source using the values for the 'title' field. Sometimes it is better to explicitly set the field to use for discovering indexes using setIndexField().
  • Keys are not required to be numeric, they should however match the HTML_QuickForm2_Container_Repeat::INDEX_REGEXP regular expression: '/^[a-zA-Z0-9_]+$/'.
  • The output contains an extra item which still has its index templates intact. It should be hidden thanks to repeatPrototype CSS class and will be used by Javascript code to add new visible repeated items.

If you are using a named group as a repeat prototype, you may want to use another name structure: group[index][element] instead of group[element][index]. It is possible to do so if you manually put [:idx:] into group's name, Repeat will not mangle names further if this string is already present in them.

Custom names for repeated elements

<?php
$form 
= new HTML_QuickForm2('repeatGroup');
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
    
'tags' => array(
        array(
'title' => 'foo'),
        array(
'title' => 'bar'),
        
'key' => array('title' => 'baz')
    )
)));

$group  = new HTML_QuickForm2_Container_Group('tags[:idx:]');
$repeat $form->addRepeat()->setPrototype($group);

// custom id as well
$group->addText('title', array('id' => 'tags-:idx:-title'));

$renderer $repeat->render(
    
HTML_QuickForm2_Renderer::factory('array')
);

$array $renderer->toArray();

foreach (
$array['elements'] as $groupArray) {
    echo 
$groupArray['elements'][0]['html'] . "\n";
}
?>

outputs


<input type="text" id="tags-:idx:-title" name="tags[:idx:][title]" />
<input type="text" id="tags-0-title" name="tags[0][title]" value="foo" />
<input type="text" id="tags-1-title" name="tags[1][title]" value="bar" />
<input type="text" id="tags-key-title" name="tags[key][title]" value="baz" />
   

For radios and checkboxes it is also possible to put :idx: into value attribute, this way their names will not be changed, essentially allowing to use one set of radios or checkboxes for all repeated items.

Indexes of repeated items

As was noted in the above example, indexes for repeated items can be automatically discovered from data sources. The field name to use for discovering indexes can be either set explicitly with setIndexField() or left for the Repeat to choose automatically using names of prototype's child elements. When guessing, it will only consider elements that are expected to always have a submit value, so elements like buttons, checkboxes and multiple selects will not be used.

Indexes can be also set manually using setIndexes(), the corresponding getIndexes() is also available. Duplicate indexes and those not matching HTML_QuickForm2_Container_Repeat::INDEX_REGEXP will be ignored by setIndexes().

Working with indexes

<?php
$form 
= new HTML_QuickForm2('repeatIndexes');
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
    
'title' => array('foo''bar''key' => 'baz')
)));

$fieldset = new HTML_QuickForm2_Container_Fieldset();
$repeat   $form->addRepeat()->setPrototype($fieldset);

// no element to guess indexes present
var_dump($repeat->getIndexes());

// explicitly set field to discover indexes
$repeat->setIndexField('title');
var_dump($repeat->getIndexes());

// explicit setIndexes() with a few errors
$repeat->setIndexes(array('foo''bar''baz''qu\'ux''baz'));
var_dump($repeat->getIndexes());
?>

outputs


array(0) {
}
array(3) {
  [0]=>
  int(0)
  [1]=>
  int(1)
  [2]=>
  string(3) "key"
}
array(3) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  string(3) "baz"
}
   

Setting indexes for repeat elements works quite similar to setting values for other elements, so keeping correct order really helps.