Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • DefaultFormRenderer
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  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:  * Converts a Form into the HTML output.
 17:  */
 18: class DefaultFormRenderer implements Nette\Forms\IFormRenderer
 19: {
 20:     use Nette\SmartObject;
 21: 
 22:     /**
 23:      *  /--- form.container
 24:      *
 25:      *    /--- error.container
 26:      *      .... error.item [.class]
 27:      *    \---
 28:      *
 29:      *    /--- hidden.container
 30:      *      .... HIDDEN CONTROLS
 31:      *    \---
 32:      *
 33:      *    /--- group.container
 34:      *      .... group.label
 35:      *      .... group.description
 36:      *
 37:      *      /--- controls.container
 38:      *
 39:      *        /--- pair.container [.required .optional .odd]
 40:      *
 41:      *          /--- label.container
 42:      *            .... LABEL
 43:      *            .... label.suffix
 44:      *            .... label.requiredsuffix
 45:      *          \---
 46:      *
 47:      *          /--- control.container [.odd]
 48:      *            .... CONTROL [.required .text .password .file .submit .button]
 49:      *            .... control.requiredsuffix
 50:      *            .... control.description
 51:      *            .... control.errorcontainer + control.erroritem
 52:      *          \---
 53:      *        \---
 54:      *      \---
 55:      *    \---
 56:      *  \--
 57:      * @var array of HTML tags */
 58:     public $wrappers = [
 59:         'form' => [
 60:             'container' => null,
 61:         ],
 62: 
 63:         'error' => [
 64:             'container' => 'ul class=error',
 65:             'item' => 'li',
 66:         ],
 67: 
 68:         'group' => [
 69:             'container' => 'fieldset',
 70:             'label' => 'legend',
 71:             'description' => 'p',
 72:         ],
 73: 
 74:         'controls' => [
 75:             'container' => 'table',
 76:         ],
 77: 
 78:         'pair' => [
 79:             'container' => 'tr',
 80:             '.required' => 'required',
 81:             '.optional' => null,
 82:             '.odd' => null,
 83:             '.error' => null,
 84:         ],
 85: 
 86:         'control' => [
 87:             'container' => 'td',
 88:             '.odd' => null,
 89: 
 90:             'description' => 'small',
 91:             'requiredsuffix' => '',
 92:             'errorcontainer' => 'span class=error',
 93:             'erroritem' => '',
 94: 
 95:             '.required' => 'required',
 96:             '.text' => 'text',
 97:             '.password' => 'text',
 98:             '.file' => 'text',
 99:             '.email' => 'text',
100:             '.number' => 'text',
101:             '.submit' => 'button',
102:             '.image' => 'imagebutton',
103:             '.button' => 'button',
104:         ],
105: 
106:         'label' => [
107:             'container' => 'th',
108:             'suffix' => null,
109:             'requiredsuffix' => '',
110:         ],
111: 
112:         'hidden' => [
113:             'container' => null,
114:         ],
115:     ];
116: 
117:     /** @var Nette\Forms\Form */
118:     protected $form;
119: 
120:     /** @var int */
121:     protected $counter;
122: 
123: 
124:     /**
125:      * Provides complete form rendering.
126:      * @param  Nette\Forms\Form
127:      * @param  string 'begin', 'errors', 'ownerrors', 'body', 'end' or empty to render all
128:      * @return string
129:      */
130:     public function render(Nette\Forms\Form $form, $mode = null)
131:     {
132:         if ($this->form !== $form) {
133:             $this->form = $form;
134:         }
135: 
136:         $s = '';
137:         if (!$mode || $mode === 'begin') {
138:             $s .= $this->renderBegin();
139:         }
140:         if (!$mode || strtolower($mode) === 'ownerrors') {
141:             $s .= $this->renderErrors();
142: 
143:         } elseif ($mode === 'errors') {
144:             $s .= $this->renderErrors(null, false);
145:         }
146:         if (!$mode || $mode === 'body') {
147:             $s .= $this->renderBody();
148:         }
149:         if (!$mode || $mode === 'end') {
150:             $s .= $this->renderEnd();
151:         }
152:         return $s;
153:     }
154: 
155: 
156:     /**
157:      * Renders form begin.
158:      * @return string
159:      */
160:     public function renderBegin()
161:     {
162:         $this->counter = 0;
163: 
164:         foreach ($this->form->getControls() as $control) {
165:             $control->setOption('rendered', false);
166:         }
167: 
168:         if ($this->form->isMethod('get')) {
169:             $el = clone $this->form->getElementPrototype();
170:             $query = parse_url($el->action, PHP_URL_QUERY);
171:             $el->action = str_replace("?$query", '', $el->action);
172:             $s = '';
173:             foreach (preg_split('#[;&]#', $query, -1, PREG_SPLIT_NO_EMPTY) as $param) {
174:                 $parts = explode('=', $param, 2);
175:                 $name = urldecode($parts[0]);
176:                 $prefix = explode('[', $name, 2)[0];
177:                 if (!isset($this->form[$prefix])) {
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:      * Renders form end.
191:      * @return string
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:      * Renders validation errors (per form or per control).
214:      * @return string
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:      * Renders form body.
242:      * @return string
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 ($text != null) { // intentionally ==
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 ($text != null) { // intentionally ==
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:      * Renders group of controls.
307:      * @param  Nette\Forms\Container|Nette\Forms\ControlGroup
308:      * @return string
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:                 // skip
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:      * Renders single visual row.
350:      * @return string
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:      * Renders single visual row of multiple controls.
370:      * @param  Nette\Forms\IControl[]
371:      * @return string
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 ($description != null) { // intentionally ==
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:      * Renders 'label' part of visual row of controls.
410:      * @return Html
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) { // @intentionally ==
422:             $label .= $suffix;
423:         }
424:         return $this->getWrapper('label container')->setHtml($label);
425:     }
426: 
427: 
428:     /**
429:      * Renders 'control' part of visual row of controls.
430:      * @return Html
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 ($description != null) { // intentionally ==
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:      * @param  string
468:      * @return Html
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:      * @param  string
479:      * @return mixed
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: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0