Skip to content
Browse files

Do not force purely numeric indexes

Changed index generation in JS, previous approach made it impossible to reorder repeated items
Renamed setIdentityField() to setIndexField(), added setIndexes() / getIndexes()
Added possibility to guess index field name


git-svn-id: http://svn.php.net/repository/pear/packages/HTML_QuickForm2/branches/repeat_element@325153 c90b9560-bf6c-de11-be94-00142212c4b1
  • Loading branch information...
1 parent 4bd9cd3 commit 560af95737e4eac1415f45c8ef02f1bfad22e641 @sad-spirit sad-spirit committed
Showing with 170 additions and 20 deletions.
  1. +85 −11 HTML/QuickForm2/Container/Repeat.php
  2. +17 −9 js/src/repeat.js
  3. +68 −0 tests/QuickForm2/Container/RepeatTest.php
View
96 HTML/QuickForm2/Container/Repeat.php
@@ -137,8 +137,8 @@ public function passLibraries(HTML_QuickForm2_JavascriptBuilder $recipient)
* // this is identical to $group->addCheckbox('related_active');
* $repeat->addCheckbox('related_active');
*
- * // value of this field will be used to find the number of repeated items
- * $repeat->setIdentityField('related_id');
+ * // value of this field will be used to find the indexes of repeated items
+ * $repeat->setIndexField('related_id');
* </code>
*
* @category HTML
@@ -157,10 +157,15 @@ class HTML_QuickForm2_Container_Repeat extends HTML_QuickForm2_Container
const INDEX_KEY = ':idx:';
/**
+ * Regular expression used to check for valid indexes
+ */
+ const INDEX_REGEXP = '/^[a-zA-Z0-9_]+$/';
+
+ /**
* Field used to search for available indexes
* @var string
*/
- protected $identityField;
+ protected $indexField = null;
/**
* Available indexes
@@ -311,22 +316,89 @@ protected function getDataSources()
* may be disabled are bad choices
*
* @param string $field field name
- *
- * @todo currently will not work without this, maybe use the first field if not given explicitly?
*/
- public function setIdentityField($field)
+ public function setIndexField($field)
{
- $this->identityField = $field;
+ $this->indexField = $field;
$this->updateValue();
}
/**
+ * Tries to guess a field name to use for getting indexes of repeated items
+ *
+ * @return bool Whether we were able to guess something
+ * @see setIndexField()
+ */
+ private function _guessIndexField()
+ {
+ $this->appendIndexTemplates();
+ $this->passDataSources = false;
+ /* @var $child HTML_QuickForm2_Node */
+ foreach ($this->getRecursiveIterator(RecursiveIteratorIterator::LEAVES_ONLY) as $child) {
+ $name = $child->getName();
+ if (false === ($pos = strpos($name, '[' . self::INDEX_KEY . ']'))
+ || $child->getAttribute('disabled')
+ ) {
+ continue;
+ }
+ // The list is somewhat future-proof for HTML5 input elements
+ if ($child instanceof HTML_QuickForm2_Element_Input
+ && !($child instanceof HTML_QuickForm2_Element_InputButton
+ || $child instanceof HTML_QuickForm2_Element_InputCheckable
+ || $child instanceof HTML_QuickForm2_Element_InputFile
+ || $child instanceof HTML_QuickForm2_Element_InputImage
+ || $child instanceof HTML_QuickForm2_Element_InputReset
+ || $child instanceof HTML_QuickForm2_Element_InputSubmit)
+ || ($child instanceof HTML_QuickForm2_Element_Select
+ && !$child->getAttribute('multiple'))
+ || $child instanceof HTML_QuickForm2_Element_Textarea
+ ) {
+ $this->indexField = substr($name, 0, $pos);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the indexes for repeated items
+ *
+ * @return array
+ */
+ public function getIndexes()
+ {
+ if (null === $this->indexField && $this->_guessIndexField()) {
+ $this->updateValue();
+ }
+ return $this->itemIndexes;
+ }
+
+ /**
+ * Sets the indexes for repeated items
+ *
+ * As is the case with elements' values, the indexes will be updated
+ * from data sources, so use this after all possible updates were done.
+ *
+ * @param array $indexes
+ */
+ public function setIndexes(array $indexes)
+ {
+ $hash = array();
+ foreach ($indexes as $index) {
+ if (preg_match(self::INDEX_REGEXP, $index)) {
+ $hash[$index] = true;
+ }
+ }
+ $this->itemIndexes = array_keys($hash);
+ }
+
+ /**
* Called when the element needs to update its value from form's data sources
*
* Behaves similar to Element::updateValue(), the field's value is used to
* deduce indexes taken by repeat items.
*
- * @see setIdentityField()
+ * @see setIndexField()
*/
protected function updateValue()
{
@@ -342,11 +414,13 @@ protected function updateValue()
$container = $container->getContainer();
}
+ if (null === $this->indexField && !$this->_guessIndexField()) {
+ return;
+ }
/* @var HTML_QuickForm2_DataSource $ds */
foreach (parent::getDataSources() as $ds) {
- if (null !== ($value = $ds->getValue($this->identityField))) {
- unset($value[self::INDEX_KEY]);
- $this->itemIndexes = array_map('intval', array_keys($value));
+ if (null !== ($value = $ds->getValue($this->indexField))) {
+ $this->setIndexes(array_keys($value));
return;
}
}
View
26 js/src/repeat.js
@@ -159,7 +159,7 @@ qf.Repeat.prototype = {
*/
findIndex: function(item)
{
- var itemRegexp = new RegExp('^' + this.itemId.replace(':idx:', '(\\d+?)') + '$'),
+ var itemRegexp = new RegExp('^' + this.itemId.replace(':idx:', '([a-zA-Z0-9_]+?)') + '$'),
m;
if (item.id && (m = itemRegexp.exec(item.id))) {
@@ -189,6 +189,21 @@ qf.Repeat.prototype = {
return parent;
},
/**
+ * Generates a new index for item being added to the repeat
+ *
+ * @returns {String}
+ */
+ generateIndex: function()
+ {
+ var index;
+
+ do {
+ // 10000 will be enough for everybody!
+ index = 'add' + Math.round(Math.random() * 10000);
+ } while (document.getElementById(this.itemId.replace(':idx:', index)));
+ return index;
+ },
+ /**
* Adds a new repeated item to the repeat element
*/
add: function()
@@ -200,14 +215,7 @@ qf.Repeat.prototype = {
var items = this.getElementsByClass('repeatItem', this.container),
lastItem = items[items.length - 1],
clone = this.repeatPrototype.cloneNode(true),
- index;
-
- if (qf.classes.has(lastItem, 'repeatPrototype')) {
- // last item *is* prototype -> use 0 as index
- index = 0;
- } else {
- index = this.findIndex(lastItem) - (-1);
- }
+ index = this.generateIndex();
qf.classes.remove(clone, 'repeatPrototype');
if (clone.id) {
View
68 tests/QuickForm2/Container/RepeatTest.php
@@ -107,5 +107,73 @@ public function testElementsAreAddedToPrototype()
$repeat->removeChild($textOne);
$this->assertNull($textOne->getContainer());
}
+
+ public function testSetIndexesExplicitly()
+ {
+ $repeat = new HTML_QuickForm2_Container_Repeat();
+ $this->assertEquals(array(), $repeat->getIndexes());
+
+ $repeat->setIndexes(array('foo', 'bar', 'baz', 'qu\'ux', 'baz', 25));
+ $this->assertEquals(array('foo', 'bar', 'baz', 25), $repeat->getIndexes());
+ }
+
+ public function testSetIndexFieldExplicitly()
+ {
+ $form = new HTML_QuickForm2('testIndexField');
+ $form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
+ 'blah' => array(
+ 'blergh' => 'a',
+ 'blurgh' => 'b',
+ 'ba-a-a-ah' => 'c',
+ 42 => 'd'
+ ),
+ 'argh' => array(
+ 'a' => 'e',
+ 'b\'c' => 'f',
+ 'd' => 'g'
+ )
+ )));
+
+ $repeat = new HTML_QuickForm2_Container_Repeat();
+ $repeat->setIndexField('blah');
+ $repeat->setIndexes(array('foo', 'bar'));
+ $form->appendChild($repeat);
+ $this->assertEquals(array('blergh', 'blurgh', 42), $repeat->getIndexes());
+
+ $repeat->setIndexField('argh');
+ $this->assertEquals(array('a', 'd'), $repeat->getIndexes());
+ }
+
+ public function testGuessIndexField()
+ {
+ $form = new HTML_QuickForm2('guessIndexField');
+ $form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
+ 'blah' => array('foo' => 1),
+ 'bzz' => array('bar' => array('a', 'b')),
+ 'aaargh' => array('foo' => ''),
+ 'blergh' => array('foo' => '', 'bar' => 'bar value')
+ )));
+
+ $repeat = new HTML_QuickForm2_Container_Repeat();
+ $form->appendChild($repeat);
+
+ $this->assertEquals(array(), $repeat->getIndexes());
+
+ $fieldset = new HTML_QuickForm2_Container_Fieldset();
+ $repeat->setPrototype($fieldset);
+ $this->assertEquals(array(), $repeat->getIndexes());
+
+ $fieldset->addCheckbox('blah');
+ $this->assertEquals(array(), $repeat->getIndexes());
+
+ $fieldset->addSelect('bzz', array('multiple'));
+ $this->assertEquals(array(), $repeat->getIndexes());
+
+ $fieldset->addText('aaargh', array('disabled'));
+ $this->assertEquals(array(), $repeat->getIndexes());
+
+ $fieldset->addText('blergh');
+ $this->assertEquals(array('foo', 'bar'), $repeat->getIndexes());
+ }
}
?>

0 comments on commit 560af95

Please sign in to comment.
Something went wrong with that request. Please try again.