• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

contributte / forms-bootstrap / #71

22 Apr 2024 10:51AM UTC coverage: 83.039%. Remained the same
#71

Pull #95

github

dakorpar
compativility with nette forms 3.2.2
Pull Request #95: Nette forms3.2.2

705 of 849 relevant lines covered (83.04%)

5.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.76
/src/BootstrapRenderer.php
1
<?php declare(strict_types = 1);
2

3
namespace Contributte\FormsBootstrap;
4

5
use Contributte\FormsBootstrap\Enums\BootstrapVersion;
6
use Contributte\FormsBootstrap\Enums\RendererConfig as Cnf;
7
use Contributte\FormsBootstrap\Enums\RendererOptions;
8
use Contributte\FormsBootstrap\Enums\RenderMode;
9
use Contributte\FormsBootstrap\Grid\BootstrapRow;
10
use Contributte\FormsBootstrap\Inputs\IValidationInput;
11
use Nette;
12
use Nette\Forms\Control;
13
use Nette\Forms\Controls\BaseControl;
14
use Nette\Forms\Form;
15
use Nette\Forms\FormRenderer;
16
use Nette\Utils\Html;
17

18
/**
19
 * Converts a Form into Bootstrap 4 HTML output.
20
 *
21
 * @property int        $mode
22
 * @property string     $gridBreakPoint    Bootstrap grid breakpoint for side-by-side view. Default is 'sm'.
23
 *           NULL means not to use a breakpoint
24
 * @property-read array $config
25
 * @property-read array $configOverride
26
 * @property bool       $groupHidden       if true, hidden fields will be grouped at the end. If false,
27
 *           hidden fields are placed where they were added. Default is true.
28
 */
