1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace mindplay\kissform; |
4
|
|
|
|
5
|
|
|
use mindplay\kissform\Facets\FieldInterface; |
6
|
|
|
use RuntimeException; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* This class renders and populates input elements for use in forms, |
10
|
|
|
* by consuming property-information provided by {@see Field} objects, |
11
|
|
|
* and populating them with state from an {@see InputModel}. |
12
|
|
|
* |
13
|
|
|
* Conventions for method-names in this class: |
14
|
|
|
* |
15
|
|
|
* * `get_()` and `is_()` methods provide raw information about Fields |
16
|
|
|
* |
17
|
|
|
* * `render_()` methods delegate rendering to {@see Field::renderInput} implementations. |
18
|
|
|
* |
19
|
|
|
* * `_For()` methods (such as `inputFor()`) render low-level HTML tags (with state) for Fields |
20
|
|
|
* |
21
|
|
|
* * Verb methods like `visit`, `merge` and `escape` perform various relevant actions |
22
|
|
|
* |
23
|
|
|
* * Noun methods like `tag`, `attrs` and `label` create low-level HTML tags |
24
|
|
|
* |
25
|
|
|
*/ |
26
|
|
|
class InputRenderer |
27
|
|
|
{ |
28
|
|
|
/** |
29
|
|
|
* @var string HTML encoding charset |
30
|
|
|
*/ |
31
|
|
|
public $encoding = 'UTF-8'; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var bool if true, use long form XHTML for value-less attributes (e.g. disabled="disabled") |
35
|
|
|
* |
36
|
|
|
* @see attrs() |
37
|
|
|
*/ |
38
|
|
|
public $xhtml = false; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var InputModel input model |
42
|
|
|
*/ |
43
|
|
|
public $model; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var string|string[]|null form element collection name(s) |
47
|
|
|
*/ |
48
|
|
|
public $collection_name; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var string|null form element id-attribute prefix (or null, to bypass id-attribute generation) |
52
|
|
|
*/ |
53
|
|
|
public $id_prefix; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var string CSS class name applied to all form controls |
57
|
|
|
* |
58
|
|
|
* @see inputFor() |
59
|
|
|
*/ |
60
|
|
|
public $input_class = 'form-control'; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var string CSS class name added to labels |
64
|
|
|
* |
65
|
|
|
* @see labelFor() |
66
|
|
|
*/ |
67
|
|
|
public $label_class = 'control-label'; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @var string suffix to append to all labels (e.g. ":") |
71
|
|
|
* |
72
|
|
|
* @see labelFor() |
73
|
|
|
*/ |
74
|
|
|
public $label_suffix = ''; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var string CSS class-name added to required fields |
78
|
|
|
* |
79
|
|
|
* @see groupFor() |
80
|
|
|
*/ |
81
|
|
|
public $required_class = 'required'; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @var string CSS class-name added to fields with error state |
85
|
|
|
* |
86
|
|
|
* @see groupFor() |
87
|
|
|
*/ |
88
|
|
|
public $error_class = 'has-error'; |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @var string group tag name (e.g. "div", "fieldset", etc.; defaults to "div") |
92
|
|
|
* |
93
|
|
|
* @see groupFor() |
94
|
|
|
* @see endGroup() |
95
|
|
|
*/ |
96
|
|
|
public $group_tag = 'div'; |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @var array default attributes to be added to opening control-group tags |
100
|
|
|
* |
101
|
|
|
* @see groupFor() |
102
|
|
|
*/ |
103
|
|
|
public $group_attrs = ['class' => 'form-group']; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @var string[] map where Field name => label override |
107
|
|
|
*/ |
108
|
|
|
protected $labels = []; |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @var string[] map where Field name => placeholder override |
112
|
|
|
*/ |
113
|
|
|
protected $placeholders = []; |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @var bool[] map where Field name => required flag |
117
|
|
|
*/ |
118
|
|
|
protected $required = []; |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* @var string[] list of void elements |
122
|
|
|
* |
123
|
|
|
* @see tag() |
124
|
|
|
* |
125
|
|
|
* @link http://www.w3.org/TR/html-markup/syntax.html#void-elements |
126
|
|
|
*/ |
127
|
|
|
private static $void_elements = [ |
128
|
|
|
'area' => true, |
129
|
|
|
'base' => true, |
130
|
|
|
'br' => true, |
131
|
|
|
'col' => true, |
132
|
|
|
'command' => true, |
133
|
|
|
'embed' => true, |
134
|
|
|
'hr' => true, |
135
|
|
|
'img' => true, |
136
|
|
|
'input' => true, |
137
|
|
|
'keygen' => true, |
138
|
|
|
'link' => true, |
139
|
|
|
'meta' => true, |
140
|
|
|
'param' => true, |
141
|
|
|
'source' => true, |
142
|
|
|
'track' => true, |
143
|
|
|
'wbr' => true, |
144
|
|
|
]; |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param InputModel|array|null $model input model, or (possibly nested) input array (e.g. $_GET or $_POST) |
148
|
|
|
* @param string|string[]|null $collection_name collection name(s) for inputs, e.g. 'myform' or ['myform', '123'] etc. |
149
|
|
|
* @param string|null $id_prefix base id for inputs, e.g. 'myform' or 'myform-123', etc. |
150
|
|
|
*/ |
151
|
23 |
|
public function __construct($model = null, $collection_name = null, $id_prefix = null) |
152
|
|
|
{ |
153
|
23 |
|
$this->model = InputModel::create($model); |
154
|
23 |
|
$this->collection_name = $collection_name; |
155
|
23 |
|
$this->id_prefix = $id_prefix === null |
156
|
22 |
|
? ($collection_name === null |
157
|
20 |
|
? null |
158
|
22 |
|
: implode('-', (array) $this->collection_name)) |
159
|
1 |
|
: $id_prefix; |
160
|
23 |
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @param FieldInterface $field |
164
|
|
|
* |
165
|
|
|
* @return string |
166
|
|
|
* |
167
|
|
|
* @see Field::getLabel() |
168
|
|
|
*/ |
169
|
3 |
|
public function getLabel(FieldInterface $field) |
170
|
|
|
{ |
171
|
3 |
|
return array_key_exists($field->getName(), $this->labels) |
172
|
1 |
|
? $this->labels[$field->getName()] |
173
|
3 |
|
: $field->getLabel(); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Override the label defined by the Field |
178
|
|
|
* |
179
|
|
|
* @param FieldInterface $field |
180
|
|
|
* @param string $label |
181
|
|
|
*/ |
182
|
1 |
|
public function setLabel(FieldInterface $field, $label) |
183
|
|
|
{ |
184
|
1 |
|
$this->labels[$field->getName()] = $label; |
185
|
1 |
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @param FieldInterface $field |
189
|
|
|
* |
190
|
|
|
* @return string |
191
|
|
|
* |
192
|
|
|
* @see Field::getPlaceholder() |
193
|
|
|
*/ |
194
|
10 |
|
public function getPlaceholder(FieldInterface $field) |
195
|
|
|
{ |
196
|
10 |
|
return array_key_exists($field->getName(), $this->placeholders) |
197
|
1 |
|
? $this->placeholders[$field->getName()] |
198
|
10 |
|
: $field->getPlaceholder(); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Override the placeholder label defined by the Field |
203
|
|
|
* |
204
|
|
|
* @param FieldInterface $field |
205
|
|
|
* @param string $placeholder |
206
|
|
|
*/ |
207
|
1 |
|
public function setPlaceholder(FieldInterface $field, $placeholder) |
208
|
|
|
{ |
209
|
1 |
|
$this->placeholders[$field->getName()] = $placeholder; |
210
|
1 |
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* @param FieldInterface $field |
214
|
|
|
* |
215
|
|
|
* @return string|null computed name-attribute |
216
|
|
|
*/ |
217
|
16 |
|
public function getName(FieldInterface $field) |
218
|
|
|
{ |
219
|
16 |
|
$names = (array) $this->collection_name; |
220
|
16 |
|
$names[] = $field->getName(); |
221
|
|
|
|
222
|
16 |
|
return $names[0] . (count($names) > 1 ? '[' . implode('][', array_slice($names, 1)) . ']' : ''); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* @param FieldInterface $field |
227
|
|
|
* |
228
|
|
|
* @return string|null computed id-attribute |
229
|
|
|
*/ |
230
|
13 |
|
public function getId(FieldInterface $field) |
231
|
|
|
{ |
232
|
13 |
|
return $this->id_prefix |
233
|
3 |
|
? $this->id_prefix . '-' . $field->getName() |
234
|
13 |
|
: null; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Conditionally create (or add) CSS class-names for Field status, e.g. |
239
|
|
|
* {@see $required_class} for {@see Field::$required} and {@see $error_class} |
240
|
|
|
* if the {@see $model} contains an error. |
241
|
|
|
* |
242
|
|
|
* @param FieldInterface $field |
243
|
|
|
* |
244
|
|
|
* @return array map of HTML attributes (with additonial classes) |
245
|
|
|
* |
246
|
|
|
* @see $required_class |
247
|
|
|
* @see $error_class |
248
|
|
|
*/ |
249
|
4 |
|
public function getAttrs(FieldInterface $field) |
250
|
|
|
{ |
251
|
4 |
|
$classes = []; |
252
|
|
|
|
253
|
4 |
|
if ($this->required_class !== null && $this->isRequired($field)) { |
254
|
3 |
|
$classes[] = $this->required_class; |
255
|
|
|
} |
256
|
|
|
|
257
|
4 |
|
if ($this->error_class !== null && $this->model->hasError($field)) { |
258
|
2 |
|
$classes[] = $this->error_class; |
259
|
|
|
} |
260
|
|
|
|
261
|
4 |
|
return ['class' => $classes]; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @param FieldInterface $field |
266
|
|
|
* |
267
|
|
|
* @return bool true, if the given Field is required |
268
|
|
|
*/ |
269
|
4 |
|
public function isRequired(FieldInterface $field) |
270
|
|
|
{ |
271
|
4 |
|
return array_key_exists($field->getName(), $this->required) |
272
|
1 |
|
? $this->required[$field->getName()] |
273
|
4 |
|
: $field->isRequired(); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Override the required flag defined by the Field |
278
|
|
|
* |
279
|
|
|
* @param FieldInterface $field |
280
|
|
|
* @param bool $required |
281
|
|
|
*/ |
282
|
1 |
|
public function setRequired(FieldInterface $field, $required = true) |
283
|
|
|
{ |
284
|
1 |
|
$this->required[$field->getName()] = (bool) $required; |
285
|
1 |
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Build an HTML input for a given Field. |
289
|
|
|
* |
290
|
|
|
* @param FieldInterface $field |
291
|
|
|
* @param array $attr |
292
|
|
|
* |
293
|
|
|
* @return string |
294
|
|
|
* |
295
|
|
|
* @throws RuntimeException if the given Field cannot be rendered |
296
|
|
|
*/ |
297
|
16 |
|
public function render(FieldInterface $field, array $attr = []) |
298
|
|
|
{ |
299
|
16 |
|
return $field->renderInput($this, $this->model, $attr); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Builds an HTML group containing a label and rendered input for a given Field. |
304
|
|
|
* |
305
|
|
|
* @param FieldInterface $field |
306
|
|
|
* @param string|null $label label text (optional) |
307
|
|
|
* @param array $input_attr map of HTML attributes for the input (optional) |
308
|
|
|
* @param array $group_attr map of HTML attributes for the group (optional) |
309
|
|
|
* |
310
|
|
|
* @return string |
311
|
|
|
*/ |
312
|
1 |
|
public function renderGroup(FieldInterface $field, $label = null, array $input_attr = [], $group_attr = []) |
313
|
|
|
{ |
314
|
|
|
return |
315
|
1 |
|
$this->groupFor($field, $group_attr) |
316
|
1 |
|
. $this->labelFor($field, $label) |
317
|
1 |
|
. $this->render($field, $input_attr) |
318
|
1 |
|
. $this->endGroup(); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Builds an HTML div with state-classes, containing a rendered input for a given Field. |
323
|
|
|
* |
324
|
|
|
* @param FieldInterface $field |
325
|
|
|
* @param array $input_attr attributes for the generated input |
326
|
|
|
* @param array $div_attr attributes for the wrapper div |
327
|
|
|
* |
328
|
|
|
* @return string HTML |
329
|
|
|
*/ |
330
|
2 |
|
public function renderDiv(FieldInterface $field, array $input_attr = [], $div_attr = []) |
331
|
|
|
{ |
332
|
2 |
|
return $this->divFor($field, $this->render($field, $input_attr), $div_attr); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Visit a given Field - temporarily swaps out {@see $model}, {@see $name_prefix} |
337
|
|
|
* and {@see $id_prefix} and merges any changes made to the model while calling |
338
|
|
|
* the given function. |
339
|
|
|
* |
340
|
|
|
* @param FieldInterface|int|string $field Field instance, or an integer index, or string key |
341
|
|
|
* @param callable $func function (InputModel $model): mixed |
342
|
|
|
* |
343
|
|
|
* @return mixed |
344
|
|
|
*/ |
345
|
2 |
|
public function visit($field, $func) |
346
|
|
|
{ |
347
|
2 |
|
$model = $this->model; |
348
|
2 |
|
$name_prefix = $this->collection_name; |
349
|
2 |
|
$id_prefix = $this->id_prefix; |
350
|
|
|
|
351
|
2 |
|
$key = $field instanceof FieldInterface |
352
|
2 |
|
? $field->getName() |
353
|
2 |
|
: (string) $field; |
354
|
|
|
|
355
|
2 |
|
$this->model = InputModel::create(@$model->input[$key], $model->getError($key)); |
|
|
|
|
356
|
2 |
|
$this->collection_name = array_merge((array) $this->collection_name, [$key]); |
|
|
|
|
357
|
2 |
|
$this->id_prefix = $this->id_prefix |
358
|
1 |
|
? $this->id_prefix . '-' . $key |
359
|
1 |
|
: null; |
360
|
|
|
|
361
|
2 |
|
call_user_func($func, $this->model); |
362
|
|
|
|
363
|
2 |
|
if ($this->model->input !== []) { |
364
|
2 |
|
$model->input[$key] = $this->model->input; |
365
|
|
|
} else { |
366
|
2 |
|
unset($model->input[$key]); |
367
|
|
|
} |
368
|
|
|
|
369
|
2 |
|
if ($this->model->hasErrors()) { |
370
|
1 |
|
$model->setError($key, $this->model->getErrors()); |
371
|
|
|
} |
372
|
|
|
|
373
|
2 |
|
$this->model = $model; |
374
|
2 |
|
$this->collection_name = $name_prefix; |
375
|
2 |
|
$this->id_prefix = $id_prefix; |
376
|
2 |
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Merge any number of attribute maps, with the latter overwriting the first, and |
380
|
|
|
* with special handling for the class-attribute. |
381
|
|
|
* |
382
|
|
|
* @param array ...$attr |
383
|
|
|
* |
384
|
|
|
* @return array |
385
|
|
|
*/ |
386
|
17 |
|
public function mergeAttrs() |
387
|
|
|
{ |
388
|
17 |
|
$maps = func_get_args(); |
389
|
|
|
|
390
|
17 |
|
$result = []; |
391
|
|
|
|
392
|
17 |
|
foreach ($maps as $map) { |
393
|
17 |
|
if (isset($map['class'])) { |
394
|
16 |
|
if (isset($result['class'])) { |
395
|
7 |
|
$map['class'] = array_merge((array) $result['class'], (array) $map['class']); |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
|
399
|
17 |
|
$result = array_merge($result, $map); |
400
|
|
|
} |
401
|
|
|
|
402
|
17 |
|
return $result; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* Encode plain text as HTML |
407
|
|
|
* |
408
|
|
|
* @param string $text plain text |
409
|
|
|
* @param int $flags encoding flags (optional, see htmlspecialchars) |
410
|
|
|
* |
411
|
|
|
* @return string escaped HTML |
412
|
|
|
* |
413
|
|
|
* @see softEscape() |
414
|
|
|
*/ |
415
|
21 |
|
public function escape($text, $flags = ENT_COMPAT) |
416
|
|
|
{ |
417
|
21 |
|
return htmlspecialchars($text, $flags, $this->encoding, true); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Encode plain text as HTML, while attempting to avoid double-encoding |
422
|
|
|
* |
423
|
|
|
* @param string $text plain text |
424
|
|
|
* @param int $flags encoding flags (optional, see htmlspecialchars) |
425
|
|
|
* |
426
|
|
|
* @return string escaped HTML |
427
|
|
|
* |
428
|
|
|
* @see escape() |
429
|
|
|
*/ |
430
|
4 |
|
public function softEscape($text, $flags = ENT_COMPAT) |
431
|
|
|
{ |
432
|
4 |
|
return htmlspecialchars($text, $flags, $this->encoding, false); |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* Build an opening and closing HTML tag (or a self-closing tag) - examples: |
437
|
|
|
* |
438
|
|
|
* echo $renderer->tag('input', array('type' => 'text')); => <input type="text"/> |
439
|
|
|
* |
440
|
|
|
* echo $renderer->tag('div', array(), 'Foo & Bar'); => <div>Foo & Bar</div> |
441
|
|
|
* |
442
|
|
|
* echo $renderer->tag('script', array(), ''); => <script></script> |
443
|
|
|
* |
444
|
|
|
* @param string $name HTML tag name |
445
|
|
|
* @param array $attr map of HTML attributes |
446
|
|
|
* @param string|null $html inner HTML, or NULL to build a self-closing tag |
447
|
|
|
* |
448
|
|
|
* @return string |
449
|
|
|
* |
450
|
|
|
* @see openTag() |
451
|
|
|
*/ |
452
|
20 |
|
public function tag($name, array $attr = [], $html = null) |
453
|
|
|
{ |
454
|
20 |
|
return $html === null && isset(self::$void_elements[$name]) |
455
|
15 |
|
? '<' . $name . $this->attrs($attr) . '/>' |
456
|
20 |
|
: '<' . $name . $this->attrs($attr) . '>' . $html . '</' . $name . '>'; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Build an open HTML tag; remember to close the tag. |
461
|
|
|
* |
462
|
|
|
* Note that there is no closeTag() equivalent, as this wouldn't help with anything |
463
|
|
|
* and would actually require more code than e.g. a simple literal `</div>` |
464
|
|
|
* |
465
|
|
|
* @param string $name HTML tag name |
466
|
|
|
* @param array $attr map of HTML attributes |
467
|
|
|
* |
468
|
|
|
* @return string |
469
|
|
|
* |
470
|
|
|
* @see tag() |
471
|
|
|
*/ |
472
|
3 |
|
public function openTag($name, array $attr = []) |
473
|
|
|
{ |
474
|
3 |
|
return '<' . $name . $this->attrs($attr) . '>'; |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
/** |
478
|
|
|
* Build HTML attributes for use inside an HTML (or XML) tag. |
479
|
|
|
* |
480
|
|
|
* Includes a leading space, since this is usually used inside a tag, e.g.: |
481
|
|
|
* |
482
|
|
|
* <div<?= $form->attrs(array('class' => 'foo')) ?>>...</div> |
483
|
|
|
* |
484
|
|
|
* Accepts strings, or arrays of strings, as attribute-values - arrays will |
485
|
|
|
* be folded using space as a separator, e.g. useful for the class-attribute. |
486
|
|
|
* |
487
|
|
|
* Attributes containing NULL, FALSE or an empty array() are ignored. |
488
|
|
|
* |
489
|
|
|
* Attributes containing TRUE are rendered as value-less attributes. |
490
|
|
|
* |
491
|
|
|
* @param array $attr map where attribute-name => attribute value(s) |
492
|
|
|
* @param bool $sort true, to sort attributes by name; otherwise false (sorting is enabled by default) |
493
|
|
|
* |
494
|
|
|
* @return string |
495
|
|
|
*/ |
496
|
21 |
|
public function attrs(array $attr, $sort = true) |
497
|
|
|
{ |
498
|
21 |
|
if ($sort) { |
499
|
21 |
|
ksort($attr); |
500
|
|
|
} |
501
|
|
|
|
502
|
21 |
|
$html = ''; |
503
|
|
|
|
504
|
21 |
|
foreach ($attr as $name => $value) { |
505
|
21 |
|
if (is_array($value)) { |
506
|
8 |
|
$value = count($value) |
507
|
7 |
|
? implode(' ', $value) // fold multi-value attribute (e.g. class-names) |
508
|
8 |
|
: null; // filter empty array |
509
|
|
|
} |
510
|
|
|
|
511
|
21 |
|
if ($value === null || $value === false) { |
512
|
15 |
|
continue; // skip NULL and FALSE attributes |
513
|
|
|
} |
514
|
|
|
|
515
|
21 |
|
if ($value === true) { |
516
|
6 |
|
$html .= $this->xhtml ? |
517
|
1 |
|
' ' . $name . '="' . $name . '"' // e.g. disabled="disabled" (as required for XHTML) |
518
|
6 |
|
: ' ' . $name; // value-less HTML attribute |
519
|
|
|
} else { |
520
|
21 |
|
$html .= ' ' . $name . '="' . $this->escape($value) . '"'; |
521
|
|
|
} |
522
|
|
|
} |
523
|
|
|
|
524
|
21 |
|
return $html; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
/** |
528
|
|
|
* Builds an HTML <input> tag |
529
|
|
|
* |
530
|
|
|
* @param string $type |
531
|
|
|
* @param string $name |
|
|
|
|
532
|
|
|
* @param string $value |
|
|
|
|
533
|
|
|
* @param array $attr map of HTML attributes |
534
|
|
|
* |
535
|
|
|
* @return string |
536
|
|
|
* |
537
|
|
|
* @see inputFor() |
538
|
|
|
*/ |
539
|
1 |
|
public function input($type, $name = null, $value= null, $attr = []) |
540
|
|
|
{ |
541
|
1 |
|
return $this->tag( |
542
|
1 |
|
'input', |
543
|
1 |
|
$this->mergeAttrs( |
544
|
|
|
[ |
545
|
1 |
|
'type' => $type, |
546
|
1 |
|
'name' => $name, |
547
|
1 |
|
'value' => $value, |
548
|
|
|
], |
549
|
|
|
$attr |
550
|
|
|
) |
551
|
|
|
); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
/** |
555
|
|
|
* Build an HTML input-tag for a given Field |
556
|
|
|
* |
557
|
|
|
* @param FieldInterface $field |
558
|
|
|
* @param string $type HTML input type-attribute (e.g. "text", "password", etc.) |
559
|
|
|
* @param array $attr map of HTML attributes |
560
|
|
|
* |
561
|
|
|
* @return string |
562
|
|
|
*/ |
563
|
9 |
|
public function inputFor(FieldInterface $field, $type, array $attr = []) |
564
|
|
|
{ |
565
|
9 |
|
return $this->tag( |
566
|
9 |
|
'input', |
567
|
9 |
|
$this->mergeAttrs( |
568
|
|
|
[ |
569
|
9 |
|
'name' => $this->getName($field), |
570
|
9 |
|
'id' => $this->getId($field), |
571
|
9 |
|
'class' => $this->input_class, |
572
|
9 |
|
'value' => $this->model->getInput($field), |
573
|
9 |
|
'type' => $type, |
574
|
9 |
|
'placeholder' => @$attr['placeholder'] ?: $this->getPlaceholder($field), |
575
|
|
|
], |
576
|
|
|
$attr |
577
|
|
|
) |
578
|
|
|
); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
/** |
582
|
|
|
* Build an HTML opening tag for an input group |
583
|
|
|
* |
584
|
|
|
* Call {@see endGroup()} to create the matching closing tag. |
585
|
|
|
* |
586
|
|
|
* @param array $attr optional map of HTML attributes |
587
|
|
|
* |
588
|
|
|
* @return string |
589
|
|
|
* |
590
|
|
|
* @see groupFor() |
591
|
|
|
*/ |
592
|
1 |
|
public function group($attr = []) |
593
|
|
|
{ |
594
|
1 |
|
return $this->openTag( |
595
|
1 |
|
$this->group_tag, |
596
|
1 |
|
$this->mergeAttrs($this->group_attrs, $attr) |
597
|
|
|
); |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
/** |
601
|
|
|
* Build an HTML opening tag for an input group, with CSS classes added for |
602
|
|
|
* {@see Field::$required} and error state, as needed. |
603
|
|
|
* |
604
|
|
|
* Call {@see endGroup()} to create the matching closing tag. |
605
|
|
|
* |
606
|
|
|
* @param FieldInterface $field |
607
|
|
|
* @param array $attr map of HTML attributes (optional) |
608
|
|
|
* |
609
|
|
|
* @return string |
610
|
|
|
* |
611
|
|
|
* @see $group_tag |
612
|
|
|
* @see $group_attrs |
613
|
|
|
* @see $required_class |
614
|
|
|
* @see $error_class |
615
|
|
|
* @see endGroup() |
616
|
|
|
*/ |
617
|
2 |
|
public function groupFor(FieldInterface $field, array $attr = []) |
618
|
|
|
{ |
619
|
2 |
|
return $this->openTag( |
620
|
2 |
|
$this->group_tag, |
621
|
2 |
|
$this->mergeAttrs($this->group_attrs, $this->getAttrs($field), $attr) |
622
|
|
|
); |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Returns the matching closing tag for a {@see group()} or {@see groupFor()} tag. |
627
|
|
|
* |
628
|
|
|
* @return string |
629
|
|
|
* |
630
|
|
|
* @see groupFor() |
631
|
|
|
* @see $group_tag |
632
|
|
|
*/ |
633
|
2 |
|
public function endGroup() |
634
|
|
|
{ |
635
|
2 |
|
return "</{$this->group_tag}>"; |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
/** |
639
|
|
|
* Builds an HTML div with state-classes, containing the given HTML. |
640
|
|
|
* |
641
|
|
|
* @param FieldInterface $field |
642
|
|
|
* @param string $html inner HTML for the generated div |
643
|
|
|
* @param array $attr additional attributes for the div |
644
|
|
|
* |
645
|
|
|
* @return string HTML |
646
|
|
|
*/ |
647
|
2 |
|
public function divFor(FieldInterface $field, $html, array $attr = []) |
648
|
|
|
{ |
649
|
2 |
|
return $this->tag('div', $this->mergeAttrs($this->getAttrs($field), $attr), $html); |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* Build a `<label for="id" />` tag |
654
|
|
|
* |
655
|
|
|
* @param string $for target element ID |
656
|
|
|
* @param string $label label text |
657
|
|
|
* @param array $attr map of HTML attributes |
658
|
|
|
* |
659
|
|
|
* @return string |
660
|
|
|
* |
661
|
|
|
* @see labelFor() |
662
|
|
|
*/ |
663
|
3 |
|
public function label($for, $label, $attr = []) |
664
|
|
|
{ |
665
|
3 |
|
return $this->tag( |
666
|
3 |
|
'label', |
667
|
3 |
|
$this->mergeAttrs( |
668
|
|
|
[ |
669
|
3 |
|
'for' => $for, |
670
|
3 |
|
'class' => $this->label_class |
671
|
|
|
], |
672
|
|
|
$attr |
673
|
|
|
), |
674
|
3 |
|
$this->softEscape($label . $this->label_suffix) |
675
|
|
|
); |
676
|
|
|
} |
677
|
|
|
|
678
|
|
|
/** |
679
|
|
|
* Build an HTML `<label for="id" />` tag |
680
|
|
|
* |
681
|
|
|
* @param FieldInterface $field |
682
|
|
|
* @param string|null $label label text (optional) |
683
|
|
|
* @param array $attr map of HTML attributes |
684
|
|
|
* |
685
|
|
|
* @return string |
686
|
|
|
* |
687
|
|
|
* @see Field::getLabel() |
688
|
|
|
* |
689
|
|
|
* @throws RuntimeException if a label cannot be produced |
690
|
|
|
*/ |
691
|
2 |
|
public function labelFor(FieldInterface $field, $label = null, array $attr = []) |
|
|
|
|
692
|
|
|
{ |
693
|
2 |
|
$id = $this->getId($field); |
694
|
|
|
|
695
|
2 |
|
if ($id === null) { |
696
|
1 |
|
throw new RuntimeException("cannot produce a label when FormHelper::\$id_prefix is NULL"); |
697
|
|
|
} |
698
|
|
|
|
699
|
2 |
|
if ($label === null) { |
700
|
2 |
|
$label = $this->getLabel($field); |
701
|
|
|
|
702
|
2 |
|
if ($label === null) { |
703
|
1 |
|
throw new RuntimeException("the given Field has no defined label"); |
704
|
|
|
} |
705
|
|
|
} |
706
|
|
|
|
707
|
2 |
|
return $this->label($id, $label); |
708
|
|
|
} |
709
|
|
|
} |
710
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.