hierselect.php 19 KB

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