1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Forms\Rendering;
9:
10: use Nette;
11: use Nette\Utils\Html;
12: use Nette\Utils\IHtmlString;
13:
14:
15: 16: 17:
18: class DefaultFormRenderer implements Nette\Forms\IFormRenderer
19: {
20: use Nette\SmartObject;
21:
22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
59: public $wrappers = [
60: 'form' => [
61: 'container' => NULL,
62: ],
63:
64: 'error' => [
65: 'container' => 'ul class=error',
66: 'item' => 'li',
67: ],
68:
69: 'group' => [
70: 'container' => 'fieldset',
71: 'label' => 'legend',
72: 'description' => 'p',
73: ],
74:
75: 'controls' => [
76: 'container' => 'table',
77: ],
78:
79: 'pair' => [
80: 'container' => 'tr',
81: '.required' => 'required',
82: '.optional' => NULL,
83: '.odd' => NULL,
84: '.error' => NULL,
85: ],
86:
87: 'control' => [
88: 'container' => 'td',
89: '.odd' => NULL,
90:
91: 'description' => 'small',
92: 'requiredsuffix' => '',
93: 'errorcontainer' => 'span class=error',
94: 'erroritem' => '',
95:
96: '.required' => 'required',
97: '.text' => 'text',
98: '.password' => 'text',
99: '.file' => 'text',
100: '.email' => 'text',
101: '.number' => 'text',
102: '.submit' => 'button',
103: '.image' => 'imagebutton',
104: '.button' => 'button',
105: ],
106:
107: 'label' => [
108: 'container' => 'th',
109: 'suffix' => NULL,
110: 'requiredsuffix' => '',
111: ],
112:
113: 'hidden' => [
114: 'container' => NULL,
115: ],
116: ];
117:
118:
119: protected $form;
120:
121:
122: protected $counter;
123:
124:
125: 126: 127: 128: 129: 130:
131: public function render(Nette\Forms\Form $form, $mode = NULL)
132: {
133: if ($this->form !== $form) {
134: $this->form = $form;
135: }
136:
137: $s = '';
138: if (!$mode || $mode === 'begin') {
139: $s .= $this->renderBegin();
140: }
141: if (!$mode || strtolower($mode) === 'ownerrors') {
142: $s .= $this->renderErrors();
143:
144: } elseif ($mode === 'errors') {
145: $s .= $this->renderErrors(NULL, FALSE);
146: }
147: if (!$mode || $mode === 'body') {
148: $s .= $this->renderBody();
149: }
150: if (!$mode || $mode === 'end') {
151: $s .= $this->renderEnd();
152: }
153: return $s;
154: }
155:
156:
157: 158: 159: 160:
161: public function renderBegin()
162: {
163: $this->counter = 0;
164:
165: foreach ($this->form->getControls() as $control) {
166: $control->setOption('rendered', FALSE);
167: }
168:
169: if ($this->form->isMethod('get')) {
170: $el = clone $this->form->getElementPrototype();
171: $query = parse_url($el->action, PHP_URL_QUERY);
172: $el->action = str_replace("?$query", '', $el->action);
173: $s = '';
174: foreach (preg_split('#[;&]#', $query, NULL, PREG_SPLIT_NO_EMPTY) as $param) {
175: $parts = explode('=', $param, 2);
176: $name = urldecode($parts[0]);
177: if (!isset($this->form[$name])) {
178: $s .= Html::el('input', ['type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])]);
179: }
180: }
181: return $el->startTag() . ($s ? "\n\t" . $this->getWrapper('hidden container')->setHtml($s) : '');
182:
183: } else {
184: return $this->form->getElementPrototype()->startTag();
185: }
186: }
187:
188:
189: 190: 191: 192:
193: public function renderEnd()
194: {
195: $s = '';
196: foreach ($this->form->getControls() as $control) {
197: if ($control->getOption('type') === 'hidden' && !$control->getOption('rendered')) {
198: $s .= $control->getControl();
199: }
200: }
201: if (iterator_count($this->form->getComponents(TRUE, Nette\Forms\Controls\TextInput::class)) < 2) {
202: $s .= '<!--[if IE]><input type=IEbug disabled style="display:none"><![endif]-->';
203: }
204: if ($s) {
205: $s = $this->getWrapper('hidden container')->setHtml($s) . "\n";
206: }
207:
208: return $s . $this->form->getElementPrototype()->endTag() . "\n";
209: }
210:
211:
212: 213: 214: 215:
216: public function renderErrors(Nette\Forms\IControl $control = NULL, $own = TRUE)
217: {
218: $errors = $control
219: ? $control->getErrors()
220: : ($own ? $this->form->getOwnErrors() : $this->form->getErrors());
221: if (!$errors) {
222: return;
223: }
224: $container = $this->getWrapper($control ? 'control errorcontainer' : 'error container');
225: $item = $this->getWrapper($control ? 'control erroritem' : 'error item');
226:
227: foreach ($errors as $error) {
228: $item = clone $item;
229: if ($error instanceof IHtmlString) {
230: $item->addHtml($error);
231: } else {
232: $item->setText($error);
233: }
234: $container->addHtml($item);
235: }
236: return "\n" . $container->render($control ? 1 : 0);
237: }
238:
239:
240: 241: 242: 243:
244: public function renderBody()
245: {
246: $s = $remains = '';
247:
248: $defaultContainer = $this->getWrapper('group container');
249: $translator = $this->form->getTranslator();
250:
251: foreach ($this->form->getGroups() as $group) {
252: if (!$group->getControls() || !$group->getOption('visual')) {
253: continue;
254: }
255:
256: $container = $group->getOption('container', $defaultContainer);
257: $container = $container instanceof Html ? clone $container : Html::el($container);
258:
259: $id = $group->getOption('id');
260: if ($id) {
261: $container->id = $id;
262: }
263:
264: $s .= "\n" . $container->startTag();
265:
266: $text = $group->getOption('label');
267: if ($text instanceof IHtmlString) {
268: $s .= $this->getWrapper('group label')->addHtml($text);
269:
270: } elseif (is_string($text)) {
271: if ($translator !== NULL) {
272: $text = $translator->translate($text);
273: }
274: $s .= "\n" . $this->getWrapper('group label')->setText($text) . "\n";
275: }
276:
277: $text = $group->getOption('description');
278: if ($text instanceof IHtmlString) {
279: $s .= $text;
280:
281: } elseif (is_string($text)) {
282: if ($translator !== NULL) {
283: $text = $translator->translate($text);
284: }
285: $s .= $this->getWrapper('group description')->setText($text) . "\n";
286: }
287:
288: $s .= $this->renderControls($group);
289:
290: $remains = $container->endTag() . "\n" . $remains;
291: if (!$group->getOption('embedNext')) {
292: $s .= $remains;
293: $remains = '';
294: }
295: }
296:
297: $s .= $remains . $this->renderControls($this->form);
298:
299: $container = $this->getWrapper('form container');
300: $container->setHtml($s);
301: return $container->render(0);
302: }
303:
304:
305: 306: 307: 308: 309:
310: public function renderControls($parent)
311: {
312: if (!($parent instanceof Nette\Forms\Container || $parent instanceof Nette\Forms\ControlGroup)) {
313: throw new Nette\InvalidArgumentException('Argument must be Nette\Forms\Container or Nette\Forms\ControlGroup instance.');
314: }
315:
316: $container = $this->getWrapper('controls container');
317:
318: $buttons = NULL;
319: foreach ($parent->getControls() as $control) {
320: if ($control->getOption('rendered') || $control->getOption('type') === 'hidden' || $control->getForm(FALSE) !== $this->form) {
321:
322:
323: } elseif ($control->getOption('type') === 'button') {
324: $buttons[] = $control;
325:
326: } else {
327: if ($buttons) {
328: $container->addHtml($this->renderPairMulti($buttons));
329: $buttons = NULL;
330: }
331: $container->addHtml($this->renderPair($control));
332: }
333: }
334:
335: if ($buttons) {
336: $container->addHtml($this->renderPairMulti($buttons));
337: }
338:
339: $s = '';
340: if (count($container)) {
341: $s .= "\n" . $container . "\n";
342: }
343:
344: return $s;
345: }
346:
347:
348: 349: 350: 351:
352: public function renderPair(Nette\Forms\IControl $control)
353: {
354: $pair = $this->getWrapper('pair container');
355: $pair->addHtml($this->renderLabel($control));
356: $pair->addHtml($this->renderControl($control));
357: $pair->class($this->getValue($control->isRequired() ? 'pair .required' : 'pair .optional'), TRUE);
358: $pair->class($control->hasErrors() ? $this->getValue('pair .error') : NULL, TRUE);
359: $pair->class($control->getOption('class'), TRUE);
360: if (++$this->counter % 2) {
361: $pair->class($this->getValue('pair .odd'), TRUE);
362: }
363: $pair->id = $control->getOption('id');
364: return $pair->render(0);
365: }
366:
367:
368: 369: 370: 371: 372:
373: public function renderPairMulti(array $controls)
374: {
375: $s = [];
376: foreach ($controls as $control) {
377: if (!$control instanceof Nette\Forms\IControl) {
378: throw new Nette\InvalidArgumentException('Argument must be array of Nette\Forms\IControl instances.');
379: }
380: $description = $control->getOption('description');
381: if ($description instanceof IHtmlString) {
382: $description = ' ' . $description;
383:
384: } elseif (is_string($description)) {
385: if ($control instanceof Nette\Forms\Controls\BaseControl) {
386: $description = $control->translate($description);
387: }
388: $description = ' ' . $this->getWrapper('control description')->setText($description);
389:
390: } else {
391: $description = '';
392: }
393:
394: $control->setOption('rendered', TRUE);
395: $el = $control->getControl();
396: if ($el instanceof Html && $el->getName() === 'input') {
397: $el->class($this->getValue("control .$el->type"), TRUE);
398: }
399: $s[] = $el . $description;
400: }
401: $pair = $this->getWrapper('pair container');
402: $pair->addHtml($this->renderLabel($control));
403: $pair->addHtml($this->getWrapper('control container')->setHtml(implode(' ', $s)));
404: return $pair->render(0);
405: }
406:
407:
408: 409: 410: 411:
412: public function renderLabel(Nette\Forms\IControl $control)
413: {
414: $suffix = $this->getValue('label suffix') . ($control->isRequired() ? $this->getValue('label requiredsuffix') : '');
415: $label = $control->getLabel();
416: if ($label instanceof Html) {
417: $label->addHtml($suffix);
418: if ($control->isRequired()) {
419: $label->class($this->getValue('control .required'), TRUE);
420: }
421: } elseif ($label != NULL) {
422: $label .= $suffix;
423: }
424: return $this->getWrapper('label container')->setHtml($label);
425: }
426:
427:
428: 429: 430: 431:
432: public function renderControl(Nette\Forms\IControl $control)
433: {
434: $body = $this->getWrapper('control container');
435: if ($this->counter % 2) {
436: $body->class($this->getValue('control .odd'), TRUE);
437: }
438:
439: $description = $control->getOption('description');
440: if ($description instanceof IHtmlString) {
441: $description = ' ' . $description;
442:
443: } elseif (is_string($description)) {
444: if ($control instanceof Nette\Forms\Controls\BaseControl) {
445: $description = $control->translate($description);
446: }
447: $description = ' ' . $this->getWrapper('control description')->setText($description);
448:
449: } else {
450: $description = '';
451: }
452:
453: if ($control->isRequired()) {
454: $description = $this->getValue('control requiredsuffix') . $description;
455: }
456:
457: $control->setOption('rendered', TRUE);
458: $el = $control->getControl();
459: if ($el instanceof Html && $el->getName() === 'input') {
460: $el->class($this->getValue("control .$el->type"), TRUE);
461: }
462: return $body->setHtml($el . $description . $this->renderErrors($control));
463: }
464:
465:
466: 467: 468: 469:
470: protected function getWrapper($name)
471: {
472: $data = $this->getValue($name);
473: return $data instanceof Html ? clone $data : Html::el($data);
474: }
475:
476:
477: 478: 479: 480:
481: protected function getValue($name)
482: {
483: $name = explode(' ', $name);
484: $data = & $this->wrappers[$name[0]][$name[1]];
485: return $data;
486: }
487:
488: }
489: