Permalink
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...
sad-spirit committed Apr 13, 2012
1 parent 4bd9cd3 commit 560af95737e4eac1415f45c8ef02f1bfad22e641
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
@@ -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
@@ -156,11 +156,16 @@ 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
@@ -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))) {
@@ -188,6 +188,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
*/
@@ -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) {
@@ -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.