PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/Nette/Forms/Renderers/ConventionalRenderer.php

https://github.com/DocX/nette
PHP | 553 lines | 310 code | 107 blank | 136 comment | 58 complexity | 37a471220c9ac4ee30c5593260d2d23d MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license" that is bundled
  8. * with this package in the file license.txt.
  9. *
  10. * For more information please see http://nettephp.com
  11. *
  12. * @copyright Copyright (c) 2004, 2009 David Grudl
  13. * @license http://nettephp.com/license Nette license
  14. * @link http://nettephp.com
  15. * @category Nette
  16. * @package Nette\Forms
  17. */
  18. /*namespace Nette\Forms;*/
  19. /*use Nette\Web\Html;*/
  20. require_once dirname(__FILE__) . '/../../Object.php';
  21. require_once dirname(__FILE__) . '/../../Forms/IFormRenderer.php';
  22. /**
  23. * Converts a Form into the HTML output.
  24. *
  25. * @author David Grudl
  26. * @copyright Copyright (c) 2004, 2009 David Grudl
  27. * @package Nette\Forms
  28. */
  29. class ConventionalRenderer extends /*Nette\*/Object implements IFormRenderer
  30. {
  31. /**
  32. * /--- form.container
  33. *
  34. * /--- if (form.errors) error.container
  35. * .... error.item [.class]
  36. * \---
  37. *
  38. * /--- hidden.container
  39. * .... HIDDEN CONTROLS
  40. * \---
  41. *
  42. * /--- group.container
  43. * .... group.label
  44. * .... group.description
  45. *
  46. * /--- controls.container
  47. *
  48. * /--- pair.container [.required .optional .odd]
  49. *
  50. * /--- label.container
  51. * .... LABEL
  52. * .... label.suffix
  53. * .... label.requiredsuffix
  54. * \---
  55. *
  56. * /--- control.container [.odd]
  57. * .... CONTROL [.required .text .password .file .submit .button]
  58. * .... control.requiredsuffix
  59. * .... control.description
  60. * .... if (control.errors) error.container
  61. * \---
  62. * \---
  63. * \---
  64. * \---
  65. * \--
  66. *
  67. * @var array of HTML tags */
  68. public $wrappers = array(
  69. 'form' => array(
  70. 'container' => NULL,
  71. 'errors' => TRUE,
  72. ),
  73. 'error' => array(
  74. 'container' => 'ul class=error',
  75. 'item' => 'li',
  76. ),
  77. 'group' => array(
  78. 'container' => 'fieldset',
  79. 'label' => 'legend',
  80. 'description' => 'p',
  81. ),
  82. 'controls' => array(
  83. 'container' => 'table',
  84. ),
  85. 'pair' => array(
  86. 'container' => 'tr',
  87. '.required' => 'required',
  88. '.optional' => NULL,
  89. '.odd' => NULL,
  90. ),
  91. 'control' => array(
  92. 'container' => 'td',
  93. '.odd' => NULL,
  94. 'errors' => FALSE,
  95. 'description' => 'small',
  96. 'requiredsuffix' => '',
  97. '.required' => 'required',
  98. '.text' => 'text',
  99. '.password' => 'text',
  100. '.file' => 'text',
  101. '.submit' => 'button',
  102. '.image' => 'imagebutton',
  103. '.button' => 'button',
  104. ),
  105. 'label' => array(
  106. 'container' => 'th',
  107. 'suffix' => NULL,
  108. 'requiredsuffix' => '',
  109. ),
  110. 'hidden' => array(
  111. 'container' => 'div',
  112. ),
  113. );
  114. /** @var Form */
  115. protected $form;
  116. /** @var object */
  117. protected $clientScript = TRUE; // means autodetect
  118. /** @var int */
  119. protected $counter;
  120. /**
  121. * Provides complete form rendering.
  122. * @param Form
  123. * @param string
  124. * @return string
  125. */
  126. public function render(Form $form, $mode = NULL)
  127. {
  128. if ($this->form !== $form) {
  129. $this->form = $form;
  130. $this->init();
  131. }
  132. $s = '';
  133. if (!$mode || $mode === 'begin') {
  134. $s .= $this->renderBegin();
  135. }
  136. if ((!$mode && $this->getValue('form errors')) || $mode === 'errors') {
  137. $s .= $this->renderErrors();
  138. }
  139. if (!$mode || $mode === 'body') {
  140. $s .= $this->renderBody();
  141. }
  142. if (!$mode || $mode === 'end') {
  143. $s .= $this->renderEnd();
  144. }
  145. return $s;
  146. }
  147. /**
  148. * Sets JavaScript handler.
  149. * @param object
  150. * @return ConventionalRenderer provides a fluent interface
  151. */
  152. public function setClientScript($clientScript = NULL)
  153. {
  154. $this->clientScript = $clientScript;
  155. return $this;
  156. }
  157. /**
  158. * Returns JavaScript handler.
  159. * @return mixed
  160. */
  161. public function getClientScript()
  162. {
  163. if ($this->clientScript === TRUE) {
  164. $this->clientScript = new InstantClientScript($this->form);
  165. }
  166. return $this->clientScript;
  167. }
  168. /**
  169. * Initializes form.
  170. * @return void
  171. */
  172. protected function init()
  173. {
  174. $clientScript = $this->getClientScript();
  175. if ($clientScript !== NULL) {
  176. $clientScript->enable();
  177. }
  178. // TODO: only for back compatiblity - remove?
  179. $wrapper = & $this->wrappers['control'];
  180. foreach ($this->form->getControls() as $control) {
  181. if ($control->getOption('required') && isset($wrapper['.required'])) {
  182. $control->getLabelPrototype()->class($wrapper['.required'], TRUE);
  183. }
  184. $el = $control->getControlPrototype();
  185. if ($el->getName() === 'input' && isset($wrapper['.' . $el->type])) {
  186. $el->class($wrapper['.' . $el->type], TRUE);
  187. }
  188. }
  189. }
  190. /**
  191. * Renders form begin.
  192. * @return string
  193. */
  194. public function renderBegin()
  195. {
  196. $this->counter = 0;
  197. foreach ($this->form->getControls() as $control) {
  198. $control->setOption('rendered', FALSE);
  199. }
  200. if (strcasecmp($this->form->getMethod(), 'get') === 0) {
  201. $el = clone $this->form->getElementPrototype();
  202. $uri = explode('?', (string) $el->action, 2);
  203. $el->action = $uri[0];
  204. $s = '';
  205. if (isset($uri[1])) {
  206. foreach (preg_split('#[;&]#', $uri[1]) as $param) {
  207. $parts = explode('=', $param, 2);
  208. $name = urldecode($parts[0]);
  209. if (!isset($this->form[$name])) {
  210. $s .= Html::el('input', array('type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])));
  211. }
  212. }
  213. $s = "\n\t" . $this->getWrapper('hidden container')->setHtml($s);
  214. }
  215. return $el->startTag() . $s;
  216. } else {
  217. return $this->form->getElementPrototype()->startTag();
  218. }
  219. }
  220. /**
  221. * Renders form end.
  222. * @return string
  223. */
  224. public function renderEnd()
  225. {
  226. $s = '';
  227. foreach ($this->form->getControls() as $control) {
  228. if ($control instanceof HiddenField && !$control->getOption('rendered')) {
  229. $s .= (string) $control->getControl();
  230. }
  231. }
  232. if ($s) {
  233. $s = $this->getWrapper('hidden container')->setHtml($s) . "\n";
  234. }
  235. $s .= $this->form->getElementPrototype()->endTag() . "\n";
  236. $clientScript = $this->getClientScript();
  237. if ($clientScript !== NULL) {
  238. $s .= $clientScript->renderClientScript() . "\n";
  239. }
  240. return $s;
  241. }
  242. /**
  243. * Renders validation errors (per form or per control).
  244. * @param IFormControl
  245. * @return string
  246. */
  247. public function renderErrors(IFormControl $control = NULL)
  248. {
  249. $errors = $control === NULL ? $this->form->getErrors() : $control->getErrors();
  250. if (count($errors)) {
  251. $ul = $this->getWrapper('error container');
  252. $li = $this->getWrapper('error item');
  253. foreach ($errors as $error) {
  254. $item = clone $li;
  255. if ($error instanceof Html) {
  256. $item->add($error);
  257. } else {
  258. $item->setText($error);
  259. }
  260. $ul->add($item);
  261. }
  262. return "\n" . $ul->render(0);
  263. }
  264. }
  265. /**
  266. * Renders form body.
  267. * @return string
  268. */
  269. public function renderBody()
  270. {
  271. $s = $remains = '';
  272. $defaultContainer = $this->getWrapper('group container');
  273. $translator = $this->form->getTranslator();
  274. foreach ($this->form->getGroups() as $group) {
  275. if (!$group->getControls() || !$group->getOption('visual')) continue;
  276. $container = $group->getOption('container', $defaultContainer);
  277. $container = $container instanceof Html ? clone $container : Html::el($container);
  278. $s .= "\n" . $container->startTag();
  279. $text = $group->getOption('label');
  280. if ($text instanceof Html) {
  281. $s .= $text;
  282. } elseif (is_string($text)) {
  283. if ($translator !== NULL) {
  284. $text = $translator->translate($text);
  285. }
  286. $s .= "\n" . $this->getWrapper('group label')->setText($text) . "\n";
  287. }
  288. $text = $group->getOption('description');
  289. if ($text instanceof Html) {
  290. $s .= $text;
  291. } elseif (is_string($text)) {
  292. if ($translator !== NULL) {
  293. $text = $translator->translate($text);
  294. }
  295. $s .= $this->getWrapper('group description')->setText($text) . "\n";
  296. }
  297. $s .= $this->renderControls($group);
  298. $remains = $container->endTag() . "\n" . $remains;
  299. if (!$group->getOption('embedNext')) {
  300. $s .= $remains;
  301. $remains = '';
  302. }
  303. }
  304. $s .= $remains . $this->renderControls($this->form);
  305. $container = $this->getWrapper('form container');
  306. $container->setHtml($s);
  307. return $container->render(0);
  308. }
  309. /**
  310. * Renders group of controls.
  311. * @param FormContainer|FormGroup
  312. * @return string
  313. */
  314. public function renderControls($parent)
  315. {
  316. if (!($parent instanceof FormContainer || $parent instanceof FormGroup)) {
  317. throw new /*\*/InvalidArgumentException("Argument must be FormContainer or FormGroup instance.");
  318. }
  319. $container = $this->getWrapper('controls container');
  320. $buttons = NULL;
  321. foreach ($parent->getControls() as $control) {
  322. if ($control->getOption('rendered') || $control instanceof HiddenField || $control->getForm(FALSE) !== $this->form) {
  323. // skip
  324. } elseif ($control instanceof Button) {
  325. $buttons[] = $control;
  326. } else {
  327. if ($buttons) {
  328. $container->add($this->renderPairMulti($buttons));
  329. $buttons = NULL;
  330. }
  331. $container->add($this->renderPair($control));
  332. }
  333. }
  334. if ($buttons) {
  335. $container->add($this->renderPairMulti($buttons));
  336. }
  337. $s = '';
  338. if (count($container)) {
  339. $s .= "\n" . $container . "\n";
  340. }
  341. return $s;
  342. }
  343. /**
  344. * Renders single visual row.
  345. * @param IFormControl
  346. * @return string
  347. */
  348. public function renderPair(IFormControl $control)
  349. {
  350. $pair = $this->getWrapper('pair container');
  351. $pair->add($this->renderLabel($control));
  352. $pair->add($this->renderControl($control));
  353. $pair->class($this->getValue($control->getOption('required') ? 'pair .required' : 'pair .optional'), TRUE);
  354. $pair->class($control->getOption('class'), TRUE);
  355. if (++$this->counter % 2) $pair->class($this->getValue('pair .odd'), TRUE);
  356. $pair->id = $control->getOption('id');
  357. return $pair->render(0);
  358. }
  359. /**
  360. * Renders single visual row of multiple controls.
  361. * @param array of IFormControl
  362. * @return string
  363. */
  364. public function renderPairMulti(array $controls)
  365. {
  366. $s = array();
  367. foreach ($controls as $control) {
  368. if (!($control instanceof IFormControl)) {
  369. throw new /*\*/InvalidArgumentException("Argument must be array of IFormControl instances.");
  370. }
  371. $s[] = (string) $control->getControl();
  372. }
  373. $pair = $this->getWrapper('pair container');
  374. $pair->add($this->renderLabel($control));
  375. $pair->add($this->getWrapper('control container')->setHtml(implode(" ", $s)));
  376. return $pair->render(0);
  377. }
  378. /**
  379. * Renders 'label' part of visual row of controls.
  380. * @param IFormControl
  381. * @return string
  382. */
  383. public function renderLabel(IFormControl $control)
  384. {
  385. $head = $this->getWrapper('label container');
  386. if ($control instanceof Checkbox || $control instanceof Button) {
  387. return $head->setHtml(($head->getName() === 'td' || $head->getName() === 'th') ? '&nbsp;' : '');
  388. } else {
  389. $label = $control->getLabel();
  390. $suffix = $this->getValue('label suffix') . ($control->getOption('required') ? $this->getValue('label requiredsuffix') : '');
  391. if ($label instanceof Html) {
  392. $label->setHtml($label->getHtml() . $suffix);
  393. $suffix = '';
  394. }
  395. return $head->setHtml((string) $label . $suffix);
  396. }
  397. }
  398. /**
  399. * Renders 'control' part of visual row of controls.
  400. * @param IFormControl
  401. * @return string
  402. */
  403. public function renderControl(IFormControl $control)
  404. {
  405. $body = $this->getWrapper('control container');
  406. if ($this->counter % 2) $body->class($this->getValue('control .odd'), TRUE);
  407. $description = $control->getOption('description');
  408. if ($description instanceof Html) {
  409. $description = ' ' . $control->getOption('description');
  410. } elseif (is_string($description)) {
  411. $description = ' ' . $this->getWrapper('control description')->setText($description);
  412. } else {
  413. $description = '';
  414. }
  415. if ($control->getOption('required')) {
  416. $description = $this->getValue('control requiredsuffix') . $description;
  417. }
  418. if ($this->getValue('control errors')) {
  419. $description .= $this->renderErrors($control);
  420. }
  421. if ($control instanceof Checkbox || $control instanceof Button) {
  422. return $body->setHtml((string) $control->getControl() . (string) $control->getLabel() . $description);
  423. } else {
  424. return $body->setHtml((string) $control->getControl() . $description);
  425. }
  426. }
  427. /**
  428. * @param string
  429. * @return Nette\Web\Html
  430. */
  431. protected function getWrapper($name)
  432. {
  433. $data = $this->getValue($name);
  434. return $data instanceof Html ? clone $data : Html::el($data);
  435. }
  436. /**
  437. * @param string
  438. * @return string
  439. */
  440. protected function getValue($name)
  441. {
  442. $name = explode(' ', $name);
  443. $data = & $this->wrappers[$name[0]][$name[1]];
  444. return $data;
  445. }
  446. }