FormControlGroup.php 13.6 KB
Newer Older
1
2
3
4
<?php

namespace UnicaenApp\Form\View\Helper;

5
use UnicaenApp\Exception\LogicException;
6
7
8
use UnicaenApp\Form\Element\Date;
use UnicaenApp\Form\Element\DateInfSup;
use UnicaenApp\Form\Element\SearchAndSelect;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
9
10
11
12
13
14
15
16
17
use Laminas\Form\Element\Button;
use Laminas\Form\Element\Checkbox;
use Laminas\Form\Element\DateTime;
use Laminas\Form\Element\MultiCheckbox;
use Laminas\Form\Element\Radio;
use Laminas\Form\Element\Select;
use Laminas\Form\ElementInterface;
use Laminas\Form\View\Helper\AbstractHelper;
use Laminas\Form\View\Helper\FormElementErrors;
18
19

/**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
20
 * Aide de vue générant un élément de fomulaire à la mode Bootsrap 5.
21
22
23
24
25
 *
 * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
 */
class FormControlGroup extends AbstractHelper
{
26
27
28
    /**
     * @var bool
     */
29
    protected $includeLabel = true;
30
31
32
33

    /**
     * @var bool
     */
34
    protected $includeErrors = true;
35
36
37
38

    /**
     * @var bool
     */
39
40
    protected $addClearButton = false;

41
42
43
44
45
46
47
48
49
50
    /**
     * @var string|null
     */
    protected $prefix;

    /**
     * @var string|null
     */
    protected $suffix;

51
52
53
    /**
     * @var string
     */
54
    protected $helpContent = ['before' => '', 'after' => ''];
55

56
57
58
59
60
61
    /**
     * @var string[]
     */
    protected $helpers = [
        Date::class           => 'formDate',
        DateInfSup::class     => 'formDateInfSup',
62
    //    DateTime::class       => 'formDateTime',
63
64
    ];

65
66
    /**
     * Appel de l'objet comme une fonction.
67
     *
Bertrand Gauthier's avatar
Bertrand Gauthier committed
68
     * @param \Laminas\Form\ElementInterface|null $element Élément de formulaire
Bertrand Gauthier's avatar
Bertrand Gauthier committed
69
     * @param string|null $pluginClass Plugin
70
     *
71
     * @return string|FormControlGroup
72
     */
73
    public function __invoke(ElementInterface $element = null, $pluginClass = 'formElement')
74
    {
75
76
77
        if (null === $element) {
            return $this;
        }
78

79
80
        $this->helpContent = ['before' => '', 'after' => ''];

81
        return $this->render($element, $pluginClass);
82
    }
83

84
85
    /**
     * Génère le code HTML.
86
     *
87
     * @param ElementInterface $element
88
     * @param string|null      $pluginClass
89
     *
90
91
     * @return string
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
92
    public function render(ElementInterface $element, $pluginClass = 'formElement'): string
93
    {
94
        $this->normalizeElement($element);
95
        $this->customFromOptions($element);
96

97
98
99
100
        /* Bypass pour des éléments spécifiques */
        $class = get_class($element);
        if (array_key_exists($class, $this->helpers)) {
            $helper = $this->getView()->plugin($this->helpers[$class]);
101

102
103
104
            return $helper($element);
        }

105
        $inputGroup = [];
106
        if ($this->getPrefix()) {
107
            $inputGroup[] = $this->prefixHtml($element);
108
        }
109
        $inputGroup[] = $this->inputHtml($element, $pluginClass);
110
        if ($this->getSuffix()) {
111
112
            $inputGroup[] = $this->suffixHtml($element);
        }
113
        if ($this->addClearButton) {
114
            $inputGroup[] = $this->clearButtonHtml($element);
115
116
        }

117

Bertrand Gauthier's avatar
Bertrand Gauthier committed
118
        $class = ['mb-2'];
119
        if ($this->elementHasErrors($element)) $class[] = 'has-error';
120

121
        if (count($inputGroup) == 1) {
122
            $input = $inputGroup[0];
123
        } else {
124
            $class[] = 'input-group';
125
            $input   = sprintf('<div class="input-group">%s</div>', implode('', $inputGroup));
126
        }
127

128
129
130
131
132
133
134
        $divContent =
            $this->labelHtml($element)
            . $this->helpContentBeforeHtml($element)
            . $input
            . $this->clearButtonHtml($element)
            . $this->helpContentAfterHtml($element)
            . $this->errorsHtml($element);
135

136
        return sprintf('<div class="%s">%s</div>', implode(' ', $class), $divContent);
137
138
139
140
    }

    private function normalizeElement(ElementInterface $element)
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
        // FIX pour "bootstrap-select-1.14.0-beta2" qui ne supporte pas les 'optgroup' avec Bootstrap 5 :
        // on transforme en select sans optgroup (le nom du groupe est répété en tête de chaque option).
        if ($element instanceof Select) {
            $valueOptions = $element->getValueOptions();
            $newValueOptions = [];
            foreach ($valueOptions as $key => $value) {
                if (is_array($value) && array_key_exists('options', $value)) { // détection d'un optgroup !
                    $options = $value['options'];
                    $groupName = $value['label'];
                    foreach ($options as $k => $v) {
                        if (is_array($v)) {
                            $k = $v['value'];
                            $v = $v['label'];
                        }
                        $newValueOptions[$k] = $groupName . ' > ' . $v; // nom du groupe en tête de l'option
                    }
                } else {
                    $newValueOptions[$key] = $value;
                }
            }
            $element->setValueOptions($newValueOptions);
        }

        if (!$element instanceof Button && !is_a($element, Checkbox::class)) {
165
            $class      = $element->getAttribute('class');
166
167
            $element->setAttribute('class', $class . ' form-control');

168
            $labelClass = array_key_exists('class', $tmp = (array)$element->getLabelAttributes()) ? $tmp['class'] : '';
Bertrand Gauthier's avatar
Bertrand Gauthier committed
169
            $labelAttributes = array_merge($element->getLabelAttributes(), ['class' => $labelClass . ' form-label']);
170
            $element->setLabelAttributes($labelAttributes);
171
        }
