Package home | Report new bug | New search | Development Roadmap Status: Open | Feedback | All | Closed Since Version 2.0.2

Request #1283 Feature request: Optgroup support
Submitted: 2004-04-26 04:35 UTC Modified: 2010-05-24 20:14 UTC
From: boci at dravanet dot hu Assigned: avb
Status: Closed Package: HTML_QuickForm2
PHP Version: Irrelevant OS: Linux
Roadmaps: 0.1.0    
Subscription  


 [2004-04-26 04:35 UTC] boci at dravanet dot hu
Description: ------------ Hi! I needed optgtoup support for the Select element. It's possible?

Comments

 [2004-06-09 09:41 UTC] avb
Marking the stuff as feature request.
 [2005-01-13 08:00 UTC] joao at cbpf dot br
I´m looking for this too.
 [2005-03-31 07:26 UTC] no at mail dot com
Optgroup support with recursion would be great. (Like smarty's html_options).
 [2005-09-28 08:52 UTC] chris dot yates1 at btconnect dot com
Here's how I did it, using an array for the options; recursive optgroups is supported. There are changes to the addOption and toHtml functions, and the addition of the _optionToHtml function. The code below shows the changes with the original code commented out. Hope this helps // CLY - modification to allow optgroups // function addOption($text, $value, $attributes=null) function addOption($text, $value, $attributes=null, &$optGroup=null) { // if text is an array, start an optgroup if (is_array($text)) { if (is_array($optGroup)) { $optGroup[$value]['options'] = array(); $optGroup =& $optGroup[$value]['options']; } else { $this->_options[$value]['options'] = array(); $optGroup =& $this->_options[$value]['options']; }; foreach($text as $key=>$val) { $this->addOption($val, $key, null, $optGroup); } // foreach // done all the options in the optgroup return; } // end mod if (null === $attributes) { $attributes = array('value' => $value); } else { $attributes = $this->_parseAttributes($attributes); if (isset($attributes['selected'])) { // the 'selected' attribute will be set in toHtml() $this->_removeAttr('selected', $attributes); if (is_null($this->_values)) { $this->_values = array($value); } elseif (!in_array($value, $this->_values)) { $this->_values[] = $value; } } $this->_updateAttrArray($attributes, array('value' => $value)); } // CLY - modification to allow optgroups // if $optGroup is an array, add the option to it if (is_array($optGroup)) { $optGroup[$text]['attr'] = $attributes; } // if $optGroup is a string, add the option to the option group // used if directly adding an option to an optgroup elseif (is_string($optGroup)) { $optGroups = explode($optGroup, ','); $target =& $this->_options; foreach($optGroups as $group) { // create the option group if it does not exist if (empty($target[$group]['options'])) { $target[$group]['options'] = array(); } $target =& $target[$group]['options']; } // foreach // add the option $target[$text]['attr'] = $attributes; } // else if there are attributes, add them to the option elseif (is_array($attributes)) { $this->_options[$text]['attr'] = $attributes; } // $this->_options[] = array('text' => $text, 'attr' => $attributes); // end mod } // end func addOption function toHtml() { if ($this->_flagFrozen) { return $this->getFrozenHtml(); } else { $tabs = $this->_getTabs(); $strHtml = ''; if ($this->getComment() != '') { $strHtml .= $tabs . '<!-- ' . $this->getComment() . " //-->\n"; } if (!$this->getMultiple()) { $attrString = $this->_getAttrString($this->_attributes); } else { $myName = $this->getName(); $this->setName($myName . '[]'); $attrString = $this->_getAttrString($this->_attributes); $this->setName($myName); } $strHtml .= $tabs . '<select' . $attrString . ">\n"; // CLY - modified to allow optgroups foreach ($this->_options as $text=>$option) { $strHtml .= $tabs . $this->_optionToHtml($text, $option); } /* foreach ($this->_options as $option) { if (is_array($this->_values) && in_array((string)$option['attr']['value'], $this->_values)) { $this->_updateAttrArray($option['attr'], array('selected' => 'selected')); } $strHtml .= $tabs . "\t<option" . $this->_getAttrString($option['attr']) . '>' . $option['text'] . "</option>\n"; } */ // end mod return $strHtml . $tabs . '</select>'; } } //end func toHtml // CLY - new function to allow optgroups /** * Returns an OPTION in HTML * * This function is called recursively to support optgroups * * @param string $text Display text for the option * @param array $option The option * @since ?? * @access private * @return string */ // Creates the HTML for an option function _optionToHtml($text, $option) { $tabs = $this->_getTabs(); // if an option has options it's an optgroup if (isset($option['options'])) { $strHtml = $tabs . "<optgroup label=\"$text\">\n"; foreach($option['options'] as $txt=>$opt) { $strHtml .= $tabs . $this->_optionToHtml($txt, $opt); } // foreach $strHtml .= $tabs . "</optgroup>\n"; return($strHtml); } // else it's an option else { if (is_array($this->_values) && in_array((string)$option['attr']['value'], $this->_values)) { $this->_updateAttrArray($option['attr'], array('selected' => 'selected')); } return("\t<option" . $this->_getAttrString($option['attr']) . ">$text</option>\n"); } } And here's an array of UK county names that shows two levels of optgroups. Just pass $ukCounties as the options to a select as modified above. $ukCounties = array( '0' => '-Select-', 'England' => array( 'B' => array( 'Bedfordshire' => 'Bedfordshire', 'Berkshire' => 'Berkshire', 'Buckinghamshire' => 'Buckinghamshire', ), 'C' => array( 'Cambridgeshire' => 'Cambridgeshire', 'Cheshire' => 'Cheshire', 'Cornwall' => 'Cornwall', 'Cumberland' => 'Cumberland', ), 'D' => array( 'Derbyshire' => 'Derbyshire', 'Devon' => 'Devon', 'Dorset' => 'Dorset', 'Durham' => 'Durham', ), 'E' => array( 'Essex' => 'Essex', ), 'G' => array( 'Gloucestershire' => 'Gloucestershire', ), 'H' => array( 'Hampshire' => 'Hampshire', 'Herefordshire' => 'Herefordshire', 'Hertfordshire' => 'Hertfordshire', 'Huntingdonshire' => 'Huntingdonshire', ), 'K' => array( 'Kent' => 'Kent', ), 'L' => array( 'Lancashire' => 'Lancashire', 'Leicestershire' => 'Leicestershire', 'Lincolnshire' => 'Lincolnshire', ), 'M' => array( 'Middlesex' => 'Middlesex', ), 'N' => array( 'Norfolk' => 'Norfolk', 'Northamptonshire' => 'Northamptonshire', 'Northumberland' => 'Northumberland', 'Nottinghamshire' => 'Nottinghamshire', ), 'O' => array( 'Oxfordshire' => 'Oxfordshire', ), 'R' => array( 'Rutland' => 'Rutland', ), 'S' => array( 'Shropshire' => 'Shropshire', 'Somerset' => 'Somerset', 'Staffordshire' => 'Staffordshire', 'Suffolk' => 'Suffolk', 'Surrey' => 'Surrey', 'Sussex' => 'Sussex', ), 'W' => array( 'Warwickshire' => 'Warwickshire', 'Westmorland' => 'Westmorland', 'Wiltshire' => 'Wiltshire', 'Worcestershire' => 'Worcestershire', ), 'Y' => array( 'Yorkshire' => 'Yorkshire', ), ), 'Scotland' => array( 'A' => array( 'Aberdeenshire' => 'Aberdeenshire', 'Angus' => 'Angus', 'Argyllshire' => 'Argyllshire', 'Ayrshire' => 'Ayrshire', ), 'B' => array( 'Banffshire' => 'Banffshire', 'Berwickshire' => 'Berwickshire', 'Buteshire' => 'Buteshire', ), 'C' => array( 'Cromartyshire' => 'Cromartyshire', 'Caithness' => 'Caithness', 'Clackmannanshire' => 'Clackmannanshire', ), 'D' => array( 'Dumfriesshire' => 'Dumfriesshire', 'Dunbartonshire' => 'Dunbartonshire', ), 'E' => array( 'East Lothian' => 'East Lothian', ), 'F' => array( 'Fife' => 'Fife', ), 'I' => array( 'Inverness-shire' => 'Inverness-shire', ), 'K' => array( 'Kincardineshire' => 'Kincardineshire', 'Kinross-shire' => 'Kinross-shire', 'Kirkcudbrightshire' => 'Kirkcudbrightshire', ), 'L' => array( 'Lanarkshire' => 'Lanarkshire', ), 'M' => array( 'Midlothian' => 'Midlothian', 'Morayshire' => 'Morayshire', ), 'N' => array( 'Nairnshire' => 'Nairnshire', ), 'O' => array( 'Orkney' => 'Orkney', ), 'P' => array( 'Peeblesshire' => 'Peeblesshire', 'Perthshire' => 'Perthshire', ), 'R' => array( 'Renfrewshire' => 'Renfrewshire', 'Ross-shire' => 'Ross-shire', 'Roxburghshire' => 'Roxburghshire', ), 'S' => array( 'Selkirkshire' => 'Selkirkshire', 'Shetland' => 'Shetland', 'Stirlingshire' => 'Stirlingshire', 'Sutherland' => 'Sutherland', ), 'W' => array( 'West Lothian' => 'West Lothian', 'Wigtownshire' => 'Wigtownshire', ), ), 'Ulster' => array( 'County Antrim' => 'County Antrim', 'County Armagh' => 'County Armagh', 'County Down' => 'County Down', 'County Fermanagh' => 'County Fermanagh', 'County Tyrone' => 'County Tyrone', 'County Londonderry' => 'County Londonderry', ), 'Wales' => array( 'A' => array( 'Anglesey' => 'Anglesey', ), 'B' => array( 'Brecknockshire' => 'Brecknockshire', ), 'C' => array( 'Caernarfonshire' => 'Caernarfonshire', 'Carmarthenshire' => 'Carmarthenshire', 'Cardiganshire' => 'Cardiganshire', ), 'D' => array( 'Denbighshire' => 'Denbighshire', ), 'F' => array( 'Flintshire' => 'Flintshire', ), 'G' => array( 'Glamorgan' => 'Glamorgan', ), 'M' => array( 'Merioneth' => 'Merioneth', 'Monmouthshire' => 'Monmouthshire', 'Montgomeryshire' => 'Montgomeryshire', ), 'P' => array( 'Pembrokeshire' => 'Pembrokeshire', ), 'R' => array( 'Radnorshire' => 'Radnorshire', ), ), );
 [2006-05-24 23:32 UTC] walter_tom at hotmail dot com (Tom)
Hi I came across a bug when using Chris Yates' modification to allow optgroups. The exportValues function errors when it tries to check the submitted values against the _options array to see if it is clean. I came up with this workaround. Warning, this has not been thoroughly tested at all! Modify the exportValues function, from line 651, replacing the stuff which is commented out here: // CLY - modified to allow optgroups if ($this->_isInOptGroup($v, $this->_options)) { $cleanValue[] = $v; } // for ($i = 0, $optCount = count($this->_options); $i < $optCount; $i++) { // if ($v == $this->_options[$i]['attr']['value']) { // $cleanValue[] = $v; // break; // } // } // end mod Also, add this function: // CLY - new function to allow optgroups function _isInOptGroup($v, $opts) { $isInOptGroup = false; foreach ($opts as $opt) { if (isset($opt['options'])) { $isInOptGroup = $this->_isInOptGroup($v, $opt['options']); } else { if ($v == $opt['attr']['value']) { $isInOptGroup = true; } } } return $isInOptGroup; }
 [2006-05-25 00:38 UTC] walter_tom at hotmail dot com (Tom)
OOPS! Yes, as I said, the aforementioned code is not really tested at all, and I discovered soon after posting there was an error. The _isInOptGroup function from the above code should be replaced with this: function _isInOptGroup($v, $opts) { $isInOptGroup = false; foreach ($opts as $opt) { if (isset($opt['options'])) { $isInOptGroup = $this->_isInOptGroup($v, $opt['options']); } else { if ($v == $opt['attr']['value']) { $isInOptGroup = true; } } if ($isInOptGroup) break; } return $isInOptGroup; } The break; clause is the bit that changed. Sorry. Use at your own risk.
 [2006-06-08 08:14 UTC] avb (Alexey Borzov)
Moving feature requests to HTML_QuickForm2.
 [2006-10-18 16:07 UTC] avb (Alexey Borzov)
Implemented in CVS. The new HTML_QuickForm2_Element_Select class supports optgroups.
 [2007-04-20 05:05 UTC] banquette (Alex Desktop)
Hi all ! Could this fix be implemented in the current CVS of HTML_QuickForm. I ve looked at http://cvs.php.net/viewvc.cgi/pear/HTML_QuickForm/QuickForm/select.php?view=log and there is nothing about '<optgroup>' tag in select.php file yet. Many thanks for your help !!
 [2007-04-20 05:44 UTC] avb (Alexey Borzov)
No feature additions will be done to HTML_QuickForm, all new features go to HTML_QuickForm2.
 [2009-10-17 08:31 UTC] mrfelton (Tom Kirkpatrick)
How about a patch for Quickform 1 for those of us who can't upgrade to Quickform2 yet. It looks like it is possible by using one of or a combination of the hacks above, but I'm unclear on which bit of which hack to take. Perhaps Chris Yates or Tom Walter can throw together a patch and post it up here?
 [2009-10-17 09:24 UTC] mrfelton (Tom Kirkpatrick)
 [2009-10-17 09:25 UTC] mrfelton (Tom Kirkpatrick)
So I roled up the modifications from the comments above and created a patch. Seems to be working well for me. Thanks.
 [2010-04-06 15:51 UTC] mattaa (matt ab)
Thanks for the patch, it works great! please include it in trunk
 [2010-05-24 20:14 UTC] xolphin (Maarten Bremer)
The patch breaks support for static elements. The _options array is different, but the getFrozenHtml() funtion hasn't changed. for ($i = 0, $optCount = count($this->_options); $i < $optCount; $i++) { if (0 == strcmp($val, $this->_options[$i]['attr']['value'])) { $value[$key] = $this->_options[$i]['text']; break; } } should be modified to something like foreach ($this->_options as $keyoption => $keyvalue) { if (0 == strcmp($val, $keyvalue['attr']['value'])) { $value[$key] = $keyoption; break; } }
 [2010-07-06 07:41 UTC] raziel057 (Thomas Lallement)
In getFrozenHtml() I suggest to replace: for ($i = 0, $optCount = count($this->_options); $i < $optCount; $i++) { if (0 == strcmp($val, $this->_options[$i]['attr']['value'])) { $value[$key] = $this->_options[$i]['text']; break; } } By foreach ($this->_options as $text=>$option) { if (($frozenVal = $this->_optionToFrozen($text, $option)) !== null) { $value[$key] = $frozenVal; break; } } And add the folowing function: function _optionToFrozen($text, $option) { // if an option has options it's an optgroup if (isset($option['options'])) { foreach($option['options'] as $txt=>$opt) { if (($value = $this->_optionToFrozen($txt, $opt)) !== null) { return $value; } } } // else it's an option else { if (is_array($this->_values) && in_array((string)$option['attr']['value'], $this->_values)) { return $text; } return null; } } But for me, it's not interresting to change key => values because it can be dangerous. For example if we have this: $options = array( 2 => 'Autres', 3 => 'Bruxelles - Abelag', 4 => 'Bruxelles - Abelag', 5 => 'Bruxelles - Zaventem (civil)', 6 => 'Charleroi'); option Id 3 will be lost because the value of element 3 is the same as value of element 4. So the value become a key and is crushed.