PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/system/library/PEAR/HTML/QuickForm2/Container/Repeat.php

https://bitbucket.org/spekkionu/passworddb
PHP | 725 lines | 342 code | 46 blank | 337 comment | 51 complexity | f306f7b68476f84f96d93d55c5b60de4 MD5 | raw file
Possible License(s): BSD-2-Clause
  1. <?php
  2. /**
  3. * Handles a Container that can be repeated multiple times in the form
  4. *
  5. * PHP version 5
  6. *
  7. * LICENSE:
  8. *
  9. * Copyright (c) 2006-2012, Alexey Borzov <avb@php.net>,
  10. * Bertrand Mansion <golgote@mamasam.com>
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without
  14. * modification, are permitted provided that the following conditions
  15. * are met:
  16. *
  17. * * Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. * * Redistributions in binary form must reproduce the above copyright
  20. * notice, this list of conditions and the following disclaimer in the
  21. * documentation and/or other materials provided with the distribution.
  22. * * The names of the authors may not be used to endorse or promote products
  23. * derived from this software without specific prior written permission.
  24. *
  25. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  26. * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  27. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  28. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  29. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  30. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  31. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  32. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  33. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  34. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  35. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @category HTML
  38. * @package HTML_QuickForm2
  39. * @author Alexey Borzov <avb@php.net>
  40. * @author Bertrand Mansion <golgote@mamasam.com>
  41. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  42. * @version SVN: $Id: Repeat.php 325694 2012-05-15 07:46:13Z avb $
  43. * @link http://pear.php.net/package/HTML_QuickForm2
  44. */
  45. /** Base class for all HTML_QuickForm2 containers */
  46. require_once 'HTML/QuickForm2/Container.php';
  47. /** Javascript aggregator and builder class */
  48. require_once 'HTML/QuickForm2/JavascriptBuilder.php';
  49. /**
  50. * Javascript builder used when rendering a repeat prototype
  51. *
  52. * Instead of returning form setup code and client-side rules as normal
  53. * Javascript code, it returns them as Javascript string literals. These are
  54. * expected to be eval()'d when adding a new repeat item.
  55. *
  56. * This class is not intended for normal use.
  57. *
  58. * @category HTML
  59. * @package HTML_QuickForm2
  60. * @author Alexey Borzov <avb@php.net>
  61. * @author Bertrand Mansion <golgote@mamasam.com>
  62. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  63. * @version Release: 2.0.0
  64. * @link http://pear.php.net/package/HTML_QuickForm2
  65. */
  66. class HTML_QuickForm2_Container_Repeat_JavascriptBuilder
  67. extends HTML_QuickForm2_JavascriptBuilder
  68. {
  69. /**
  70. * Fake "current form" ID
  71. * @var string
  72. */
  73. protected $formId = 'repeat';
  74. /**
  75. * Empty list of javascript libraries, base one(s) are in original builder
  76. * @var array
  77. */
  78. protected $libraries = array();
  79. /**
  80. * Returns rules and element setup code as Javascript string literals
  81. *
  82. * @return array array('rules', 'setup code')
  83. */
  84. public function getFormJavascriptAsStrings()
  85. {
  86. return array(
  87. self::encode(
  88. empty($this->rules['repeat'])
  89. ? '' : "[\n" . implode(",\n", $this->rules['repeat']) . "\n]"
  90. ),
  91. self::encode(
  92. empty($this->scripts['repeat'])
  93. ? '' : implode("\n", $this->scripts['repeat'])
  94. )
  95. );
  96. }
  97. /**
  98. * Passes Javascript libraries added by repeat prototype
  99. *
  100. * @param HTML_QuickForm2_JavascriptBuilder $recipient original Javascript builder
  101. */
  102. public function passLibraries(HTML_QuickForm2_JavascriptBuilder $recipient)
  103. {
  104. foreach ($this->libraries as $name => $library) {
  105. $recipient->addLibrary(
  106. $name, $library['file'], $library['webPath'], $library['absPath']
  107. );
  108. }
  109. }
  110. }
  111. /**
  112. * Handles a Container that can be repeated multiple times in the form
  113. *
  114. * This element accepts a Container (a Fieldset, a Group, but not another
  115. * Repeat) serving as a "prototype" and repeats it several times. Repeated
  116. * items can be dynamically added / removed via Javascript, with the benefit
  117. * that server-side part automatically knows about these changes and that
  118. * server-side and client-side validation can be easily leveraged.
  119. *
  120. * Example:
  121. * <code>
  122. * $group = new HTML_QuickForm2_Container_Group()
  123. * $repeat = $form->addRepeat('related');
  124. * ->setPrototype($group);
  125. * // repeat indexes will be automatically appended to elements in prototype
  126. * $group->addHidden('related_id');
  127. * $group->addText('related_title');
  128. * // this is identical to $group->addCheckbox('related_active');
  129. * $repeat->addCheckbox('related_active');
  130. *
  131. * // value of this field will be used to find the indexes of repeated items
  132. * $repeat->setIndexField('related_id');
  133. * </code>
  134. *
  135. * @category HTML
  136. * @package HTML_QuickForm2
  137. * @author Alexey Borzov <avb@php.net>
  138. * @author Bertrand Mansion <golgote@mamasam.com>
  139. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  140. * @version Release: 2.0.0
  141. * @link http://pear.php.net/package/HTML_QuickForm2
  142. */
  143. class HTML_QuickForm2_Container_Repeat extends HTML_QuickForm2_Container
  144. {
  145. /**
  146. * Key to replace by actual item index in elements' names / ids / values
  147. */
  148. const INDEX_KEY = ':idx:';
  149. /**
  150. * Regular expression used to check for valid indexes
  151. */
  152. const INDEX_REGEXP = '/^[a-zA-Z0-9_]+$/';
  153. /**
  154. * Field used to search for available indexes
  155. * @var string
  156. */
  157. protected $indexField = null;
  158. /**
  159. * Available indexes
  160. * @var array
  161. */
  162. protected $itemIndexes = array();
  163. /**
  164. * Errors for (repeated) child elements set during validate() call
  165. * @var array
  166. */
  167. protected $childErrors = array();
  168. /**
  169. * Whether getDataSources() should return Container's data sources
  170. *
  171. * This is done to prevent useless updateValue() activity in child
  172. * elements when their values are not going to be needed.
  173. *
  174. * @var bool
  175. */
  176. protected $passDataSources = false;
  177. /**
  178. * Returns the element's type
  179. *
  180. * @return string
  181. */
  182. public function getType()
  183. {
  184. return 'repeat';
  185. }
  186. /**
  187. * Sets the element's value (not implemented)
  188. *
  189. * @param mixed $value element's value
  190. *
  191. * @throws HTML_QuickForm2_Exception
  192. */
  193. public function setValue($value)
  194. {
  195. throw new HTML_QuickForm2_Exception('Not implemented');
  196. }
  197. /**
  198. * Class constructor
  199. *
  200. * Repeat element can understand the following keys in $data parameter:
  201. * - 'prototype': a Container to be repeated. Passed to {@link setPrototype()}.
  202. *
  203. * @param string $name Element name
  204. * @param string|array $attributes Attributes (either a string or an array)
  205. * @param array $data Additional element data
  206. */
  207. public function __construct($name = null, $attributes = null, array $data = array())
  208. {
  209. if (!empty($data['prototype'])) {
  210. $this->setPrototype($data['prototype']);
  211. }
  212. unset($data['prototype']);
  213. parent::__construct($name, $attributes, $data);
  214. }
  215. /**
  216. * Sets the Container that will be used as a prototype for repeating
  217. *
  218. * @param HTML_QuickForm2_Container $prototype prototype container
  219. *
  220. * @return HTML_QuickForm2_Container_Repeat
  221. */
  222. public function setPrototype(HTML_QuickForm2_Container $prototype)
  223. {
  224. if (!empty($this->elements[0])) {
  225. parent::removeChild($this->elements[0]);
  226. $this->elements = array();
  227. }
  228. parent::appendChild($prototype);
  229. return $this;
  230. }
  231. /**
  232. * Returns the prototype Container
  233. *
  234. * @return HTML_QuickForm2_Container prototype
  235. * @throws HTML_QuickForm2_NotFoundException if prototype was not set
  236. */
  237. protected function getPrototype()
  238. {
  239. if (empty($this->elements[0])) {
  240. throw new HTML_QuickForm2_NotFoundException(
  241. "Repeat element needs a prototype, use setPrototype()"
  242. );
  243. }
  244. return $this->elements[0];
  245. }
  246. /**
  247. * Appends an element to the prototype container
  248. *
  249. * Elements are kept in the prototype rather than directly in repeat
  250. *
  251. * @param HTML_QuickForm2_Node $element Element to add
  252. *
  253. * @return HTML_QuickForm2_Node Added element
  254. * @throws HTML_QuickForm2_InvalidArgumentException
  255. */
  256. public function appendChild(HTML_QuickForm2_Node $element)
  257. {
  258. return $this->getPrototype()->appendChild($element);
  259. }
  260. /**
  261. * Removes the element from the prototype container
  262. *
  263. * Elements are kept in the prototype rather than directly in repeat
  264. *
  265. * @param HTML_QuickForm2_Node $element Element to remove
  266. *
  267. * @return HTML_QuickForm2_Node Removed object
  268. * @throws HTML_QuickForm2_NotFoundException
  269. */
  270. public function removeChild(HTML_QuickForm2_Node $element)
  271. {
  272. return $this->getPrototype()->removeChild($element);
  273. }
  274. /**
  275. * Inserts an element to the prototype container
  276. *
  277. * Elements are kept in the prototype rather than directly in repeat
  278. *
  279. * @param HTML_QuickForm2_Node $element Element to insert
  280. * @param HTML_QuickForm2_Node $reference Reference to insert before
  281. *
  282. * @return HTML_QuickForm2_Node Inserted element
  283. */
  284. public function insertBefore(
  285. HTML_QuickForm2_Node $element, HTML_QuickForm2_Node $reference = null
  286. ) {
  287. return $this->getPrototype()->insertBefore($element, $reference);
  288. }
  289. /**
  290. * Returns the data sources for this element
  291. *
  292. * @return array
  293. * @see $passDataSources
  294. */
  295. protected function getDataSources()
  296. {
  297. if (!$this->passDataSources) {
  298. return array();
  299. } else {
  300. return parent::getDataSources();
  301. }
  302. }
  303. /**
  304. * Sets a field to check for available indexes
  305. *
  306. * Form data sources will be searched for this field's value, indexes present
  307. * in the array will be used for repeated elements. Use the field that will be
  308. * always present in submit data: checkboxes, multiple selects and fields that
  309. * may be disabled are bad choices
  310. *
  311. * @param string $field field name
  312. *
  313. * @return HTML_QuickForm2_Container_Repeat
  314. */
  315. public function setIndexField($field)
  316. {
  317. $this->indexField = $field;
  318. $this->updateValue();
  319. return $this;
  320. }
  321. /**
  322. * Tries to guess a field name to use for getting indexes of repeated items
  323. *
  324. * @return bool Whether we were able to guess something
  325. * @see setIndexField()
  326. */
  327. private function _guessIndexField()
  328. {
  329. $this->appendIndexTemplates();
  330. $this->passDataSources = false;
  331. /* @var $child HTML_QuickForm2_Node */
  332. foreach ($this->getRecursiveIterator(RecursiveIteratorIterator::LEAVES_ONLY) as $child) {
  333. $name = $child->getName();
  334. if (false === ($pos = strpos($name, '[' . self::INDEX_KEY . ']'))
  335. || $child->getAttribute('disabled')
  336. ) {
  337. continue;
  338. }
  339. // The list is somewhat future-proof for HTML5 input elements
  340. if ($child instanceof HTML_QuickForm2_Element_Input
  341. && !($child instanceof HTML_QuickForm2_Element_InputButton
  342. || $child instanceof HTML_QuickForm2_Element_InputCheckable
  343. || $child instanceof HTML_QuickForm2_Element_InputFile
  344. || $child instanceof HTML_QuickForm2_Element_InputImage
  345. || $child instanceof HTML_QuickForm2_Element_InputReset
  346. || $child instanceof HTML_QuickForm2_Element_InputSubmit)
  347. || ($child instanceof HTML_QuickForm2_Element_Select
  348. && !$child->getAttribute('multiple'))
  349. || $child instanceof HTML_QuickForm2_Element_Textarea
  350. ) {
  351. $this->indexField = substr($name, 0, $pos);
  352. return true;
  353. }
  354. }
  355. return false;
  356. }
  357. /**
  358. * Returns the indexes for repeated items
  359. *
  360. * @return array
  361. */
  362. public function getIndexes()
  363. {
  364. if (null === $this->indexField && $this->_guessIndexField()) {
  365. $this->updateValue();
  366. }
  367. return $this->itemIndexes;
  368. }
  369. /**
  370. * Sets the indexes for repeated items
  371. *
  372. * As is the case with elements' values, the indexes will be updated
  373. * from data sources, so use this after all possible updates were done.
  374. *
  375. * @param array $indexes
  376. *
  377. * @return HTML_QuickForm2_Container_Repeat
  378. */
  379. public function setIndexes(array $indexes)
  380. {
  381. $hash = array();
  382. foreach ($indexes as $index) {
  383. if (preg_match(self::INDEX_REGEXP, $index)) {
  384. $hash[$index] = true;
  385. }
  386. }
  387. $this->itemIndexes = array_keys($hash);
  388. return $this;
  389. }
  390. /**
  391. * Called when the element needs to update its value from form's data sources
  392. *
  393. * Behaves similar to Element::updateValue(), the field's value is used to
  394. * deduce indexes taken by repeat items.
  395. *
  396. * @see setIndexField()
  397. * @throws HTML_QuickForm2_Exception
  398. */
  399. protected function updateValue()
  400. {
  401. // check that we are not added to another Repeat
  402. // done here instead of in setContainer() for reasons outlined in InputFile
  403. $container = $this->getContainer();
  404. while (!empty($container)) {
  405. if ($container instanceof self) {
  406. throw new HTML_QuickForm2_Exception(
  407. "Repeat element cannot be added to another Repeat element"
  408. );
  409. }
  410. $container = $container->getContainer();
  411. }
  412. if (null === $this->indexField && !$this->_guessIndexField()) {
  413. return;
  414. }
  415. /* @var HTML_QuickForm2_DataSource $ds */
  416. foreach (parent::getDataSources() as $ds) {
  417. if (null !== ($value = $ds->getValue($this->indexField))) {
  418. $this->setIndexes(array_keys($value));
  419. return;
  420. }
  421. }
  422. }
  423. /**
  424. * Appends the template to elements' names and ids that will be later replaced by index
  425. *
  426. * Default behaviour is to append '[:idx:]' to element names and '_:idx:' to
  427. * element ids. If the string ':idx:' is already present in the attribute,
  428. * then it will not be changed.
  429. *
  430. * Checkboxes and radios may contain ':idx:' in their 'value' attribute,
  431. * in this case their 'name' attribute is left alone. Names of groups are
  432. * also not touched.
  433. */
  434. protected function appendIndexTemplates()
  435. {
  436. $this->passDataSources = true;
  437. /* @var HTML_QuickForm2_Node $child */
  438. foreach ($this->getRecursiveIterator() as $child) {
  439. $id = $child->getId();
  440. if (false === strpos($id, self::INDEX_KEY)) {
  441. $child->setId($id . '_' . self::INDEX_KEY);
  442. }
  443. $name = $child->getName();
  444. // checkboxes and radios can have index inside "value" attribute instead,
  445. // group names should not be touched
  446. if (strlen($name) && false === strpos($name, self::INDEX_KEY)
  447. && (!$child instanceof HTML_QuickForm2_Container || !$child->prependsName())
  448. && (!$child instanceof HTML_QuickForm2_Element_InputCheckable
  449. || false === strpos($child->getAttribute('value'), self::INDEX_KEY))
  450. ) {
  451. $child->setName($name . '[' . self::INDEX_KEY . ']');
  452. }
  453. }
  454. }
  455. /**
  456. * Backs up child attributes
  457. *
  458. * @param bool $backupId whether to backup id attribute
  459. * @param bool $backupError whether to backup error message
  460. *
  461. * @return array backup array
  462. */
  463. protected function backupChildAttributes($backupId = false, $backupError = false)
  464. {
  465. $this->appendIndexTemplates();
  466. $backup = array();
  467. $key = 0;
  468. /* @var HTML_QuickForm2_Node $child */
  469. foreach ($this->getRecursiveIterator() as $child) {
  470. $backup[$key++] = array(
  471. 'name' => $child->getName(),
  472. ) + (
  473. $child instanceof HTML_QuickForm2_Element_InputCheckable
  474. ? array('valueAttr' => $child->getAttribute('value')) : array()
  475. ) + (
  476. $child instanceof HTML_QuickForm2_Container
  477. ? array() : array('value' => $child->getValue())
  478. ) + (
  479. $backupId ? array('id' => $child->getId()) : array()
  480. ) + (
  481. $backupError ? array('error' => $child->getError()) : array()
  482. );
  483. }
  484. return $backup;
  485. }
  486. /**
  487. * Restores child attributes from backup array
  488. *
  489. * @param array $backup backup array
  490. *
  491. * @see backupChildAttributes()
  492. */
  493. protected function restoreChildAttributes(array $backup)
  494. {
  495. $key = 0;
  496. /* @var HTML_QuickForm2_Node $child */
  497. foreach ($this->getRecursiveIterator() as $child) {
  498. if (array_key_exists('value', $backup[$key])) {
  499. $child->setValue($backup[$key]['value']);
  500. }
  501. if (false !== strpos($backup[$key]['name'], self::INDEX_KEY)) {
  502. $child->setName($backup[$key]['name']);
  503. }
  504. if ($child instanceof HTML_QuickForm2_Element_InputCheckable
  505. && false !== strpos($backup[$key]['valueAttr'], self::INDEX_KEY)
  506. ) {
  507. $child->setAttribute('value', $backup[$key]['valueAttr']);
  508. }
  509. if (array_key_exists('id', $backup[$key])) {
  510. $child->setId($backup[$key]['id']);
  511. }
  512. if (array_key_exists('error', $backup[$key])) {
  513. $child->setError($backup[$key]['error']);
  514. }
  515. $key++;
  516. }
  517. $this->passDataSources = false;
  518. }
  519. /**
  520. * Replaces a template in elements' attributes by a numeric index
  521. *
  522. * @param int $index numeric index
  523. * @param array $backup backup array, contains attributes with templates
  524. *
  525. * @see backupChildAttributes()
  526. */
  527. protected function replaceIndexTemplates($index, array $backup)
  528. {
  529. $this->passDataSources = true;
  530. $key = 0;
  531. /* @var HTML_QuickForm2_Node $child */
  532. foreach ($this->getRecursiveIterator() as $child) {
  533. if (false !== strpos($backup[$key]['name'], self::INDEX_KEY)) {
  534. $child->setName(str_replace(self::INDEX_KEY, $index, $backup[$key]['name']));
  535. }
  536. if ($child instanceof HTML_QuickForm2_Element_InputCheckable
  537. && false !== strpos($backup[$key]['valueAttr'], self::INDEX_KEY)
  538. ) {
  539. $child->setAttribute(
  540. 'value', str_replace(self::INDEX_KEY, $index, $backup[$key]['valueAttr'])
  541. );
  542. }
  543. if (array_key_exists('id', $backup[$key])) {
  544. $child->setId(str_replace(self::INDEX_KEY, $index, $backup[$key]['id']));
  545. }
  546. if (array_key_exists('error', $backup[$key])) {
  547. $child->setError();
  548. }
  549. $key++;
  550. }
  551. }
  552. /**
  553. * Returns the array containing child elements' values
  554. *
  555. * Iterates over all available repeat indexes to get values
  556. *
  557. * @param bool $filtered Whether child elements should apply filters on values
  558. *
  559. * @return array|null
  560. */
  561. protected function getChildValues($filtered = false)
  562. {
  563. $backup = $this->backupChildAttributes();
  564. $values = array();
  565. foreach ($this->getIndexes() as $index) {
  566. $this->replaceIndexTemplates($index, $backup);
  567. $values = self::arrayMerge(
  568. $values, parent::getChildValues($filtered)
  569. );
  570. }
  571. $this->restoreChildAttributes($backup);
  572. return empty($values) ? null : $values;
  573. }
  574. /**
  575. * Performs the server-side validation
  576. *
  577. * Iterates over all available repeat indexes and calls validate() on
  578. * prototype container.
  579. *
  580. * @return boolean Whether the repeat and all repeated items are valid
  581. */
  582. protected function validate()
  583. {
  584. $backup = $this->backupChildAttributes(false, true);
  585. $valid = true;
  586. $this->childErrors = array();
  587. foreach ($this->getIndexes() as $index) {
  588. $this->replaceIndexTemplates($index, $backup);
  589. $valid = $this->getPrototype()->validate() && $valid;
  590. /* @var HTML_QuickForm2_Node $child */
  591. foreach ($this->getRecursiveIterator() as $child) {
  592. if (strlen($error = $child->getError())) {
  593. $this->childErrors[spl_object_hash($child)][$index] = $error;
  594. }
  595. }
  596. }
  597. $this->restoreChildAttributes($backup);
  598. foreach ($this->rules as $rule) {
  599. if (strlen($this->error)) {
  600. break;
  601. }
  602. if ($rule[1] & HTML_QuickForm2_Rule::SERVER) {
  603. $rule[0]->validate();
  604. }
  605. }
  606. return !strlen($this->error) && $valid;
  607. }
  608. /**
  609. * Generates Javascript code to initialize repeat behaviour
  610. *
  611. * @param HTML_QuickForm2_Container_Repeat_JavascriptBuilder $evalBuilder
  612. * Javascript builder returning JS string literals
  613. *
  614. * @return string javascript
  615. */
  616. private function _generateInitScript(
  617. HTML_QuickForm2_Container_Repeat_JavascriptBuilder $evalBuilder
  618. ) {
  619. $myId = HTML_QuickForm2_JavascriptBuilder::encode($this->getId());
  620. $protoId = HTML_QuickForm2_JavascriptBuilder::encode($this->getPrototype()->getId());
  621. $triggers = HTML_QuickForm2_JavascriptBuilder::encode(
  622. $this->getJavascriptTriggers()
  623. );
  624. list ($rules, $scripts) = $evalBuilder->getFormJavascriptAsStrings();
  625. return "new qf.elements.Repeat(document.getElementById({$myId}), {$protoId}, "
  626. . "{$triggers},\n{$rules},\n{$scripts}\n);";
  627. }
  628. /**
  629. * Renders the container using the given renderer
  630. *
  631. * Container will be output N + 1 times, where N are visible items and 1 is
  632. * the hidden prototype used by Javascript code to create new items.
  633. *
  634. * @param HTML_QuickForm2_Renderer $renderer renderer to use
  635. *
  636. * @return HTML_QuickForm2_Renderer
  637. */
  638. public function render(HTML_QuickForm2_Renderer $renderer)
  639. {
  640. $backup = $this->backupChildAttributes(true, true);
  641. $hiddens = $renderer->getOption('group_hiddens');
  642. $jsBuilder = $renderer->getJavascriptBuilder();
  643. $evalBuilder = new HTML_QuickForm2_Container_Repeat_JavascriptBuilder();
  644. $renderer->setJavascriptBuilder($evalBuilder)
  645. ->setOption('group_hiddens', false)
  646. ->startContainer($this);
  647. // first, render a (hidden) prototype
  648. $this->getPrototype()->addClass('repeatItem repeatPrototype');
  649. $this->getPrototype()->render($renderer);
  650. $this->getPrototype()->removeClass('repeatPrototype');
  651. // restore original JS builder
  652. $evalBuilder->passLibraries($jsBuilder);
  653. $renderer->setJavascriptBuilder($jsBuilder);
  654. // next, render all available rows
  655. foreach ($this->getIndexes() as $index) {
  656. $this->replaceIndexTemplates($index, $backup);
  657. /* @var HTML_QuickForm2_Node $child */
  658. foreach ($this->getRecursiveIterator() as $child) {
  659. if (isset($this->childErrors[$hash = spl_object_hash($child)])
  660. && isset($this->childErrors[$hash][$index])
  661. ) {
  662. $child->setError($this->childErrors[$hash][$index]);
  663. }
  664. }
  665. $this->getPrototype()->render($renderer);
  666. }
  667. $this->restoreChildAttributes($backup);
  668. // only add javascript if not frozen
  669. if (!$this->toggleFrozen()) {
  670. $jsBuilder->addLibrary('repeat', 'quickform-repeat.js');
  671. $jsBuilder->addElementJavascript($this->_generateInitScript($evalBuilder));
  672. $this->renderClientRules($jsBuilder);
  673. }
  674. $renderer->finishContainer($this);
  675. $renderer->setOption('group_hiddens', $hiddens);
  676. return $renderer;
  677. }
  678. }
  679. ?>