172

Bertrand Gauthier's avatar
Bertrand Gauthier committed
173
174
175
176
177
        if (is_a($element, Checkbox::class)) {
            $element->setAttribute('class', $element->getAttribute('class') . ' form-check-input');
            if (!$element->getAttribute('id')) {
                $element->setAttribute('id', uniqid('checkbox_'));
            }
178

Bertrand Gauthier's avatar
Bertrand Gauthier committed
179
180
181
182
183
            $labelClass = array_key_exists('class', $tmp = (array)$element->getLabelAttributes()) ? $tmp['class'] : '';
            $labelAttributes = array_merge($element->getLabelAttributes(), ['class' => $labelClass . ' form-check-label']);
            $element->setLabelAttributes($labelAttributes);
        }
    }
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208

    /**
     * On récupère des options de l'élément directement la customisation de l'affichage
     *
     * @param ElementInterface $element
     *
     * @return $this
     */
    private function customFromOptions(ElementInterface $element)
    {
        if ($prefix = $element->getOption('prefix')) {
            $this->setPrefix($prefix);
        }

        if ($suffix = $element->getOption('suffix')) {
            $this->setSuffix($suffix);
        }

        if ($addClearBtn = $element->getOption('add-clear-btn')) {
            $this->setAddClearButton($addClearBtn);
        }

        return $this;
    }

209
210
211
    private function labelHtml(ElementInterface $element)
    {
        $html = '';
212
213

        if ($this->includeLabel && $element->getLabel() && !$element instanceof Button) {
214
215
216
            if ($element instanceof Checkbox && !$element instanceof MultiCheckbox) {
                $html = null;
            } else {
217
                $helper = $this->getView()->plugin('formLabel');
218
                $html   = $helper($element);
219
            }
220
        }
221
222
223

        if ($element->hasAttribute('info_icon')) {
            $info = $element->getAttribute('info_icon');
224
            $html .= sprintf(
Bertrand Gauthier's avatar
Bertrand Gauthier committed
225
                '&nbsp;<span data-bs-toggle="tooltip" data-bs-placement="bottom" class="icon iconly icon-information" title="%s"></span>',
226
                $info);
227
        }
228

229
230
        return $html;
    }
231

232
233
    private function prefixHtml(ElementInterface $element)
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
234
        return sprintf('<span class="input-text">%s</span>', $this->getPrefix());
235
236
237
238
    }

    private function suffixHtml(ElementInterface $element)
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
239
        return sprintf('<span class="input-text">%s</span>', $this->getSuffix());
240
241
242
243
244
245
    }

    private function helpContentBeforeHtml(ElementInterface $element)
    {
        $helpContentBefore = null;
        if (!empty($this->helpContent['before'])) {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
246
            $helpContentBefore = sprintf('<p class="form-text">%s<p>', $this->helpContent['before']);
247
248
249
250
251
252
253
254
255
        }

        return $helpContentBefore;
    }

    private function helpContentAfterHtml(ElementInterface $element)
    {
        $helpContentAfter = null;
        if (!empty($this->helpContent['after'])) {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
256
            $helpContentAfter = sprintf('<p class="form-text">%s<p>', $this->helpContent['after']);
257
258
259
260
261
        }

        return $helpContentAfter;
    }

