Skip to content
Browse files

An example for repeat elements, event callbacks for repeat JS

Frozen repeat no longer outputs Javascript
Repeat's constructor accepts prototype, setters return $this


git-svn-id: http://svn.php.net/repository/pear/packages/HTML_QuickForm2/trunk@325176 c90b9560-bf6c-de11-be94-00142212c4b1
  • Loading branch information...
1 parent ab31422 commit bf1aad61a742d52ce6710fd7a3db71326bdd7add @sad-spirit sad-spirit committed Apr 14, 2012
View
34 HTML/QuickForm2/Container/Repeat.php
@@ -212,6 +212,25 @@ public function setValue($value)
}
/**
+ * Class constructor
+ *
+ * Repeat element can understand the following keys in $data parameter:
+ * - 'prototype': a Container to be repeated. Passed to {@link setPrototype()}.
+ *
+ * @param string $name Element name
+ * @param string|array $attributes Attributes (either a string or an array)
+ * @param array $data Additional element data
+ */
+ public function __construct($name = null, $attributes = null, array $data = array())
+ {
+ if (!empty($data['prototype'])) {
+ $this->setPrototype($data['prototype']);
+ }
+ unset($data['prototype']);
+ parent::__construct($name, $attributes, $data);
+ }
+
+ /**
* Sets the Container that will be used as a prototype for repeating
*
* @param HTML_QuickForm2_Container $prototype prototype container
@@ -316,11 +335,14 @@ protected function getDataSources()
* may be disabled are bad choices
*
* @param string $field field name
+ *
+ * @return HTML_QuickForm2_Container_Repeat
*/
public function setIndexField($field)
{
$this->indexField = $field;
$this->updateValue();
+ return $this;
}
/**
@@ -380,6 +402,8 @@ public function getIndexes()
* from data sources, so use this after all possible updates were done.
*
* @param array $indexes
+ *
+ * @return HTML_QuickForm2_Container_Repeat
*/
public function setIndexes(array $indexes)
{
@@ -390,6 +414,7 @@ public function setIndexes(array $indexes)
}
}
$this->itemIndexes = array_keys($hash);
+ return $this;
}
/**
@@ -682,9 +707,12 @@ public function render(HTML_QuickForm2_Renderer $renderer)
}
$this->restoreChildAttributes($backup);
- $jsBuilder->addLibrary('repeat', 'quickform-repeat.js');
- $jsBuilder->addElementJavascript($this->_generateInitScript($evalBuilder));
- $this->renderClientRules($jsBuilder);
+ // only add javascript if not frozen
+ if (!$this->toggleFrozen()) {
+ $jsBuilder->addLibrary('repeat', 'quickform-repeat.js');
+ $jsBuilder->addElementJavascript($this->_generateInitScript($evalBuilder));
+ $this->renderClientRules($jsBuilder);
+ }
$renderer->finishContainer($this);
$renderer->setOption('group_hiddens', $hiddens);
View
6 data/js/min/quickform-repeat.js
@@ -8,9 +8,9 @@
http://opensource.org/licenses/bsd-license.php
*/
qf.Repeat=function(a,b,d,e,f){a.repeat=this;this.repeatPrototype=this.form=null;this.container=a;this.itemId=b;this.rulesTpl=e;this.scriptsTpl=f;this.triggers=d;e=this.getElementsByClass("repeatAdd",a);for(b=0;d=e[b];b++)qf.events.addListener(d,"click",qf.Repeat.addHandler);a=this.getElementsByClass("repeatRemove",a);for(b=0;d=a[b];b++)qf.events.addListener(d,"click",qf.Repeat.removeHandler)};
-qf.Repeat.addHandler=function(a){for(var a=qf.events.fixEvent(a),b=a.target;b&&!qf.classes.has(b,"repeat");)b=b.parentNode;b&&b.repeat&&b.repeat.add();a.preventDefault()};qf.Repeat.removeHandler=function(a){for(var a=qf.events.fixEvent(a),b=a.target,d;b&&!qf.classes.has(b,"repeat");)qf.classes.has(b,"repeatItem")&&(d=b),b=b.parentNode;b&&b.repeat&&b.repeat.remove(d);a.preventDefault()};
+qf.Repeat.addHandler=function(a){for(var a=qf.events.fixEvent(a),b=a.target;b&&!qf.classes.has(b,"repeat");)b=b.parentNode;b&&b.repeat&&b.repeat.onBeforeAdd()&&b.repeat.add();a.preventDefault()};qf.Repeat.removeHandler=function(a){for(var a=qf.events.fixEvent(a),b=a.target,d;b&&!qf.classes.has(b,"repeat");)qf.classes.has(b,"repeatItem")&&(d=b),b=b.parentNode;b&&b.repeat&&b.repeat.onBeforeRemove(d)&&b.repeat.remove(d);a.preventDefault()};
qf.Repeat.prototype={getElementsByClass:function(){return document.getElementsByClassName?function(a,b){return b.getElementsByClassName(a)}:function(a,b){for(var d=b.getElementsByTagName("*"),e=[],f=0,c;c=d[f];f++)qf.classes.has(c,a)&&e.push(c);return e}}(),findIndex:function(a){var b=RegExp("^"+this.itemId.replace(":idx:","([a-zA-Z0-9_]+?)")+"$"),d;if(a.id&&(d=b.exec(a.id)))return d[1];for(var a=a.getElementsByTagName("*"),e=0,f;f=a[e];e++)if(f.id&&(d=b.exec(f.id)))return d[1]},findForm:function(){for(var a=
this.container;a&&"form"!==a.nodeName.toLowerCase();)a=a.parentNode;return a},generateIndex:function(){var a;do a="add"+Math.round(1E4*Math.random());while(document.getElementById(this.itemId.replace(":idx:",a)));return a},add:function(){this.repeatPrototype||(this.repeatPrototype=this.getElementsByClass("repeatPrototype",this.container)[0]);var a=this.getElementsByClass("repeatItem",this.container),b=a[a.length-1],d=this.repeatPrototype.cloneNode(!0),e=this.generateIndex();qf.classes.remove(d,"repeatPrototype");
d.id&&(d.id=d.id.replace(":idx:",e));for(var f=d.getElementsByTagName("*"),a=0,c;c=f[a];a++){c.id&&(c.id=c.id.replace(":idx:",e));c.name&&(c.name=c.name.replace(":idx:",e));if(c.type&&("checkbox"==c.type||"radio"==c.type))c.value=c.value.replace(":idx:",e);c.htmlFor&&(c.htmlFor=c.htmlFor.replace(":idx:",e));"script"==c.nodeName.toLowerCase()&&eval(c.innerHTML.replace(/:idx:/g,e));qf.classes.has(c,"repeatAdd")&&qf.events.addListener(c,"click",qf.Repeat.addHandler);qf.classes.has(c,"repeatRemove")&&
-qf.events.addListener(c,"click",qf.Repeat.removeHandler)}b.parentNode.insertBefore(d,b.nextSibling);this.scriptsTpl&&eval(this.scriptsTpl.replace(/:idx:/g,e));if(this.rulesTpl&&(this.form||(this.form=this.findForm()),this.form.validator)){b=eval(this.rulesTpl.replace(/:idx:/g,e));for(a=0;d=b[a];a++)this.form.validator.rules.push(d)}},remove:function(a){if(this.rulesTpl&&(this.form||(this.form=this.findForm()),this.form.validator)){var b=new qf.Map,d=this.findIndex(a),e=this.form.validator.rules,f,
-c;for(c=0;f=this.triggers[c];c++)b.set(f.replace(":idx:",d),!0);for(c=e.length-1;d=e[c];c--)b.hasKey(d.owner)&&e.splice(c,1)}a.parentNode.removeChild(a)}};
+qf.events.addListener(c,"click",qf.Repeat.removeHandler)}b.parentNode.insertBefore(d,b.nextSibling);this.scriptsTpl&&eval(this.scriptsTpl.replace(/:idx:/g,e));if(this.rulesTpl&&(this.form||(this.form=this.findForm()),this.form.validator)){b=eval(this.rulesTpl.replace(/:idx:/g,e));for(a=0;d=b[a];a++)this.form.validator.rules.push(d)}this.onChange()},remove:function(a){if(this.rulesTpl&&(this.form||(this.form=this.findForm()),this.form.validator)){var b=new qf.Map,d=this.findIndex(a),e=this.form.validator.rules,
+f,c;for(c=0;f=this.triggers[c];c++)b.set(f.replace(":idx:",d),!0);for(c=e.length-1;d=e[c];c--)b.hasKey(d.owner)&&e.splice(c,1)}a.parentNode.removeChild(a);this.onChange()},onBeforeAdd:function(){return!0},onBeforeRemove:function(){return!0},onChange:function(){}};
View
35 data/js/quickform-repeat.js
@@ -91,7 +91,7 @@ qf.Repeat.addHandler = function(event)
while (parent && !qf.classes.has(parent, 'repeat')) {
parent = parent.parentNode;
}
- if (parent && parent.repeat) {
+ if (parent && parent.repeat && parent.repeat.onBeforeAdd()) {
parent.repeat.add();
}
event.preventDefault();
@@ -114,7 +114,7 @@ qf.Repeat.removeHandler = function(event)
}
parent = parent.parentNode;
}
- if (parent && parent.repeat) {
+ if (parent && parent.repeat && parent.repeat.onBeforeRemove(item)) {
parent.repeat.remove(item);
}
event.preventDefault();
@@ -265,6 +265,7 @@ qf.Repeat.prototype = {
}
}
}
+ this.onChange();
},
/**
* Removes an item from repeat element
@@ -295,5 +296,35 @@ qf.Repeat.prototype = {
}
}
item.parentNode.removeChild(item);
+ this.onChange();
+ },
+ /**
+ * Called before adding a repeated item.
+ *
+ * If this method returns false, no item will be added
+ *
+ * @returns {Boolean}
+ */
+ onBeforeAdd: function()
+ {
+ return true;
+ },
+ /**
+ * Called before removing a repeated item.
+ *
+ * If this method returns false, the item will not be removed
+ *
+ * @param {Node} item
+ * @returns {Boolean}
+ */
+ onBeforeRemove: function(item)
+ {
+ return true;
+ },
+ /**
+ * Called after adding or deleting the item
+ */
+ onChange: function()
+ {
}
};
View
188 docs/examples/repeat.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Usage example for HTML_QuickForm2 package: repeat element
+ *
+ * $Id$
+ */
+
+require_once 'HTML/QuickForm2.php';
+require_once 'HTML/QuickForm2/Renderer.php';
+
+$form = new HTML_QuickForm2('testRepeat');
+
+$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
+ 'country' => array(4, 706, 180),
+ 'street' => array(
+ 'Secret Taliban caves',
+ 'Pirate hideout. Aaargh!',
+ 'Somewhere in the jungle'
+ ),
+ 'default' => array(true, false, false),
+
+ 'links' => array(
+ 'main' => 'yes_1',
+ 'title' => array('php.net', 'pear.php.net', 'google.com')
+ )
+)));
+
+/* @var $fsOne HTML_QuickForm2_Container_Fieldset */
+$fsOne = $form->addFieldset()->setLabel('Fieldset-based repeat element');
+
+/* @var $repeatFs HTML_QuickForm2_Container_Repeat */
+$repeatFs = $fsOne->addRepeat()
+ ->setPrototype(HTML_QuickForm2_Factory::createElement('fieldset'))
+ ->setId('repeat-fieldset')
+ ->setLabel('Shipping addresses');
+
+$countries = array(
+ '' => "-- please select --",
+ 4 => "Afghanistan",
+ 148 => "Chad",
+ 180 => "Congo, Democratic Republic of",
+ 368 => "Iraq",
+ 706 => "Somalia",
+ 736 => "Sudan",
+ 716 => "Zimbabwe"
+);
+
+$country = $repeatFs->addSelect('country')->loadOptions($countries)->setLabel('Country:');
+$repeatFs->addText('region', array('style' => 'width: 20em;'))->setLabel('Region:');
+$street = $repeatFs->addText('street', array('style' => 'width: 20em;'))->setLabel('Street address:');
+$repeatFs->addCheckbox('default')->setContent('default shipping address');
+// button to remove a repeated item from a repeat
+$repeatFs->addButton('remove', array('type' => 'button'))
+ ->setContent('remove this address')
+ ->addClass('repeatRemove');
+
+// setting rules for repeated elements, these will work properly server-side and client-side
+$country->addRule('required', 'Please select a country', null,
+ HTML_QuickForm2_Rule::ONBLUR_CLIENT_SERVER);
+$street->addRule('required', 'Please input street address', null,
+ HTML_QuickForm2_Rule::ONBLUR_CLIENT_SERVER);
+
+
+/* @var $fsTwo HTML_QuickForm2_Container_Fieldset */
+$fsTwo = $form->addFieldset()->setLabel('Group-based repeat element');
+/* @var $repeatGroup HTML_QuickForm2_Container_Repeat */
+$repeatGroup = $fsTwo->addRepeat(
+ null, array('id' => 'repeat-group'),
+ array('prototype' => HTML_QuickForm2_Factory::createElement('group', 'links')
+ ->setLabel('A link:')->setSeparator('&nbsp;'))
+ )->setIndexField('links[title]') // not strictly necessary, but doesn't hurt either
+ ->setLabel('Links');
+
+$repeatGroup->addText('title', array('style' => 'width: 15em;'));
+// specially crafted value attribute to prevent adding index to name
+$repeatGroup->addRadio('main', array('value' => 'yes_:idx:'))->setContent('main');
+// button to remove a repeated item from a repeat
+$repeatGroup->addButton('remove', array('type' => 'button'))
+ ->setContent('X')
+ ->addClass('repeatRemove');
+
+$form->addSubmit('submit', array('value' => 'Send this form'));
+
+$renderer = HTML_QuickForm2_Renderer::factory('default');
+$form->render($renderer);
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <style type="text/css">
+
+/* Set up custom font and form width */
+body {
+ margin-left: 10px;
+ font-family: Arial,sans-serif;
+ font-size: small;
+}
+
+.quickform {
+ min-width: 500px;
+ max-width: 600px;
+ width: 560px;
+}
+
+/* Use default styles included with the package */
+<?php
+if ('@data_dir@' != '@' . 'data_dir@') {
+ $filename = '@data_dir@/HTML_QuickForm2/quickform.css';
+} else {
+ $filename = dirname(dirname(dirname(__FILE__))) . '/data/quickform.css';
+}
+readfile($filename);
+?>
+
+/* http://www.quirksmode.org/css/clearing.html */
+#repeat-group .repeatItem { overflow: auto; width: 100%; }
+
+/* zebra table for group-based repeat */
+.quickform .repeat .odd { background-color: #FEE; }
+.quickform .repeat .even { background-color: #EEF; }
+
+ </style>
+<?php
+
+// Inline QuickForm's javascript libraries
+echo $renderer->getJavascriptBuilder()->getLibraries(true, true);
+
+?>
+ <title>HTML_QuickForm2 repeat element example</title>
+</head>
+<body>
+<?php
+
+if ($form->validate()) {
+ echo "<pre>\n";
+ var_dump($form->getValue());
+ echo "\n</pre><hr />";
+}
+
+echo $renderer;
+
+?>
+<script type="text/javascript">
+// <![CDATA[
+
+// add event handlers to repeats
+var repeatFs = document.getElementById('repeat-fieldset').repeat,
+ repeatGroup = document.getElementById('repeat-group').repeat;
+
+repeatFs.onBeforeAdd = function()
+{
+ var items = this.getElementsByClass('repeatItem', this.container);
+ // 5 visible items and 1 hidden prototype
+ if (items.length > 5) {
+ alert('5 addresses should be enough for everybody!');
+ return false;
+ }
+ return true;
+};
+
+repeatFs.onBeforeRemove = function(item)
+{
+ var items = this.getElementsByClass('repeatItem', this.container);
+ if (2 == items.length) {
+ alert('You cannot remove the last address');
+ return false;
+ }
+ return true;
+};
+
+repeatGroup.onChange = function()
+{
+ var items = this.getElementsByClass('repeatItem', this.container);
+ for (var i = 1, item; item = items[i]; i++) {
+ qf.classes.add(item, i % 2 ? 'odd' : 'even');
+ qf.classes.remove(item, i % 2 ? 'even' : 'odd');
+ }
+};
+
+// paint zebra for initial values
+repeatGroup.onChange();
+
+// ]]>
+</script>
+</body>
+</html>
View
35 js/src/repeat.js
@@ -91,7 +91,7 @@ qf.Repeat.addHandler = function(event)
while (parent && !qf.classes.has(parent, 'repeat')) {
parent = parent.parentNode;
}
- if (parent && parent.repeat) {
+ if (parent && parent.repeat && parent.repeat.onBeforeAdd()) {
parent.repeat.add();
}
event.preventDefault();
@@ -114,7 +114,7 @@ qf.Repeat.removeHandler = function(event)
}
parent = parent.parentNode;
}
- if (parent && parent.repeat) {
+ if (parent && parent.repeat && parent.repeat.onBeforeRemove(item)) {
parent.repeat.remove(item);
}
event.preventDefault();
@@ -265,6 +265,7 @@ qf.Repeat.prototype = {
}
}
}
+ this.onChange();
},
/**
* Removes an item from repeat element
@@ -295,5 +296,35 @@ qf.Repeat.prototype = {
}
}
item.parentNode.removeChild(item);
+ this.onChange();
+ },
+ /**
+ * Called before adding a repeated item.
+ *
+ * If this method returns false, no item will be added
+ *
+ * @returns {Boolean}
+ */
+ onBeforeAdd: function()
+ {
+ return true;
+ },
+ /**
+ * Called before removing a repeated item.
+ *
+ * If this method returns false, the item will not be removed
+ *
+ * @param {Node} item
+ * @returns {Boolean}
+ */
+ onBeforeRemove: function(item)
+ {
+ return true;
+ },
+ /**
+ * Called after adding or deleting the item
+ */
+ onChange: function()
+ {
}
};
View
2,090 package.xml
1,047 additions, 1,043 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
14 tests/QuickForm2/Container/RepeatTest.php
@@ -92,12 +92,13 @@ public function testPrototypeRequiredForDOMAndOutput()
public function testElementsAreAddedToPrototype()
{
- $repeat = new HTML_QuickForm2_Container_Repeat();
$fieldset = new HTML_QuickForm2_Container_Fieldset();
+ $repeat = new HTML_QuickForm2_Container_Repeat(
+ null, null, array('prototype' => $fieldset)
+ );
$textOne = new HTML_QuickForm2_Element_InputText('firstText');
$textTwo = new HTML_QuickForm2_Element_InputText('secondText');
- $repeat->setPrototype($fieldset);
$repeat->appendChild($textOne);
$this->assertSame($textOne->getContainer(), $fieldset);
@@ -202,5 +203,14 @@ public function testGetValue()
unset($values['foo']['b'], $values['bar']['baz']['b']);
$this->assertEquals($values, $repeat->getValue());
}
+
+ public function testFrozenRepeatShouldNotContainJavascript()
+ {
+ $repeat = new HTML_QuickForm2_Container_Repeat();
+ $repeat->setPrototype(new HTML_QuickForm2_Container_Fieldset());
+ $repeat->toggleFrozen(true);
+
+ $this->assertNotContains('<script', $repeat->__toString());
+ }
}
?>

0 comments on commit bf1aad6

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