PageRenderTime 28ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/Nette/Forms/Renderers/ConventionalRenderer.php

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