262
263
    private function inputHtml(ElementInterface $element, $pluginClass = 'formElement')
    {
264
        if (!$pluginClass) {
265
266
            $pluginClass = 'formElement';
        }
267

268
269
270
271
272
        if ($element instanceof SearchAndSelect) {
            /** @var FormSearchAndSelect $helper */
            $helper = $this->getView()->plugin('formSearchAndSelect');
            $helper->setAutocompleteMinLength(2);
            $html = $helper($element);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
273
274
        } elseif($element instanceof \Laminas\Form\Element\Date) {
            $helper = $this->getView()->plugin(\Laminas\Form\View\Helper\FormDate::class);
275
            $html = $helper($element);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
276
277
        } elseif($element instanceof \Laminas\Form\Element\Time) {
            $helper = $this->getView()->plugin(\Laminas\Form\View\Helper\FormTime::class);
278
            $html = $helper($element);
279
280
281
        } elseif($element instanceof DateTime) {
            $helper = $this->getView()->plugin('formDateTime');
            $html = $helper($element);
282
        } else {
283
284
            if (is_string($pluginClass)) {
                $helper = $this->getView()->plugin($pluginClass);
285
                $html   = $helper($element);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
286
            } elseif ($pluginClass instanceof \Laminas\Form\View\Helper\AbstractHelper) {
287
288
289
290
                $html = $pluginClass($element);
            } else {
                throw new LogicException('Argument $pluginClass incorrect');
            }
291
        }
292
293
294

        if ($element instanceof MultiCheckbox) {
            $html = '<div class="multicheckbox">' . $html . '</div>';
295
        } elseif ($element instanceof Checkbox) {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
296
297
            $id = $element->getAttribute('id');
            $label = $element->getLabel();
298
            $title = $element->getLabelAttributes()['title'] ?? $element->getAttributes()['title'] ?? null;
299
            $class = $element->getLabelAttributes()['class'] ?? $element->getAttributes()['class'] ?? null;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
300
301
302
            $html = <<<EOS
<div class="form-check">
    $html
303
    <label class="form-check-label $class" for="$id" title="$title">$label</label>
Bertrand Gauthier's avatar
Bertrand Gauthier committed
304
305
</div>
EOS;
306
307
        }

308
309
310
        return $html;
    }

311
312
313
314
315
316
317
    private function clearButtonHtml(ElementInterface $element)
    {
        if ($element instanceof Checkbox) { // pas de bouton de nettoyage pour une case à cocher...
            return null;
        }

        if ($this->addClearButton) {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
318
            return '<span class="input-group-btn"><button class="btn btn-secondary btn-sm" type="button" title="Vider" onclick="$(this).siblings(\':input\').val(null).focus();"><span class="icon iconly icon-supprimer"></span></button></span>';
319
320
321
322
323
324
325
326
327
328
        } else {
            return null;
        }
    }

    private function elementHasErrors(ElementInterface $element)
    {
        return $element->getMessages() && $this->getIncludeErrors();
    }

329
330
331
332
    private function errorsHtml(ElementInterface $element)
    {
        $html = '';

333
        if ($this->elementHasErrors($element)) {
334
            /** @var FormElementErrors $helper */
335
            $helper = $this->getView()->plugin('formElementErrors');
336
            $html   = $helper($element, ['class' => 'error text-danger']);
337
        }
338

339
340
        return $html;
    }
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

    /**
     * @return bool
     */
    public function getIncludeLabel()
    {
        return $this->includeLabel;
    }

    /**
     * @return bool
     */
    public function getAddClearButton()
    {
        return $this->addClearButton;
    }

    /**
359
     * Spécifie si le label doit être inclu ou non dans le rendu.
360
     *
361
     * @param bool $includeLabel
362
     *
363
364
365
366
367
     * @return self
     */
    public function setIncludeLabel($includeLabel)
    {
        $this->includeLabel = $includeLabel;
368

369
370
371
        return $this;
    }

372
373
374
375
376
377
378
379
380
381
382
383
    /**
     * @return boolean
     */
    public function getIncludeErrors()
    {
        return $this->includeErrors;
    }

    /**
     * Spécifie si les erreurs de validation éventuelles doivent être inclues ou non dans le rendu.
     *
     * @param bool $includeErrors
384
     *
385
386
387
388
389
390
391
392
393
     * @return self
     */
    public function setIncludeErrors($includeErrors = true)
    {
        $this->includeErrors = $includeErrors;

        return $this;
    }

394
    /**
395
     * Active ou non la présence d'un bouton permettant d'effacer le contenu de l'élément de formulaire.
396
     *
397
     * @param bool $addClearButton
398
     *
399
400
401
402
403
     * @return self
     */
    public function setAddClearButton($addClearButton)
    {
        $this->addClearButton = $addClearButton;
404
405
406
407
408

        return $this;
    }

    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
409
     * Spécifie le texte d'aide ou de description affiché dans un <code><p class="form-text"></code>.
410
     *
411
412
     * @param string $helpContent Texte d'aide
     * @param string $placement   'before' ou 'after'
413
     *
414
     * @return FormControlGroup
415
     */
416
    public function setHelpContent($helpContent, $placement = 'after')
417
    {
418
        $this->helpContent[$placement] = $helpContent;
419

420
421
        return $this;
    }
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461

    /**
     * @return string|null
     */
    public function getPrefix()
    {
        return $this->prefix;
    }

    /**
     * @param string|null $prefix
     *
     * @return FormControlGroup
     */
    public function setPrefix($prefix = null)
    {
        $this->prefix = $prefix;

        return $this;
    }

    /**
     * @return string|null
     */
    public function getSuffix()
    {
        return $this->suffix;
    }

    /**
     * @param string|null $suffix
     *
     * @return FormControlGroup
     */
    public function setSuffix($suffix = null)
    {
        $this->suffix = $suffix;

        return $this;
    }
462
}