Repeat element
Overview
Repeat requires
quickform.js
andquickform-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 / id
s.
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.