cvs: pear /HTML_QuickForm/QuickForm hierselect.php

From: Date: Sun, 17 Jul 2005 19:26:31 +0000
Subject: cvs: pear /HTML_QuickForm/QuickForm hierselect.php
Groups: php.pear.cvs 
Request: Send a blank email to pear-cvs+get-33093@lists.php.net to get a copy of this message
avb Sun Jul 17 15:26:31 2005 EDT Modified files: /pear/HTML_QuickForm/QuickForm hierselect.php Log: Yet Another major rewrite of hierselect element, completely rewritten Javascript. hierselect now should properly handle: * form reset (bug #2970) * page reload (bug #3176) * brackets in element name (bug #3988) * any option values (bug #4432) Also onchange attribute is not overwritten anymore, but appended to (request #3367)

http://cvs.php.net/diff.php/pear/HTML_QuickForm/QuickForm/hierselect.php?r1=1.13&r2=1.14&ty=u Index: pear/HTML_QuickForm/QuickForm/hierselect.php diff -u pear/HTML_QuickForm/QuickForm/hierselect.php:1.13 pear/HTML_QuickForm/QuickForm/hierselect.php:1.14 --- pear/HTML_QuickForm/QuickForm/hierselect.php:1.13 Thu Feb 3 13:57:08 2005 +++ pear/HTML_QuickForm/QuickForm/hierselect.php Sun Jul 17 15:26:30 2005 @@ -15,9 +15,10 @@ // +----------------------------------------------------------------------+ // | Authors: Herim Vasquez <vasquezh@iro.umontreal.ca> | // | Bertrand Mansion <bmansion@mamasam.com> | +// | Alexey Borzov <avb@php.net> // +----------------------------------------------------------------------+ // -// $Id: hierselect.php,v 1.13 2005/02/03 18:57:08 mansion Exp $ +// $Id: hierselect.php,v 1.14 2005/07/17 19:26:30 avb Exp $ require_once('HTML/QuickForm/group.php'); require_once('HTML/QuickForm/select.php'); @@ -97,11 +98,6 @@ * @access private */ var $_js = ''; - - /** - * The javascript array name - */ - var $_jsArrayName = ''; // }}} // {{{ constructor @@ -159,7 +155,6 @@ } $this->_setOptions(); - $this->_setJS(); } // end func setMainOptions // }}} @@ -213,7 +208,6 @@ } $this->_setOptions(); - $this->_setJS(); } // end func setSecOptions // }}} @@ -229,16 +223,14 @@ { $toLoad = ''; foreach (array_keys($this->_elements) AS $key) { - if (eval("return isset(\$this->_options[{$key}]{$toLoad});") ) { - $array = eval("return \$this->_options[{$key}]{$toLoad};"); - if (is_array($array)) { - $select =& $this->_elements[$key]; - $select->_options = array(); - $select->loadArray($array); + $array = eval("return isset(\$this->_options[{$key}]{$toLoad})? \$this->_options[{$key}]{$toLoad}: null;"); + if (is_array($array)) { + $select =& $this->_elements[$key]; + $select->_options = array(); + $select->loadArray($array); - $value = is_array($v = $select->getValue()) ? $v[0] : key($array); - $toLoad .= '[\''.$value.'\']'; - } + $value = is_array($v = $select->getValue()) ? $v[0] : key($array); + $toLoad .= '[\'' . str_replace(array('\\', '\''), array('\\\\', '\\\''), $value) . '\']'; } } } // end func _setOptions @@ -279,147 +271,208 @@ } // end func _createElements // }}} - // {{{ _setJS() - - /** - * Set the JavaScript for each select element (excluding de main one). - * - * @access private - * @return void - */ - function _setJS() - { - $this->_js = $js = ''; - $this->_jsArrayName = 'hs_' . str_replace(array('[', ']'), '_', $this->getName()); - for ($i = 1; $i < $this->_nbElements; $i++) { - $this->_setJSArray($this->_jsArrayName, $this->_options[$i], $js); - } - } // end func _setJS - - // }}} - // {{{ _setJSArray() - - /** - * Recursively builds the JavaScript array defining the options that a select - * element can have. - * - * @param string $grpName Group Name attribute - * @param array $options Select element options - * @param string $js JavaScript definition is build using this variable - * @param string $optValue The value for the current JavaScript option - * - * @access private - * @return void - */ - function _setJSArray($grpName, $options, &$js, $optValue = '') - { - if (is_array($options)) { - $js = ''; - // For a hierselect containing 3 elements: - // if option 1 has been selected for the 1st element - // and option 3 has been selected for the 2nd element, - // then the javascript array containing the values to load - // on the 3rd element will have the following name: grpName_1_3 - $name = ($optValue === '') ? $grpName : $grpName.'_'.$optValue; - foreach($options AS $k => $v) { - $this->_setJSArray($name, $v, $js, $k); - } - - // if $js !== '' add it to the JavaScript - $this->_js .= ($js !== '') ? $name." = {\n".$js."\n}\n" : ''; - $js = ''; - } else { - // $js empty means that we are adding the first element to the JavaScript. - if ($js != '') { - $js .= ",\n"; - } - $js .= '"'.$optValue.'":"'.$options.'"'; - } - } - - // }}} // {{{ toHtml() - /** - * Returns Html for the group - * - * @access public - * @return string - */ function toHtml() { - if ($this->_flagFrozen) { - $this->_js = ''; - } else { - // set the onchange attribute for each element - $keys = array_keys($this->_elements); - $nbElements = count($keys); - $nbElementsUsingFnc = $nbElements - 1; // last element doesn't need it - for ($i = 0; $i < $nbElementsUsingFnc; $i++) { + $this->_js = ''; + if (!$this->_flagFrozen) { + // set the onchange attribute for each element except last + $keys = array_keys($this->_elements); + $onChange = array(); + for ($i = 0; $i < count($keys) - 1; $i++) { $select =& $this->_elements[$keys[$i]]; + $onChange[$i] = $select->getAttribute('onchange'); $select->updateAttributes( - array('onChange' => 'swapOptions(this, \''.$this->getName().'\', '.$keys[$i].', '.$nbElements.', \''.$this->_jsArrayName.'\');') + array('onchange' => '_hs_swapOptions(this.form, \'' . $this->_escapeString($this->getName()) . '\', ' . $keys[$i] . ');' . $onChange[$i]) ); } // create the js function to call if (!defined('HTML_QUICKFORM_HIERSELECT_EXISTS')) { - $this->_js .= "function swapOptions(frm, grpName, eleIndex, nbElements, arName)\n" - ."{\n" - ." var n = \"\";\n" - ." var ctl;\n\n" - ." for (var i = 0; i < nbElements; i++) {\n" - ." ctl = frm.form[grpName+'['+i+']'];\n" - ." if (!ctl) {\n" - ." ctl = frm.form[grpName+'['+i+'][]'];\n" - ." }\n" - ." if (i <= eleIndex) {\n" - ." n += \"_\"+ctl.value;\n" - ." } else {\n" - ." ctl.length = 0;\n" - ." }\n" - ." }\n\n" - ." var t = eval(\"typeof(\"+arName + n +\")\");\n" - ." if (t != 'undefined') {\n" - ." var the_array = eval(arName+n);\n" - ." var j = 0;\n" - ." n = eleIndex + 1;\n" - ." ctl = frm.form[grpName+'['+ n +']'];\n" - ." if (!ctl) {\n" - ." ctl = frm.form[grpName+'['+ n +'][]'];\n" - ." }\n" - ." for (var i in the_array) {\n" - ." opt = new Option(the_array[i], i, false, false);\n" - ." ctl.options[j++] = opt;\n" - ." }\n" - ." }\n" - ." if (eleIndex+1 < nbElements) {\n" - ." swapOptions(frm, grpName, eleIndex+1, nbElements, arName);\n" - ." }\n" - ."}\n"; + $this->_js .= <<<JAVASCRIPT +function _hs_findOptions(ary, keys) +{ + var key = keys.shift(); + if (!key in ary) { + return {}; + } else if (0 == keys.length) { + return ary[key]; + } else { + return _hs_findOptions(ary[key], keys); + } +} + +function _hs_findSelect(form, groupName, selectIndex) +{ + if (groupName+'['+ selectIndex +']' in form) { + return form[groupName+'['+ selectIndex +']']; + } else { + return form[groupName+'['+ selectIndex +'][]']; + } +} + +function _hs_replaceOptions(ctl, optionList) +{ + var j = 0; + ctl.options.length = 0; + for (i in optionList) { + ctl.options[j++] = new Option(optionList[i], i, false, false); + } +} + +function _hs_setValue(ctl, value) +{ + var testValue = {}; + if (value instanceof Array) { + for (var i = 0; i < value.length; i++) { + testValue[value[i]] = true; + } + } else { + testValue[value] = true; + } + for (var i = 0; i < ctl.options.length; i++) { + if (ctl.options[i].value in testValue) { + ctl.options[i].selected = true; + } + } +} + +function _hs_getValues(form, groupName) +{ + var ret = []; + for (var i = 0; i <= _hs_options[groupName].length; i++) { + var ctl = _hs_findSelect(form, groupName, i); + if ('select-multiple' == ctl.type) { + ret[i] = []; + for (var j = 0; j < ctl.options.length; j++) { + if (ctl.options[j].selected) { + ret[i].push(ctl.options[j].value); + } + } + } else { + ret[i] = ctl.value; + } + } + return ret; +} + +function _hs_swapOptions(form, groupName, selectIndex) +{ + var hsValue = []; + for (var i = 0; i <= selectIndex; i++) { + hsValue[i] = _hs_findSelect(form, groupName, i).value; + } + + _hs_replaceOptions(_hs_findSelect(form, groupName, selectIndex + 1), + _hs_findOptions(_hs_options[groupName][selectIndex], hsValue)); + if (selectIndex + 1 < _hs_options[groupName].length) { + _hs_swapOptions(form, groupName, selectIndex + 1); + } +} + +function _hs_onReset(form, groupName) +{ + var currVal = _hs_getValues(form, groupName); + var unchanged = true; + for (var i = 0; i < currVal.length; i++) { + if (currVal[i].toString() != _hs_values[groupName][i].toString()) { + unchanged = false; + break; + } + } + if (unchanged) { + return true; + } + + for (i = 0; i <= _hs_options[groupName].length; i++) { + _hs_setValue(_hs_findSelect(form, groupName, i), _hs_defaults[groupName][i]); + if (i < _hs_options[groupName].length) { + _hs_replaceOptions(_hs_findSelect(form, groupName, i + 1), + _hs_findOptions(_hs_options[groupName][i], _hs_defaults[groupName].slice(0, i + 1))); + } + } +} + +function _hs_setupOnReset(form, groupName) +{ + _hs_values[groupName] = _hs_getValues(form, groupName); + setTimeout(function() { _hs_onReset(form, groupName); }, 50); +} + +function _hs_onReload() +{ + var ctl; + for (var i = 0; i < document.forms.length; i++) { + for (var j in _hs_defaults) { + if (ctl = _hs_findSelect(document.forms[i], j, 0)) { + for (var k = 0; k < _hs_defaults[j].length; k++) { + _hs_setValue(_hs_findSelect(document.forms[i], j, k), _hs_defaults[j][k]); + } + } + } + } + + if (_hs_prevOnload) { + _hs_prevOnload(); + } +} + +var _hs_prevOnload = null; +if (window.onload) { + _hs_prevOnload = window.onload; +} +window.onload = _hs_onReload; + +var _hs_options = {}; +var _hs_defaults = {}; +var _hs_values = {}; + +JAVASCRIPT; define('HTML_QUICKFORM_HIERSELECT_EXISTS', true); } + // option lists + $jsParts = array(); + for ($i = 1; $i < $this->_nbElements; $i++) { + $jsParts[] = $this->_convertArrayToJavascript($this->_options[$i]); + } + $this->_js .= "\n_hs_options['" . $this->_escapeString($this->getName()) . "'] = [\n" . + implode(",\n", $jsParts) . + "\n];\n"; + // default value; if we don't actually have any values yet just use + // the first option (for single selects) or empty array (for multiple) + $values = array(); + foreach (array_keys($this->_elements) as $key) { + if (is_array($v = $this->_elements[$key]->getValue())) { + $values[] = count($v) > 1? $v: $v[0]; + } else { + // XXX: accessing the supposedly private _options array + $values[] = $this->_elements[$key]->getMultiple() || empty($this->_elements[$key]->_options[0])? + array(): + $this->_elements[$key]->_options[0]['attr']['value']; + } + } + $this->_js .= "_hs_defaults['" . $this->_escapeString($this->getName()) . "'] = " . + $this->_convertArrayToJavascript($values, false) . ";\n"; } include_once('HTML/QuickForm/Renderer/Default.php'); $renderer =& new HTML_QuickForm_Renderer_Default(); $renderer->setElementTemplate('{element}'); parent::accept($renderer); - return "<script type=\"text/javascript\">\n//<![CDATA[\n" . $this->_js . "//]]>\n</script>" . + + if (!empty($onChange)) { + $keys = array_keys($this->_elements); + for ($i = 0; $i < count($keys) - 1; $i++) { + $this->_elements[$keys[$i]]->updateAttributes(array('onchange' => $onChange[$i])); + } + } + return (empty($this->_js)? '': "<script type=\"text/javascript\">\n//<![CDATA[\n" . $this->_js . "//]]>\n</script>") . $renderer->toHtml(); } // end func toHtml // }}} // {{{ accept() - /** - * Accepts a renderer - * - * @param object An HTML_QuickForm_Renderer object - * @param bool Whether a group is required - * @param string An error message associated with a group - * @access public - * @return void - */ function accept(&$renderer, $required = false, $error = null) { $renderer->renderElement($this, $required, $error); @@ -435,10 +488,94 @@ // matches the main option return HTML_QuickForm_element::onQuickFormEvent($event, $arg, $caller); } else { - return parent::onQuickFormEvent($event, $arg, $caller); + $ret = parent::onQuickFormEvent($event, $arg, $caller); + // add onreset handler to form to properly reset hierselect (see bug #2970) + if ('addElement' == $event) { + $caller->updateAttributes(array('onreset' => "if (typeof _hs_setupOnReset != 'undefined') { _hs_setupOnReset(this, '" . $this->_escapeString($this->getName()) . "'); } " . $caller->getAttribute('onreset'))); + } + return $ret; } } // end func onQuickFormEvent - // }}} + // }}} + // {{{ _convertArrayToJavascript() + + /** + * Converts PHP array to its Javascript analog + * + * @access private + * @param array PHP array to convert + * @param bool Generate Javascript object literal (default, works like PHP's associative array) or array literal + * @return string Javascript representation of the value + */ + function _convertArrayToJavascript($array, $assoc = true) + { + if (!is_array($array)) { + return $this->_convertScalarToJavascript($array); + } else { + $items = array(); + foreach ($array as $key => $val) { + $item = $assoc? "'" . $this->_escapeString($key) . "': ": ''; + if (is_array($val)) { + $item .= $this->_convertArrayToJavascript($val, $assoc); + } else { + $item .= $this->_convertScalarToJavascript($val); + } + $items[] = $item; + } + } + $js = implode(', ', $items); + return $assoc? '{ ' . $js . ' }': '[' . $js . ']'; + } + + // }}} + // {{{ _convertScalarToJavascript() + + /** + * Converts PHP's scalar value to its Javascript analog + * + * @access private + * @param mixed PHP value to convert + * @return string Javascript representation of the value + */ + function _convertScalarToJavascript($val) + { + if (is_bool($val)) { + return $val ? 'true' : 'false'; + } elseif (is_int($val) || is_double($val)) { + return $val; + } elseif (is_string($val)) { + return "'" . $this->_escapeString($val) . "'"; + } elseif (is_null($val)) { + return 'null'; + } else { + // don't bother + return '{}'; + } + } + + // }}} + // {{{ _escapeString() + + /** + * Quotes the string so that it can be used in Javascript string constants + * + * @access private + * @param string + * @return string + */ + function _escapeString($str) + { + return strtr($str,array( + "\r" => '\r', + "\n" => '\n', + "\t" => '\t', + "'" => "\\'", + '"' => '\"', + '\\' => '\\\\' + )); + } + + // }}} } // end class HTML_QuickForm_hierselect ?> \ No newline at end of file
« previous php.pear.cvs (#33093) next »