29
class BootstrapRenderer implements FormRenderer
30
{
31

32
        use Nette\SmartObject;
33

34
        public const DEFAULT_LABEL_COLUMNS = 3;
35
        public const DEFAULT_CONTROL_COLUMNS = 9;
36

37
        /**
38
         * Bootstrap grid breakpoint for side-by-side view
39
         *
40
         * @var string
41
         */
42
        protected $gridBreakPoint = 'sm';
43

44
        /** @var BootstrapForm */
45
        protected $form;
46

47
        /** @var int */
48
        protected $labelColumns = self::DEFAULT_LABEL_COLUMNS;
49

50
        /** @var int */
51
        protected $controlColumns = self::DEFAULT_CONTROL_COLUMNS;
52

53
        /** @var int current render mode */
54
        private $renderMode = RenderMode::SIDE_BY_SIDE_MODE;
55

56
        /** @var bool */
57
        private $groupHidden = true;
58

59
        public function __construct(int $mode = RenderMode::VERTICAL_MODE)
60
        {
61
                $this->setMode($mode);
47✔
62
        }
63

64
        /**
65
         * Sets the form for which to render. Used only if a specific function of the renderer must be executed
66
         * outside of render(), such as during assisted manual rendering.
67
         */
68
        public function attachForm(BootstrapForm $form): void
69
        {
70
                $this->form = $form;
12✔
71
        }
72

73
        /**
74
         * Turns configuration or and existing element and configures it appropriately
75
         *
76
         * @param string[]|string $config top-level config key
77
         * @param Html|null $el elem to config.
78
         */
79
        public function configElem($config, ?Html $el = null): ?Html
80
        {
81
                if (is_scalar($config)) {
13✔
82
                        $config = $this->fetchConfig($config);
13✔
83
                }
84

85
                // first define which element we want to work with
86
                if (isset($config[Cnf::ELEMENT_NAME])) {
13✔
87
                        $name = $config[Cnf::ELEMENT_NAME];
11✔
88
                        if (!$el) {
11✔
89
                                // element does not exist, so create it
90
                                $el = Html::el($name);
10✔
91
                        } else {
92
                                // element exists, but we want to change its name
93
                                $el->setName($name);
11✔
94
                        }
95
                }
96

97
                if ($el instanceof Html && $el !== null) {
13✔
98
                        $origClass = null;
13✔
99
                        // go through all config and configure element accordingly
100
                        foreach ($config as $key => $value) {
13✔
101
                                if (in_array($key, [Cnf::CLASS_SET, Cnf::CLASS_ADD, Cnf::CLASS_ADD, Cnf::CLASS_REMOVE])) {
11✔
102
                                        // we'll be working with classes, we must standardize everything to arrays, not strings for the sake of sanity
103
                                        if (!is_array($value)) {
10✔
104
                                                // configuration may contain classes as strings, but we always work with arrays
105
                                                $value = [$value];
10✔
106
                                        }
107

108
                                        $origClass = $el->getAttribute('class');
10✔
109
                                        $newClass = $origClass;
10✔
110
                                        if ($origClass === null) {
10✔
111
                                                // class is not set
112
                                                $newClass = [];
10✔
113
                                        } elseif (!is_array($origClass)) {
9✔
114
                                                // class is set, but not a array
115
                                                $newClass = explode(' ', $el->getAttribute('class'));
1✔
116
                                        }
117

118
                                        $el->setAttribute('class', $newClass);
10✔
119
                                        $origClass = $newClass;
10✔
120
                                }
121

122
                                if ($key === Cnf::CLASS_SET) {
11✔
123
                                        $el->setAttribute('class', $value);
10✔
124
                                } elseif ($key === Cnf::CLASS_ADD) {
11✔
125
                                        $el->setAttribute(
10✔
126
                                                'class',
10✔
127
                                                array_merge($origClass, $value)
10✔
128
                                        );
10✔
129
                                } elseif ($key === Cnf::CLASS_REMOVE) {
11✔
130
                                        $el->setAttribute(
×
131
                                                'class',
×
132
                                                array_diff($origClass, $value)
×
133
                                        );
×
134
                                } elseif ($key === Cnf::ATTRIBUTES) {
11✔
135
                                        foreach ($value as $attr => $attrVal) {
×
136
                                                $el->setAttribute($attr, $attrVal);
×
137
                                        }
138
                                }
139
                        }
140
                }
141

142
                // el may be null, but maybe it has a container defined
143
                if (isset($config[Cnf::CONTAINER])) {
13✔
144
                        $container = $this->configElem($config[Cnf::CONTAINER], null);
×
145
                        if ($container !== null && $el !== null) {
×
146
                                $elClone = clone $el;
×
147
                                $container->setHtml($elClone);
×
148
                        }
149

150
                        $el = $container;
×
151
                }
152

153
                return $el;
13✔
154
        }
155

156
        /**
157
         * @return mixed[]
158
         */
159
        public function getConfig(): array
160
        {
161
                return [
13✔
162
                        Cnf::FORM => [],
13✔
163
                        Cnf::GROUP => [
13✔
164
                                Cnf::ELEMENT_NAME => 'fieldset',
13✔
165
                        ],
13✔
166
                        Cnf::GROUP_LABEL => [
13✔
167
                                Cnf::ELEMENT_NAME => 'legend',
13✔
168
                        ],
13✔
169

170
                        Cnf::GRID_ROW => [
13✔
171
                                Cnf::ELEMENT_NAME => 'div',
13✔
172
                                Cnf::CLASS_ADD => BootstrapForm::getBootstrapVersion() === BootstrapVersion::V5 ? 'row' : 'form-row',
13✔
173
                        ],
13✔
174
                        Cnf::GRID_CELL => [
13✔
175
                                Cnf::ELEMENT_NAME => 'div',
13✔
176
                        ],
13✔
177

178
                        Cnf::FORM_OWN_ERRORS => [],
13✔
179
                        Cnf::FORM_OWN_ERROR => [
13✔
180
                                Cnf::ELEMENT_NAME => 'div',
13✔
181
                                Cnf::CLASS_SET => ['alert', 'alert-danger'],
13✔
182
                        ],
13✔
183

184
                        Cnf::PAIR => [
13✔
185
                                Cnf::ELEMENT_NAME => 'div',
13✔
186
                                Cnf::CLASS_SET => BootstrapForm::getBootstrapVersion() === BootstrapVersion::V5 ? 'mb-3' : 'form-group',
13✔
187
                        ],
13✔
188
                        Cnf::LABEL => [
13✔
189
                                Cnf::ELEMENT_NAME => 'label',
13✔
190
                                Cnf::CLASS_SET => BootstrapForm::getBootstrapVersion() === BootstrapVersion::V5 ? 'form-label' : null,
13✔
191
                        ],
13✔
192

193
                        Cnf::INPUT => [],
13✔
194
                        // inputs which are normally inline elements (after bootstrap classes are applied)
195
                        Cnf::INPUT_VALID => [
13✔
196
                                Cnf::CLASS_ADD => 'is-valid',
13✔
197
                        ],
13✔
198
                        Cnf::INPUT_INVALID => [
13✔
199
                                Cnf::CLASS_ADD => 'is-invalid',
13✔
200
                        ],
13✔
201

202
                        Cnf::DESCRIPTION => [
13✔
203
                                Cnf::ELEMENT_NAME => 'small',
13✔
204
                                Cnf::CLASS_SET => ['form-text', 'text-muted'],
13✔
205
                        ],
13✔
206

207
                        Cnf::FEEDBACK => [
13✔
208
                                Cnf::ELEMENT_NAME => 'div',
13✔
209
                        ],
13✔
210
                        Cnf::FEEDBACK_VALID => [
13✔
211
                                Cnf::CLASS_ADD => 'valid-feedback',
13✔
212
                        ],
13✔
213
                        Cnf::FEEDBACK_INVALID => [
13✔
214
                                Cnf::CLASS_ADD => 'invalid-feedback',
13✔
215
                        ],
13✔
216

217
                        // empty wrapper,  but it gets utilized in side-by side and inline mode
218
                        Cnf::NON_LABEL => [
13✔
219
                                Cnf::ELEMENT_NAME => null,
13✔
220
                        ],
13✔
221
                ];
13✔
222
        }
223

224
        /**
225
         * @return mixed[]
226
         */
227
        public function getConfigOverride(): array
228
        {
229
                if ($this->gridBreakPoint !== null) {
13✔
230
                        $labelColClass = 'col-' . $this->gridBreakPoint . '-' . $this->labelColumns;
13✔
231
                        $nonLabelColClass = 'col-' . $this->gridBreakPoint . '-' . $this->controlColumns;
13✔
232
                } else {
233
                        $labelColClass = 'col-' . $this->labelColumns;
×
234
                        $nonLabelColClass = 'col-' . $this->controlColumns;
×
235
                }
236

237
                $labelColClass .= ' col-form-label';
13✔
238

239
                return [
13✔
240
                        RenderMode::INLINE => [
13✔
241
                                Cnf::FORM => [
13✔
242
                                        Cnf::CLASS_ADD => 'form-inline',
13✔
243
                                ],
13✔
244
                                Cnf::NON_LABEL => [
13✔
245
                                        Cnf::ELEMENT_NAME => 'div',
13✔
246
                                ],
13✔
247
                        ],
13✔
248
                        RenderMode::SIDE_BY_SIDE_MODE => [
13✔
249
                                Cnf::PAIR => [
13✔
250
                                        Cnf::CLASS_ADD => 'row',
13✔
251
                                ],
13✔
252
                                Cnf::LABEL => [
13✔
253
                                        Cnf::CLASS_ADD => $labelColClass,
13✔
254
                                ],
13✔
255
                                Cnf::NON_LABEL => [
13✔
256
                                        Cnf::ELEMENT_NAME => 'div',
13✔
257
                                        Cnf::CLASS_SET => $nonLabelColClass,
13✔
258
                                ],
13✔
259
                        ],
13✔
260
                        RenderMode::VERTICAL_MODE => [],
13✔
261
                ];
13✔
262
        }
263

264
        public function getGridBreakPoint(): string
265
        {
266
                return $this->gridBreakPoint;
×
267
        }
268

269
        /**
270
         * @param string $gridBreakPoint null for none
271
         */
272
        public function setGridBreakPoint(string $gridBreakPoint): BootstrapRenderer
273
        {
274
                $this->gridBreakPoint = $gridBreakPoint;
×
275

276
                return $this;
×
277
        }
278

279
        /**
280
         * Returns render mode
281
         *
282
         * @see RenderMode
283
         */
284
        public function getMode(): int
285
        {
286
                return $this->renderMode;
1✔
287
        }
288

289
        /**
290
         * @see BootstrapRenderer::$groupHidden
291
         */
292
        public function isGroupHidden(): bool
293
        {
294
                return $this->groupHidden;
×
295
        }
296

297
        /**
298
         * @see BootstrapRenderer::$groupHidden
299
         */
300
        public function setGroupHidden(bool $groupHidden): BootstrapRenderer
301
        {
302
                $this->groupHidden = $groupHidden;
×
303

304
                return $this;
×
305
        }
306

307
        /**
308
         * Provides complete form rendering.
309
         *
310
         * @param BootstrapForm $form
311
         */
312
        public function render(Form $form): string
313
        {
314
                $this->attachForm($form);
12✔
315

316
                $s = '';
12✔
317
                $s .= $this->renderBegin();
12✔
318
                $s .= $this->renderFeedback();
12✔
319
                $s .= $this->renderBody();
12✔
320
                $s .= $this->renderEnd();
12✔
321

322
                return $s;
12✔
323
        }
324

325
        /**
326
         * Renders form begin.
327
         */
328
        public function renderBegin(): string
329
        {
330
                foreach ($this->form->getControls() as $control) {
12✔
331
                        $control->setOption(RendererOptions::_RENDERED, false);
12✔
332
                }
333

334
                /** @var Html $el */
335
                $el = $this->configElem('form', $this->form->getElementPrototype());
12✔
336

337
                if ($this->form->isMethod('get')) {
12✔
338
                        $el->action = (string) $el->action;
1✔
339
                        /** @noinspection PhpUndefinedFieldInspection */
340
                        $query = parse_url($el->action, PHP_URL_QUERY);
1✔
341
                        /** @noinspection PhpUndefinedFieldInspection */
342
                        $el->action = str_replace('?' . $query, '', $el->action);
1✔
343

344
                        $s = '';
1✔
345
                        $params = ($query === null || $query === false)
1✔
346
                                ? []
×
347
                                : (preg_split('#[;&]#', $query, -1, PREG_SPLIT_NO_EMPTY) ?: []);
1✔
348
                        foreach ($params as $param) {
1✔
349
                                $parts = explode('=', $param, 2);
1✔
350
                                $name = urldecode($parts[0]);
1✔
351
                                if (!isset($this->form[$name])) {
1✔
352
                                        $s .= Html::el('input', ['type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])]);
1✔
353
                                }
354
                        }
355

356
                        return $el->startTag() . PHP_EOL . $s;
1✔
357
                }
358

359
                return $el->startTag();
11✔
360
        }
361

362
        /**
363
         * Renders form body.
364
         */
365
        public function renderBody(): string
366
        {
367
                $translator = $this->form->getTranslator();
12✔
368

369
                // first render groups. They will mark their controls as rendered
370
                $groups = Html::el();
12✔
371
                foreach ($this->form->getGroups() as $group) {
12✔
372
                        if (!$group->getControls() || !$group->getOption(RendererOptions::VISUAL)) {
1✔
373
                                continue;
×
374
                        }
375

376
                        //region getting container
377

378
                        $container = $group->getOption(RendererOptions::CONTAINER);
1✔
379
                        if (is_string($container)) {
1✔
380
                                $container = $this->configElem(Cnf::GROUP, Html::el($container));
×
381
                        } elseif ($container instanceof Html) {
1✔
382
                                $container = $this->configElem(Cnf::GROUP, $container);
×
383
                        } else {
384
                                $container = $this->getElem(Cnf::GROUP);
1✔
385
                        }
386

387
                        $container->setAttribute('id', $group->getOption(RendererOptions::ID));
1✔
388

389
                        //endregion
390

391
                        //region label
392
                        $label = $group->getOption(RendererOptions::LABEL);
1✔
393
                        if ($label instanceof Html) {
1✔
394
                                $label = $this->configElem(Cnf::GROUP_LABEL, $label);
×
395
                        } elseif (is_string($label)) {
1✔
396
                                if ($translator !== null) {
1✔
397
                                        $label = $translator->translate($label);
×
398
                                }
399

400
                                $labelHtml = $this->getElem(Cnf::GROUP_LABEL);
1✔
401
                                $labelHtml->setText($label);
1✔
402
                                $label = $labelHtml;
1✔
403
                        }
404

405
                        //endregion
406

407
                        if (!empty($label)) {
1✔
408
                                $container->addHtml($label);
1✔
409
                        }
410

411
                        $controls = $this->renderControls($group);
1✔
412
                        if (!empty($controls)) {
1✔
413
                                $container->addHtml($controls);
1✔
414
                        }
415

416
                        $groups->addHtml($container);
1✔
417
                }
418

419
                // we now know which ones to skip, so render the rest
420
                $formControls = $this->renderControls($this->form);
12✔
421

422
                $out = Html::el();
12✔
423
                if (!empty($formControls)) {
12✔
424
                        $out->addHtml($formControls);
12✔
425
                }
426

427
                $out->addHtml($groups);
12✔
428

429
                return (string) $out;
12✔
430
        }
431

432
        /**
433
         * Renders 'control' part of visual row of controls.
434
         */
435
        public function renderControl(BaseControl $control): string
436
        {
437
                /** @var Html $controlHtml */
438
                $controlHtml = $control->getControl();
12✔
439
                $control->setOption(RendererOptions::_RENDERED, true);
12✔
440
                if (($this->form->showValidation || $control->hasErrors()) && $control instanceof IValidationInput) {
12✔
441
                        $controlHtml = $control->showValidation($controlHtml);
1✔
442
                }
443

444
                $controlHtml = $this->configElem(Cnf::INPUT, $controlHtml);
12✔
445

446
                return (string) $controlHtml;
12✔
447
        }
448

449
        /**
450
         * Renders group of controls.
451
         *
452
         * @param  Nette\Forms\Container|Nette\Forms\ControlGroup $parent
453
         */
454
        public function renderControls($parent): string
455
        {
456
                if (!($parent instanceof Nette\Forms\Container || $parent instanceof Nette\Forms\ControlGroup)) {
12✔
457
                        throw new Nette\InvalidArgumentException('Argument must be Nette\Forms\Container or Nette\Forms\ControlGroup instance.');
×
458
                }
459

460
                $html = Html::el();
12✔
461
                $hidden = Html::el();
12✔
462

463
                // note that these are NOT form groups, these are groups specified to group
464
                foreach ($parent->getControls() as $control) {
12✔
465
                        if ($control->getOption(RendererOptions::_RENDERED)) {
12✔
466
                                continue;
3✔
467
                        }
468

469
                        if ($control instanceof BootstrapRow) {
12✔
470
                                $html->addHtml($control->render());
3✔
471
                        } else {
472
                                if ($control->getOption(RendererOptions::TYPE) === 'hidden') {
12✔
473
                                        $isHidden = true;
12✔
474
                                        $pairHtml = $this->renderControl($control);
12✔
475
                                } else {
476
                                        $pairHtml = $this->renderPair($control);
7✔
477
                                        $isHidden = false;
7✔
478
                                }
479

480
                                if ($this->groupHidden && $isHidden) {
12✔
481
                                        $hidden->addHtml($pairHtml);
12✔
482
                                } else {
483
                                        $html->addHtml($pairHtml);
7✔
484
                                }
485
                        }
486
                }
487

488
                $html->addHtml($hidden);
12✔
489

490
                return (string) $html;
12✔
491
        }
492

493
        /**
494
         * Renders form end.
495
         */
496
        public function renderEnd(): string
497
        {
498
                return $this->form->getElementPrototype()->endTag() . "\n";
12✔
499
        }
500

501
        /**
502
         * Renders 'label' part of visual row of controls.
503
         */
504
        public function renderLabel(BaseControl $control): Html
505
        {
506
                if ($control->caption === null) {
10✔
507
                        return Html::el();
1✔
508
                }
509

510
                $controlLabel = $control->getLabel();
9✔
511

512
                if ($controlLabel instanceof Html && $controlLabel->getName() === 'label') {
9✔
513
                        // the control has already provided us with the element, no need to create our own
514
                        $controlLabel = $this->configElem(Cnf::LABEL, $controlLabel);
4✔
515
                        // just configure it to suit our needs
516

517
                        return $controlLabel;
4✔
518
                }
519

520
                if ($controlLabel === null) {
5✔
521
                        if (method_exists($control, 'allignWithInputControls') && $control->allignWithInputControls()) {
5✔
522
                                return $this->configElem(Cnf::LABEL, null);
2✔
523
                        }
524

525
                        return Html::el();
3✔
526
                }
527

528
                // the control doesn't give us <label>, se we'll create our own
529
                $labelHtml = $this->getElem(Cnf::LABEL);
×
530
                if ($controlLabel) {
×
531
                        $labelHtml->setHtml($controlLabel);
×
532
                }
533

534
                return $labelHtml;
×
535
        }
536

537
        /**
538
         * Renders single visual row.
539
         */
540
        public function renderPair(BaseControl $control): string
541
        {
542
                $pairHtml = $this->configElem(Cnf::PAIR);
10✔
543

544
                $pairHtml->id = $control->getOption(RendererOptions::ID);
10✔
545

546
                $labelHtml = $this->renderLabel($control);
10✔
547
                $pairHtml->addHtml($labelHtml);
10✔
548

549
                $nonLabel = $this->getElem(Cnf::NON_LABEL);
10✔
550

551
                //region non-label parts
552
                $controlHtml = $this->renderControl($control);
10✔
553
                $feedbackHtml = $this->renderFeedback($control);
10✔
554
                $descriptionHtml = $this->renderDescription($control);
10✔
555

556
                if (!empty($controlHtml)) {
10✔
557
                        $nonLabel->addHtml($controlHtml);
10✔
558
                }
559

560
                if (!empty($feedbackHtml)) {
10✔
561
                        $nonLabel->addHtml($feedbackHtml);
1✔
562
                }
563

564
                if (!empty($descriptionHtml)) {
10✔
565
                        $nonLabel->addHtml($descriptionHtml);
×
566
                }
567

568
                //endregion
569

570
                if (!empty($nonLabel)) {
10✔
571
                        $pairHtml->addHtml($nonLabel);
10✔
572
                }
573

574
                return $pairHtml->render(0);
10✔
575
        }
576

577
        /**
578
         * Set how many of Bootstrap rows shall the label and control occupy
579
         */
580
        public function setColumns(int $label, ?int $control = null): void
581
        {
582
                if ($control === null) {
×
583
                        $control = 12 - $label;
×
584
                }
585

586
                $this->labelColumns = $label;
×
587
                $this->controlColumns = $control;
×
588
        }
589

590
        /**
591
         * Sets render mode
592
         *
593
         * @param int $renderMode RenderMode
594
         * @see RenderMode
595
         */
596
        public function setMode(int $renderMode): void
597
        {
598
                $this->renderMode = $renderMode;
47✔
599
        }
600

601
        /**
602
         * Fetch config tailored for current render mode
603
         *
604
         * @param string $key first-level key of $this->config
605
         * @return mixed[]
606
         */
607
        protected function fetchConfig(string $key): array
608
        {
609
                $config = $this->config[$key];
13✔
610

611
                if (isset($this->configOverride[$this->renderMode][$key])) {
13✔
612
                        $override = $this->configOverride[$this->renderMode][$key];
9✔
613
                        $config = array_merge($config, $override);
9✔
614
                }
615

616
                return $config;
13✔
617
        }
618

619
        /**
620
         * Get element based on its first-level key
621
         *
622
         * @param string $additionalKeys config will be overridden in this order
623
         */
624
        protected function getElem(string $key, ...$additionalKeys): ?Html
625
        {
626
                $el = $this->configElem($key, Html::el());
10✔
627

628
                foreach ($additionalKeys as $additionalKey) {
10✔
629
                        $config = $this->fetchConfig($additionalKey);
1✔
630
                        $el = $this->configElem($config, $el);
1✔
631
                }
632

633
                return $el;
10✔
634
        }
635

636
        /**
637
         * Renders control description (help text)
638
         */
639
        protected function renderDescription(BaseControl $control): ?Html
640
        {
641
                $description = $control->getOption(RendererOptions::DESCRIPTION);
10✔
642
                if (is_string($description)) {
10✔
643
                        if ($control instanceof BaseControl) {
×
644
                                $description = $control->translate($description);
×
645
                        }
646
                } elseif (!$description instanceof Html) {
10✔
647
                        $description = '';
10✔
648
                }
649

650
                if (!empty($description)) {
10✔
651
                        $el = $this->getElem(Cnf::DESCRIPTION);
×
652
                        $el->setHtml($description);
×
653

654
                        return $el;
×
655
                }
656

657
                return null;
10✔
658
        }
659

660
        /**
661
         * Renders valid or invalid feedback of form or control
662
         *
663
         * @param BaseControl|null $control null = whole form
664
         */
665
        protected function renderFeedback(?BaseControl $control = null): ?Html
666
        {
667
                $isValid = true;
12✔
668
                $showFeedback = false;
12✔
669
                $messages = [];
12✔
670

671
                if ($control instanceof Control) {
12✔
672
                        // specific control
673

674
                        if ($control->hasErrors()) {
10✔
675
                                // control is invalid, mark it that way
676
                                $isValid = false;
1✔
677
                                $showFeedback = true;
1✔
678
                                $messages = $control->getErrors();
1✔
679
                        } elseif ($this->form->showValidation) {
9✔
680
                                $isValid = true;
×
681
                                // control is valid and we want to explicitly show that it's valid
682
                                $message = $control->getOption(RendererOptions::FEEDBACK_VALID);
×
683
                                if ($message) {
×
684
                                        $messages = [$message];
×
685
                                        $showFeedback = true;
×
686
                                } else {
687
                                        $showFeedback = false;
×
688
                                }
689
                        }
690

691
                        if ($showFeedback && count($messages)) {
10✔
692
                                $el = $isValid
1✔
693
                                        ? $this->getElem(Cnf::FEEDBACK, Cnf::FEEDBACK_VALID)
×
694
                                        : $this->getElem(Cnf::FEEDBACK, Cnf::FEEDBACK_INVALID);
1✔
695

696
                                foreach ($messages as $message) {
1✔
697
                                        if ($message instanceof Html) {
1✔
698
                                                $el->addHtml($message);
×
699
                                        } else {
700
                                                $el->addText($message);
1✔
701
                                        }
702

703
                                        $el->addHtml('<br>');
1✔
704
                                }
705

706
                                return $el;
1✔
707
                        } else {
708
                                return null;
9✔
709
                        }
710
                } elseif ($control === null) {
12✔
711
                        // whole form
712
                        $form = $this->form;
12✔
713

714
                        if ($form->hasErrors()) {
12✔
715
                                $showFeedback = true;
1✔
716
                                $messages = $form->getOwnErrors();
1✔
717
                        } else {
718
                                $showFeedback = false;
11✔
719
                                // this doesn't make sense, form is sent if it's valid
720
                        }
721

722
                        if ($showFeedback && count($messages)) {
12✔
723
                                $el = $this->getElem(Cnf::FORM_OWN_ERRORS);
×
724
                                $msgTemplate = $this->getElem(Cnf::FORM_OWN_ERROR);
×
725

726
                                foreach ($messages as $message) {
×
727
                                        $messageHtml = clone $msgTemplate;
×
728
                                        if ($message instanceof Html) {
×
729
                                                $messageHtml->setHtml($message);
×
730
                                        } else {
731
                                                $messageHtml->setText($message);
×
732
                                        }
733

734
                                        $el->addHtml($messageHtml);
×
735
                                }
736

737
                                return $el;
×
738
                        } else {
739
                                return null;
12✔
740
                        }
741
                } else {
742
                        throw new Nette\NotImplementedException('renderer is unable to render feedback for ' . gettype($control));
×
743
                }
744
        }
745

746
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc