hierselect.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4. * Hierarchical select element
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * LICENSE: This source file is subject to version 3.01 of the PHP license
  9. * that is available through the world-wide-web at the following URI:
  10. * http://www.php.net/license/3_01.txt If you did not receive a copy of
  11. * the PHP License and are unable to obtain it through the web, please
  12. * send a note to license@php.net so we can mail you a copy immediately.
  13. *
  14. * @category HTML
  15. * @package HTML_QuickForm
  16. * @author Herim Vasquez <vasquezh@iro.umontreal.ca>
  17. * @author Bertrand Mansion <bmansion@mamasam.com>
  18. * @author Alexey Borzov <avb@php.net>
  19. * @copyright 2001-2009 The PHP Group
  20. * @license http://www.php.net/license/3_01.txt PHP License 3.01
  21. * @version CVS: $Id: hierselect.php,v 1.20 2009/04/04 21:34:03 avb Exp $
  22. * @link http://pear.php.net/package/HTML_QuickForm
  23. */
  24. /**
  25. * Hierarchical select element
  26. *
  27. * Class to dynamically create two or more HTML Select elements
  28. * The first select changes the content of the second select and so on.
  29. * This element is considered as a group. Selects will be named
  30. * groupName[0], groupName[1], groupName[2]...
  31. *
  32. * @category HTML
  33. * @package HTML_QuickForm
  34. * @author Herim Vasquez <vasquezh@iro.umontreal.ca>
  35. * @author Bertrand Mansion <bmansion@mamasam.com>
  36. * @author Alexey Borzov <avb@php.net>
  37. * @version Release: 3.2.11
  38. * @since 3.1
  39. */
  40. class HTML_QuickForm_hierselect extends HTML_QuickForm_group
  41. {
  42. // {{{ properties
  43. /**
  44. * Options for all the select elements
  45. *
  46. * @see setOptions()
  47. * @var array
  48. * @access private
  49. */
  50. var $_options = array();
  51. /**
  52. * Number of select elements on this group
  53. *
  54. * @var int
  55. * @access private
  56. */
  57. var $_nbElements = 0;
  58. /**
  59. * The javascript used to set and change the options
  60. *
  61. * @var string
  62. * @access private
  63. */
  64. var $_js = '';
  65. // }}}
  66. // {{{ constructor
  67. /**
  68. * Class constructor
  69. *
  70. * @param string $elementName (optional)Input field name attribute
  71. * @param string $elementLabel (optional)Input field label in form
  72. * @param mixed $attributes (optional)Either a typical HTML attribute string
  73. * or an associative array. Date format is passed along the attributes.
  74. * @param mixed $separator (optional)Use a string for one separator,
  75. * use an array to alternate the separators.
  76. * @access public
  77. * @return void
  78. */
  79. function HTML_QuickForm_hierselect($elementName=null, $elementLabel=null, $attributes=null, $separator=null)
  80. {
  81. $this->HTML_QuickForm_element($elementName, $elementLabel, $attributes);
  82. $this->_persistantFreeze = true;
  83. if (isset($separator)) {
  84. $this->_separator = $separator;
  85. }
  86. $this->_type = 'hierselect';
  87. $this->_appendName = true;
  88. } //end constructor
  89. // }}}
  90. // {{{ setOptions()
  91. /**
  92. * Initialize the array structure containing the options for each select element.
  93. * Call the functions that actually do the magic.
  94. *
  95. * Format is a bit more complex than for a simple select as we need to know
  96. * which options are related to the ones in the previous select:
  97. *
  98. * Ex:
  99. * <code>
  100. * // first select
  101. * $select1[0] = 'Pop';
  102. * $select1[1] = 'Classical';
  103. * $select1[2] = 'Funeral doom';
  104. *
  105. * // second select
  106. * $select2[0][0] = 'Red Hot Chil Peppers';
  107. * $select2[0][1] = 'The Pixies';
  108. * $select2[1][0] = 'Wagner';
  109. * $select2[1][1] = 'Strauss';
  110. * $select2[2][0] = 'Pantheist';
  111. * $select2[2][1] = 'Skepticism';
  112. *
  113. * // If only need two selects
  114. * // - and using the deprecated functions
  115. * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:');
  116. * $sel->setMainOptions($select1);
  117. * $sel->setSecOptions($select2);
  118. *
  119. * // - and using the new setOptions function
  120. * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:');
  121. * $sel->setOptions(array($select1, $select2));
  122. *
  123. * // If you have a third select with prices for the cds
  124. * $select3[0][0][0] = '15.00$';
  125. * $select3[0][0][1] = '17.00$';
  126. * // etc
  127. *
  128. * // You can now use
  129. * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:');
  130. * $sel->setOptions(array($select1, $select2, $select3));
  131. * </code>
  132. *
  133. * @param array $options Array of options defining each element
  134. * @access public
  135. * @return void
  136. */
  137. function setOptions($options)
  138. {
  139. $this->_options = $options;
  140. if (empty($this->_elements)) {
  141. $this->_nbElements = count($this->_options);
  142. $this->_createElements();
  143. } else {
  144. // setDefaults has probably been called before this function
  145. // check if all elements have been created
  146. $totalNbElements = count($this->_options);
  147. for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) {
  148. $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes());
  149. $this->_nbElements++;
  150. }
  151. }
  152. $this->_setOptions();
  153. } // end func setMainOptions
  154. // }}}
  155. // {{{ setMainOptions()
  156. /**
  157. * Sets the options for the first select element. Deprecated. setOptions() should be used.
  158. *
  159. * @param array $array Options for the first select element
  160. *
  161. * @access public
  162. * @deprecated Deprecated since release 3.2.2
  163. * @return void
  164. */
  165. function setMainOptions($array)
  166. {
  167. $this->_options[0] = $array;
  168. if (empty($this->_elements)) {
  169. $this->_nbElements = 2;
  170. $this->_createElements();
  171. }
  172. } // end func setMainOptions
  173. // }}}
  174. // {{{ setSecOptions()
  175. /**
  176. * Sets the options for the second select element. Deprecated. setOptions() should be used.
  177. * The main _options array is initialized and the _setOptions function is called.
  178. *
  179. * @param array $array Options for the second select element
  180. *
  181. * @access public
  182. * @deprecated Deprecated since release 3.2.2
  183. * @return void
  184. */
  185. function setSecOptions($array)
  186. {
  187. $this->_options[1] = $array;
  188. if (empty($this->_elements)) {
  189. $this->_nbElements = 2;
  190. $this->_createElements();
  191. } else {
  192. // setDefaults has probably been called before this function
  193. // check if all elements have been created
  194. $totalNbElements = 2;
  195. for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) {
  196. $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes());
  197. $this->_nbElements++;
  198. }
  199. }
  200. $this->_setOptions();
  201. } // end func setSecOptions
  202. // }}}
  203. // {{{ _setOptions()
  204. /**
  205. * Sets the options for each select element
  206. *
  207. * @access private
  208. * @return void
  209. */
  210. function _setOptions()
  211. {
  212. $toLoad = '';
  213. foreach (array_keys($this->_elements) AS $key) {
  214. $array = eval("return isset(\$this->_options[{$key}]{$toLoad})? \$this->_options[{$key}]{$toLoad}: null;");
  215. if (is_array($array)) {
  216. $select =& $this->_elements[$key];
  217. $select->_options = array();
  218. $select->loadArray($array);
  219. $value = is_array($v = $select->getValue()) ? $v[0] : key($array);
  220. $toLoad .= '[\'' . str_replace(array('\\', '\''), array('\\\\', '\\\''), $value) . '\']';
  221. }
  222. }
  223. } // end func _setOptions
  224. // }}}
  225. // {{{ setValue()
  226. /**
  227. * Sets values for group's elements
  228. *
  229. * @param array $value An array of 2 or more values, for the first,
  230. * the second, the third etc. select
  231. *
  232. * @access public
  233. * @return void
  234. */
  235. function setValue($value)
  236. {
  237. // fix for bug #6766. Hope this doesn't break anything more
  238. // after bug #7961. Forgot that _nbElements was used in
  239. // _createElements() called in several places...
  240. $this->_nbElements = max($this->_nbElements, count($value));
  241. parent::setValue($value);
  242. $this->_setOptions();
  243. } // end func setValue
  244. // }}}
  245. // {{{ _createElements()
  246. /**
  247. * Creates all the elements for the group
  248. *
  249. * @access private
  250. * @return void
  251. */
  252. function _createElements()
  253. {
  254. for ($i = 0; $i < $this->_nbElements; $i++) {
  255. $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes());
  256. }
  257. } // end func _createElements
  258. // }}}
  259. // {{{ toHtml()
  260. function toHtml()
  261. {
  262. $this->_js = '';
  263. if (!$this->_flagFrozen) {
  264. // set the onchange attribute for each element except last
  265. $keys = array_keys($this->_elements);
  266. $onChange = array();
  267. for ($i = 0; $i < count($keys) - 1; $i++) {
  268. $select =& $this->_elements[$keys[$i]];
  269. $onChange[$i] = $select->getAttribute('onchange');
  270. $select->updateAttributes(
  271. array('onchange' => '_hs_swapOptions(this.form, \'' . $this->_escapeString($this->getName()) . '\', ' . $keys[$i] . ');' . $onChange[$i])
  272. );
  273. }
  274. // create the js function to call
  275. if (!defined('HTML_QUICKFORM_HIERSELECT_EXISTS')) {
  276. $this->_js .= <<<JAVASCRIPT
  277. function _hs_findOptions(ary, keys)
  278. {
  279. if (ary == undefined) {
  280. return {};
  281. }
  282. var key = keys.shift();
  283. if (!key in ary) {
  284. return {};
  285. } else if (0 == keys.length) {
  286. return ary[key];
  287. } else {
  288. return _hs_findOptions(ary[key], keys);
  289. }
  290. }
  291. function _hs_findSelect(form, groupName, selectIndex)
  292. {
  293. if (groupName+'['+ selectIndex +']' in form) {
  294. return form[groupName+'['+ selectIndex +']'];
  295. } else {
  296. return form[groupName+'['+ selectIndex +'][]'];
  297. }
  298. }
  299. function _hs_unescapeEntities(str)
  300. {
  301. var div = document.createElement('div');
  302. div.innerHTML = str;
  303. return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
  304. }
  305. function _hs_replaceOptions(ctl, optionList)
  306. {
  307. var j = 0;
  308. ctl.options.length = 0;
  309. for (i in optionList) {
  310. var optionText = (-1 == String(optionList[i]).indexOf('&'))? optionList[i]: _hs_unescapeEntities(optionList[i]);
  311. ctl.options[j++] = new Option(optionText, i, false, false);
  312. }
  313. }
  314. function _hs_setValue(ctl, value)
  315. {
  316. var testValue = {};
  317. if (value instanceof Array) {
  318. for (var i = 0; i < value.length; i++) {
  319. testValue[value[i]] = true;
  320. }
  321. } else {
  322. testValue[value] = true;
  323. }
  324. for (var i = 0; i < ctl.options.length; i++) {
  325. if (ctl.options[i].value in testValue) {
  326. ctl.options[i].selected = true;
  327. }
  328. }
  329. }
  330. function _hs_swapOptions(form, groupName, selectIndex)
  331. {
  332. var hsValue = [];
  333. for (var i = 0; i <= selectIndex; i++) {
  334. hsValue[i] = _hs_findSelect(form, groupName, i).value;
  335. }
  336. _hs_replaceOptions(_hs_findSelect(form, groupName, selectIndex + 1),
  337. _hs_findOptions(_hs_options[groupName][selectIndex], hsValue));
  338. if (selectIndex + 1 < _hs_options[groupName].length) {
  339. _hs_swapOptions(form, groupName, selectIndex + 1);
  340. }
  341. }
  342. function _hs_onReset(form, groupNames)
  343. {
  344. for (var i = 0; i < groupNames.length; i++) {
  345. try {
  346. for (var j = 0; j <= _hs_options[groupNames[i]].length; j++) {
  347. _hs_setValue(_hs_findSelect(form, groupNames[i], j), _hs_defaults[groupNames[i]][j]);
  348. if (j < _hs_options[groupNames[i]].length) {
  349. _hs_replaceOptions(_hs_findSelect(form, groupNames[i], j + 1),
  350. _hs_findOptions(_hs_options[groupNames[i]][j], _hs_defaults[groupNames[i]].slice(0, j + 1)));
  351. }
  352. }
  353. } catch (e) {
  354. if (!(e instanceof TypeError)) {
  355. throw e;
  356. }
  357. }
  358. }
  359. }
  360. function _hs_setupOnReset(form, groupNames)
  361. {
  362. setTimeout(function() { _hs_onReset(form, groupNames); }, 25);
  363. }
  364. function _hs_onReload()
  365. {
  366. var ctl;
  367. for (var i = 0; i < document.forms.length; i++) {
  368. for (var j in _hs_defaults) {
  369. if (ctl = _hs_findSelect(document.forms[i], j, 0)) {
  370. for (var k = 0; k < _hs_defaults[j].length; k++) {
  371. _hs_setValue(_hs_findSelect(document.forms[i], j, k), _hs_defaults[j][k]);
  372. }
  373. }
  374. }
  375. }
  376. if (_hs_prevOnload) {
  377. _hs_prevOnload();
  378. }
  379. }
  380. var _hs_prevOnload = null;
  381. if (window.onload) {
  382. _hs_prevOnload = window.onload;
  383. }
  384. window.onload = _hs_onReload;
  385. var _hs_options = {};
  386. var _hs_defaults = {};
  387. JAVASCRIPT;
  388. define('HTML_QUICKFORM_HIERSELECT_EXISTS', true);
  389. }
  390. // option lists
  391. $jsParts = array();
  392. for ($i = 1; $i < $this->_nbElements; $i++) {
  393. $jsParts[] = $this->_convertArrayToJavascript($this->_options[$i]);
  394. }
  395. $this->_js .= "\n_hs_options['" . $this->_escapeString($this->getName()) . "'] = [\n" .
  396. implode(",\n", $jsParts) .
  397. "\n];\n";
  398. // default value; if we don't actually have any values yet just use
  399. // the first option (for single selects) or empty array (for multiple)
  400. $values = array();
  401. foreach (array_keys($this->_elements) as $key) {
  402. if (is_array($v = $this->_elements[$key]->getValue())) {
  403. $values[] = count($v) > 1? $v: $v[0];
  404. } else {
  405. // XXX: accessing the supposedly private _options array
  406. $values[] = $this->_elements[$key]->getMultiple() || empty($this->_elements[$key]->_options[0])?
  407. array():
  408. $this->_elements[$key]->_options[0]['attr']['value'];
  409. }
  410. }
  411. $this->_js .= "_hs_defaults['" . $this->_escapeString($this->getName()) . "'] = " .
  412. $this->_convertArrayToJavascript($values, false) . ";\n";
  413. }
  414. include_once('HTML/QuickForm/Renderer/Default.php');
  415. $renderer =& new HTML_QuickForm_Renderer_Default();
  416. $renderer->setElementTemplate('{element}');
  417. parent::accept($renderer);
  418. if (!empty($onChange)) {
  419. $keys = array_keys($this->_elements);
  420. for ($i = 0; $i < count($keys) - 1; $i++) {
  421. $this->_elements[$keys[$i]]->updateAttributes(array('onchange' => $onChange[$i]));
  422. }
  423. }
  424. return (empty($this->_js)? '': "<script type=\"text/javascript\">\n//<![CDATA[\n" . $this->_js . "//]]>\n</script>") .
  425. $renderer->toHtml();
  426. } // end func toHtml
  427. // }}}
  428. // {{{ accept()
  429. function accept(&$renderer, $required = false, $error = null)
  430. {
  431. $renderer->renderElement($this, $required, $error);
  432. } // end func accept
  433. // }}}
  434. // {{{ onQuickFormEvent()
  435. function onQuickFormEvent($event, $arg, &$caller)
  436. {
  437. if ('updateValue' == $event) {
  438. // we need to call setValue() so that the secondary option
  439. // matches the main option
  440. return HTML_QuickForm_element::onQuickFormEvent($event, $arg, $caller);
  441. } else {
  442. $ret = parent::onQuickFormEvent($event, $arg, $caller);
  443. // add onreset handler to form to properly reset hierselect (see bug #2970)
  444. if ('addElement' == $event) {
  445. $onReset = $caller->getAttribute('onreset');
  446. if (strlen($onReset)) {
  447. if (strpos($onReset, '_hs_setupOnReset')) {
  448. $caller->updateAttributes(array('onreset' => str_replace('_hs_setupOnReset(this, [', "_hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "', ", $onReset)));
  449. } else {
  450. $caller->updateAttributes(array('onreset' => "var temp = function() { {$onReset} } ; if (!temp()) { return false; } ; if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } "));
  451. }
  452. } else {
  453. $caller->updateAttributes(array('onreset' => "if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } "));
  454. }
  455. }
  456. return $ret;
  457. }
  458. } // end func onQuickFormEvent
  459. // }}}
  460. // {{{ _convertArrayToJavascript()
  461. /**
  462. * Converts PHP array to its Javascript analog
  463. *
  464. * @access private
  465. * @param array PHP array to convert
  466. * @param bool Generate Javascript object literal (default, works like PHP's associative array) or array literal
  467. * @return string Javascript representation of the value
  468. */
  469. function _convertArrayToJavascript($array, $assoc = true)
  470. {
  471. if (!is_array($array)) {
  472. return $this->_convertScalarToJavascript($array);
  473. } else {
  474. $items = array();
  475. foreach ($array as $key => $val) {
  476. $item = $assoc? "'" . $this->_escapeString($key) . "': ": '';
  477. if (is_array($val)) {
  478. $item .= $this->_convertArrayToJavascript($val, $assoc);
  479. } else {
  480. $item .= $this->_convertScalarToJavascript($val);
  481. }
  482. $items[] = $item;
  483. }
  484. }
  485. $js = implode(', ', $items);
  486. return $assoc? '{ ' . $js . ' }': '[' . $js . ']';
  487. }
  488. // }}}
  489. // {{{ _convertScalarToJavascript()
  490. /**
  491. * Converts PHP's scalar value to its Javascript analog
  492. *
  493. * @access private
  494. * @param mixed PHP value to convert
  495. * @return string Javascript representation of the value
  496. */
  497. function _convertScalarToJavascript($val)
  498. {
  499. if (is_bool($val)) {
  500. return $val ? 'true' : 'false';
  501. } elseif (is_int($val) || is_double($val)) {
  502. return $val;
  503. } elseif (is_string($val)) {
  504. return "'" . $this->_escapeString($val) . "'";
  505. } elseif (is_null($val)) {
  506. return 'null';
  507. } else {
  508. // don't bother
  509. return '{}';
  510. }
  511. }
  512. // }}}
  513. // {{{ _escapeString()
  514. /**
  515. * Quotes the string so that it can be used in Javascript string constants
  516. *
  517. * @access private
  518. * @param string
  519. * @return string
  520. */
  521. function _escapeString($str)
  522. {
  523. return strtr($str,array(
  524. "\r" => '\r',
  525. "\n" => '\n',
  526. "\t" => '\t',
  527. "'" => "\\'",
  528. '"' => '\"',
  529. '\\' => '\\\\'
  530. ));
  531. }
  532. // }}}
  533. } // end class HTML_QuickForm_hierselect
  534. ?>