Overview

Namespaces

  • Budovy
  • Kdyby
    • BootstrapFormRenderer
      • DI
      • Latte
  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • Nextras
    • Datagrid
  • None
  • PHP
  • Tester
    • CodeCoverage
    • Runner
      • Output
  • Vodacek
    • Forms
      • Controls
  • WebLoader
    • Filter
    • Nette

Classes

  • BootstrapRenderer
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Kdyby (http://www.kdyby.org)
  5:  *
  6:  * Copyright (c) 2008 Filip Procházka (filip@prochazka.su)
  7:  *
  8:  * For the full copyright and license information, please view the file license.md that was distributed with this source code.
  9:  */
 10: 
 11: namespace Kdyby\BootstrapFormRenderer;
 12: 
 13: use Nette;
 14: use Nette\Forms\Controls;
 15: use Nette\Iterators\Filter;
 16: use Nette\Latte\Macros\FormMacros;
 17: use Nette\Templating\FileTemplate;
 18: use Nette\Utils\Html;
 19: 
 20: 
 21: 
 22: /**
 23:  * Created with twitter bootstrap in mind.
 24:  *
 25:  * <code>
 26:  * $form->setRenderer(new Kdyby\BootstrapFormRenderer\BootstrapRenderer);
 27:  * </code>
 28:  *
 29:  * @author Pavel Ptacek
 30:  * @author Filip Procházka
 31:  */
 32: class BootstrapRenderer extends Nette\Object implements Nette\Forms\IFormRenderer
 33: {
 34: 
 35:     /**
 36:      * set to false, if you want to display the field errors also as form errors
 37:      * @var bool
 38:      */
 39:     public $errorsAtInputs = TRUE;
 40: 
 41:     /**
 42:      * Groups that should be rendered first
 43:      */
 44:     public $priorGroups = array();
 45: 
 46:     /**
 47:      * @var \Nette\Forms\Form
 48:      */
 49:     private $form;
 50: 
 51:     /**
 52:      * @var \Nette\Templating\Template|\stdClass
 53:      */
 54:     private $template;
 55: 
 56: 
 57: 
 58:     /**
 59:      * @param \Nette\Templating\FileTemplate $template
 60:      */
 61:     public function __construct(FileTemplate $template = NULL)
 62:     {
 63:         $this->template = $template;
 64:     }
 65: 
 66: 
 67: 
 68:     /**
 69:      * Render the templates
 70:      *
 71:      * @param \Nette\Forms\Form $form
 72:      * @param string $mode
 73:      * @param array $args
 74:      * @return void
 75:      */
 76:     public function render(Nette\Forms\Form $form, $mode = NULL, $args = NULL)
 77:     {
 78:         if ($this->template === NULL) {
 79:             if ($presenter = $form->lookup('Nette\Application\UI\Presenter', FALSE)) {
 80:                 /** @var \Nette\Application\UI\Presenter $presenter */
 81:                 $this->template = clone $presenter->getTemplate();
 82: 
 83:             } else {
 84:                 $this->template = new FileTemplate();
 85:                 $this->template->registerFilter(new Nette\Latte\Engine());
 86:             }
 87:         }
 88: 
 89:         if ($this->form !== $form) {
 90:             $this->form = $form;
 91: 
 92:             // translators
 93:             if ($translator = $this->form->getTranslator()) {
 94:                 $this->template->setTranslator($translator);
 95:             }
 96: 
 97:             // controls placeholders & classes
 98:             foreach ($this->form->getControls() as $control) {
 99:                 $this->prepareControl($control);
100:             }
101: 
102:             $formEl = $form->getElementPrototype();
103:             if (!($classes = self::getClasses($formEl)) || stripos($classes, 'form-') === FALSE) {
104:                 $formEl->addClass('form-horizontal');
105:             }
106: 
107:         } elseif ($mode === 'begin') {
108:             foreach ($this->form->getControls() as $control) {
109:                 /** @var \Nette\Forms\Controls\BaseControl $control */
110:                 $control->setOption('rendered', FALSE);
111:             }
112:         }
113: 
114:         $this->template->setFile(__DIR__ . '/@form.latte');
115:         $this->template->setParameters(
116:             array_fill_keys(array('control', '_control', 'presenter', '_presenter'), NULL) +
117:             array('_form' => $this->form, 'form' => $this->form, 'renderer' => $this)
118:         );
119: 
120:         if ($mode === NULL) {
121:             if ($args) {
122:                 $this->form->getElementPrototype()->addAttributes($args);
123:             }
124:             $this->template->render();
125: 
126:         } elseif ($mode === 'begin') {
127:             FormMacros::renderFormBegin($this->form, (array)$args);
128: 
129:         } elseif ($mode === 'end') {
130:             FormMacros::renderFormEnd($this->form);
131: 
132:         } else {
133: 
134:             $attrs = array('input' => array(), 'label' => array());
135:             foreach ((array) $args as $key => $val) {
136:                 if (stripos($key, 'input-') === 0) {
137:                     $attrs['input'][substr($key, 6)] = $val;
138: 
139:                 } elseif (stripos($key, 'label-') === 0) {
140:                     $attrs['label'][substr($key, 6)] = $val;
141:                 }
142:             }
143: 
144:             $this->template->setFile(__DIR__ . '/@parts.latte');
145:             $this->template->mode = $mode;
146:             $this->template->attrs = (array) $attrs;
147:             $this->template->render();
148:         }
149:     }
150: 
151: 
152: 
153:     /**
154:      * @param \Nette\Forms\Controls\BaseControl $control
155:      */
156:     private function prepareControl(Controls\BaseControl $control)
157:     {
158:         $translator = $this->form->getTranslator();
159:         $control->setOption('rendered', FALSE);
160: 
161:         if ($control->isRequired()) {
162:             $control->getLabelPrototype()->addClass('required');
163:             $control->setOption('required', TRUE);
164:         }
165: 
166:         $el = $control->getControlPrototype();
167:         if ($placeholder = $control->getOption('placeholder')) {
168:             if (!$placeholder instanceof Html && $translator) {
169:                 $placeholder = $translator->translate($placeholder);
170:             }
171:             $el->placeholder($placeholder);
172:         }
173: 
174:         if ($control->controlPrototype->type === 'email'
175:             && $control->getOption('input-prepend') === NULL
176:         ) {
177:             $control->setOption('input-prepend', '@');
178:         }
179: 
180:         if ($control instanceof Nette\Forms\ISubmitterControl) {
181:             $el->addClass('btn');
182: 
183:         } else {
184:             $label = $control->labelPrototype;
185:             if ($control instanceof Controls\Checkbox) {
186:                 $label->addClass('checkbox');
187: 
188:             } elseif (!$control instanceof Controls\RadioList) {
189:                 $label->addClass('control-label');
190:             }
191: 
192:             $control->setOption('pairContainer', $pair = Html::el('div'));
193:             $pair->id = $control->htmlId . '-pair';
194:             $pair->addClass('control-group');
195:             if ($control->getOption('required', FALSE)) {
196:                 $pair->addClass('required');
197:             }
198:             if ($control->errors) {
199:                 $pair->addClass('error');
200:             }
201: 
202:             if ($prepend = $control->getOption('input-prepend')) {
203:                 $prepend = Html::el('span', array('class' => 'add-on'))
204:                     ->{$prepend instanceof Html ? 'add' : 'setText'}($prepend);
205:                 $control->setOption('input-prepend', $prepend);
206:             }
207: 
208:             if ($append = $control->getOption('input-append')) {
209:                 $append = Html::el('span', array('class' => 'add-on'))
210:                     ->{$append instanceof Html ? 'add' : 'setText'}($append);
211:                 $control->setOption('input-append', $append);
212:             }
213:         }
214:     }
215: 
216: 
217: 
218:     /**
219:      * @return array
220:      */
221:     public function findErrors()
222:     {
223:         $formErrors = $this->form->getErrors();
224: 
225:         if (!$formErrors) {
226:             return array();
227:         }
228: 
229:         $form = $this->form;
230:         $translate = function ($errors) use ($form) {
231:             if ($translator = $form->getTranslator()) { // If we have translator, translate!
232:                 foreach ($errors as $key => $val) {
233:                     $errors[$key] = $translator->translate($val);
234:                 }
235:             }
236: 
237:             return $errors;
238:         };
239: 
240:         if (!$this->errorsAtInputs) {
241:             return $translate($formErrors);
242:         }
243: 
244:         return $translate($this->form->getErrors());
245:     }
246: 
247: 
248: 
249:     /**
250:      * @throws \RuntimeException
251:      * @return object[]
252:      */
253:     public function findGroups()
254:     {
255:         $formGroups = $visitedGroups = array();
256:         foreach ($this->priorGroups as $i => $group) {
257:             if (!$group instanceof Nette\Forms\ControlGroup) {
258:                 if (!$group = $this->form->getGroup($group)) {
259:                     $groupName = (string)$this->priorGroups[$i];
260:                     throw new \RuntimeException("Form has no group $groupName.");
261:                 }
262:             }
263: 
264:             $visitedGroups[] = $group;
265:             if ($group = $this->processGroup($group)) {
266:                 $formGroups[] = $group;
267:             }
268:         }
269: 
270:         foreach ($this->form->groups as $group) {
271:             if (!in_array($group, $visitedGroups, TRUE) && ($group = $this->processGroup($group))) {
272:                 $formGroups[] = $group;
273:             }
274:         }
275: 
276:         return $formGroups;
277:     }
278: 
279: 
280: 
281:     /**
282:      * @param \Nette\Forms\Container $container
283:      * @param boolean $buttons
284:      * @return \Iterator
285:      */
286:     public function findControls(Nette\Forms\Container $container = NULL, $buttons = NULL)
287:     {
288:         $container = $container ? : $this->form;
289:         return new Filter($container->getControls(), function ($control) use ($buttons) {
290:             $control = $control instanceof Filter ? $control->current() : $control;
291:             $isButton = $control instanceof Controls\Button || $control instanceof Nette\Forms\ISubmitterControl;
292:             return !$control->getOption('rendered')
293:                 && !$control instanceof Controls\HiddenField
294:                 && (($buttons === TRUE && $isButton) || ($buttons === FALSE && !$isButton) || $buttons === NULL);
295:         });
296:     }
297: 
298: 
299: 
300:     /**
301:      * @internal
302:      * @param \Nette\Forms\ControlGroup $group
303:      * @return object
304:      */
305:     public function processGroup(Nette\Forms\ControlGroup $group)
306:     {
307:         if (!$group->getOption('visual') || !$group->getControls()) {
308:             return NULL;
309:         }
310: 
311:         $groupLabel = $group->getOption('label');
312:         $groupDescription = $group->getOption('description');
313: 
314:         // If we have translator, translate!
315:         if ($translator = $this->form->getTranslator()) {
316:             if (!$groupLabel instanceof Html) {
317:                 $groupLabel = $translator->translate($groupLabel);
318:             }
319:             if (!$groupDescription instanceof Html) {
320:                 $groupDescription = $translator->translate($groupDescription);
321:             }
322:         }
323: 
324:         $controls = array_filter($group->getControls(), function (Controls\BaseControl $control) {
325:             return !$control->getOption('rendered')
326:                 && !$control instanceof Controls\HiddenField;
327:         });
328: 
329:         if (!$controls) {
330:             return NULL; // do not render empty groups
331:         }
332: 
333:         $groupAttrs = $group->getOption('container', Html::el())->setName(NULL);
334:         /** @var Html $groupAttrs */
335:         $groupAttrs->attrs += array_diff_key($group->getOptions(), array_fill_keys(array(
336:             'container', 'label', 'description', 'visual' // these are not attributes
337:         ), NULL));
338: 
339:         // fake group
340:         return (object)(array(
341:             'controls' => $controls,
342:             'label' => $groupLabel,
343:             'description' => $groupDescription,
344:             'attrs' => $groupAttrs,
345:         ) + $group->getOptions());
346:     }
347: 
348: 
349: 
350:     /**
351:      * @internal
352:      * @param \Nette\Forms\Controls\BaseControl $control
353:      * @return string
354:      */
355:     public static function getControlName(Controls\BaseControl $control)
356:     {
357:         return $control->lookupPath('Nette\Forms\Form');
358:     }
359: 
360: 
361: 
362:     /**
363:      * @internal
364:      * @param \Nette\Forms\Controls\BaseControl $control
365:      * @return \Nette\Utils\Html
366:      */
367:     public static function getControlDescription(Controls\BaseControl $control)
368:     {
369:         if (!$desc = $control->getOption('description')) {
370:             return Html::el();
371:         }
372: 
373:         // If we have translator, translate!
374:         if (!$desc instanceof Html && ($translator = $control->form->getTranslator())) {
375:             $desc = $translator->translate($desc); // wtf?
376:         }
377: 
378:         // create element
379:         return Html::el('p', array('class' => 'help-block'))
380:             ->{$desc instanceof Html ? 'add' : 'setText'}($desc);
381:     }
382: 
383: 
384: 
385:     /**
386:      * @internal
387:      * @param \Nette\Forms\Controls\BaseControl $control
388:      * @return \Nette\Utils\Html
389:      */
390:     public function getControlError(Controls\BaseControl $control)
391:     {
392:         if (!($errors = $control->getErrors()) || !$this->errorsAtInputs) {
393:             return Html::el();
394:         }
395:         $error = reset($errors);
396: 
397:         // If we have translator, translate!
398:         if (!$error instanceof Html && ($translator = $control->form->getTranslator())) {
399:             $error = $translator->translate($error); // wtf?
400:         }
401: 
402:         // create element
403:         return Html::el('p', array('class' => 'help-inline'))
404:             ->{$error instanceof Html ? 'add' : 'setText'}($error);
405:     }
406: 
407: 
408: 
409:     /**
410:      * @internal
411:      * @param \Nette\Forms\Controls\BaseControl $control
412:      * @return string
413:      */
414:     public static function getControlTemplate(Controls\BaseControl $control)
415:     {
416:         return $control->getOption('template');
417:     }
418: 
419: 
420: 
421:     /**
422:      * @internal
423:      * @param \Nette\Forms\IControl $control
424:      * @return bool
425:      */
426:     public static function isButton(Nette\Forms\IControl $control)
427:     {
428:         return $control instanceof Controls\Button;
429:     }
430: 
431: 
432: 
433:     /**
434:      * @internal
435:      * @param \Nette\Forms\IControl $control
436:      * @return bool
437:      */
438:     public static function isSubmitButton(Nette\Forms\IControl $control = NULL)
439:     {
440:         return $control instanceof Nette\Forms\ISubmitterControl;
441:     }
442: 
443: 
444: 
445:     /**
446:      * @internal
447:      * @param \Nette\Forms\IControl $control
448:      * @return bool
449:      */
450:     public static function isCheckbox(Nette\Forms\IControl $control)
451:     {
452:         return $control instanceof Controls\Checkbox;
453:     }
454: 
455: 
456: 
457:     /**
458:      * @internal
459:      * @param \Nette\Forms\IControl $control
460:      * @return bool
461:      */
462:     public static function isRadioList(Nette\Forms\IControl $control)
463:     {
464:         return $control instanceof Controls\RadioList;
465:     }
466: 
467: 
468: 
469:     /**
470:      * @internal
471:      * @param \Nette\Forms\IControl $control
472:      * @return bool
473:      */
474:     public static function isCheckboxList(Nette\Forms\IControl $control)
475:     {
476:         foreach (array('Nette\Forms\Controls\\', 'Kdyby\Forms\Controls\\', '',) as $ns) {
477:             if (class_exists($class = $ns . 'CheckboxList', FALSE) && $control instanceof $class) {
478:                 return TRUE;
479:             }
480:         }
481: 
482:         return FALSE;
483:     }
484: 
485: 
486: 
487:     /**
488:      * @internal
489:      * @param \Nette\Forms\Controls\RadioList $control
490:      * @return bool
491:      */
492:     public static function getRadioListItems(Controls\RadioList $control)
493:     {
494:         $items = array();
495:         foreach ($control->items as $key => $value) {
496:             $el = $control->getControlPart($key);
497:             if ($el->getName() === 'input') {
498:                 $items[$key] = $radio = (object) array(
499:                     'input' => $el,
500:                     'label' => $cap = $control->getLabelPart($key),
501:                     'caption' => $cap->getText(),
502:                 );
503: 
504:             } else {
505:                 $items[$key] = $radio = (object) array(
506:                     'input' => $el[0],
507:                     'label' => $el[1],
508:                     'caption' => $el[1]->getText(),
509:                 );
510:             }
511: 
512:             $radio->label->addClass('radio');
513:             $radio->html = clone $radio->label;
514:             $radio->html->insert(0, $radio->input);
515:         }
516: 
517:         return $items;
518:     }
519: 
520: 
521: 
522:     /**
523:      * @internal
524:      * @param \Nette\Forms\Controls\BaseControl $control
525:      * @throws \Nette\InvalidArgumentException
526:      * @return bool
527:      */
528:     public static function getCheckboxListItems(Controls\BaseControl $control)
529:     {
530:         $items = array();
531:         foreach ($control->items as $key => $value) {
532:             $el = $control->getControl($key);
533:             $el[1]->addClass('checkbox')->addClass('inline');
534: 
535:             $items[$key] = $check = (object)array(
536:                 'input' => $el[0],
537:                 'label' => $el[1],
538:                 'caption' => $el[1]->getText(),
539:             );
540: 
541:             $check->html = clone $check->label;
542:             $check->html->insert(0, $check->input);
543:         }
544: 
545:         return $items;
546:     }
547: 
548: 
549: 
550:     /**
551:      * @param \Nette\Forms\Controls\BaseControl $control
552:      * @return \Nette\Utils\Html
553:      */
554:     public static function getLabelBody(Controls\BaseControl $control)
555:     {
556:         $label = $control->getLabel();
557:         return $label;
558:     }
559: 
560: 
561: 
562:     /**
563:      * @param \Nette\Forms\Controls\BaseControl $control
564:      * @param string $class
565:      * @return bool
566:      */
567:     public static function controlHasClass(Controls\BaseControl $control, $class)
568:     {
569:         $classes = explode(' ', self::getClasses($control->controlPrototype));
570:         return in_array($class, $classes, TRUE);
571:     }
572: 
573: 
574: 
575:     /**
576:      * @param \Nette\Utils\Html $_this
577:      * @param array $attrs
578:      * @return \Nette\Utils\Html
579:      */
580:     public static function mergeAttrs(Html $_this = NULL, array $attrs)
581:     {
582:         if ($_this === NULL) {
583:             return Html::el();
584:         }
585: 
586:         $_this->attrs = array_merge_recursive($_this->attrs, $attrs);
587:         return $_this;
588:     }
589: 
590: 
591: 
592:     /**
593:      * @param \Nette\Utils\Html $el
594:      * @return bool
595:      */
596:     private static function getClasses(Html $el)
597:     {
598:         if (is_array($el->class)) {
599:             $classes = array_filter(array_merge(array_keys($el->class), $el->class), 'is_string');
600:             return implode(' ', $classes);
601:         }
602:         return $el->class;
603:     }
604: 
605: }
606: 
API documentation generated by ApiGen 2.8.0