hierselect.php 19 KB

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