-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInputfieldObjectDimensions.module
More file actions
310 lines (277 loc) · 11.4 KB
/
Copy pathInputfieldObjectDimensions.module
File metadata and controls
310 lines (277 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
<?php
/**
* Inputfield ObjectDimensions
*
* Inputfield to enter the length, width and height (2 or 3 dimensions) of an object.
*
* Created by Jürgen K.
* https://github.com/juergenweb
* File name: InputfieldObjectDimensions.module
* Created: 02.02.2022
*/
declare(strict_types=1);
namespace ProcessWire;
/**
* Inputfield to enter the dimensions of an object
*
* Renders one number input per dimension. The type (2d/3d), the size unit and the number of decimals are set in the
* configuration of the fieldtype and passed to this inputfield by ProcessWire.
*
* @property string $input_type "2d" or "3d"
* @property string $input_sizeunit the size unit (e.g. cm)
* @property int $input_decimals the number of decimals
* @property bool|int $input_showhint show a hint with the number of allowed decimals below the inputs
*/
class InputfieldObjectDimensions extends Inputfield
{
/**
* Get the module information for ProcessWire
*
* @return array
*/
public static function getModuleInfo(): array
{
return [
'title' => 'Inputfield Object Dimensions',
'summary' => 'Inputfield for entering 2 (length, width) or 3 (length, width and height) dimensions '
. 'of an object as integer or float number.',
'version' => '1.3.0',
'href' => 'https://github.com/juergenweb/FieldtypeObjectDimensions',
'icon' => 'cube',
'permanent' => false,
'requires' => [
'FieldtypeObjectDimensions',
'PHP>=8.0',
'ProcessWire>=3.0.212'
],
'author' => 'Jürgen Kern'
];
}
/**
* Set the default values of the configuration
*/
public function __construct()
{
parent::__construct();
$this->set('input_type', '3d');
$this->set('input_sizeunit', 'cm');
$this->set('input_decimals', 2);
$this->set('input_showhint', false);
}
/**
* Add the CSS and JS files before the inputfield will be rendered
*
* The files are only loaded if the inputfield is really used and not on every request.
*
* @param Inputfield|null $parent
* @param bool $renderValueMode
* @return bool
*/
public function renderReady(?Inputfield $parent = null, $renderValueMode = false)
{
// the module version as parameter: browsers load the files again after an update of the module
$version = $this->getModuleInfo()['version'];
// use $this->config instead of config(), because the functions API is disabled by default
$config = $this->wire()->config;
$url = $config->urls->get($this->className);
$config->styles->add($url . 'assets/objectdimensions.css?v=' . $version);
$config->scripts->add($url . 'assets/objectdimensions.js?v=' . $version);
return parent::renderReady($parent, $renderValueMode);
}
/**
* Get the (translated) labels of the dimensions
*
* @return array key (length, width, height, volume, area, dimensions) => label
*/
public static function getLabels(): array
{
return [
'length' => __('Length'),
'width' => __('Width'),
'height' => __('Height'),
'volume' => __('Volume'),
'area' => __('Area'),
'dimensions' => __('Dimensions')
];
}
/**
* Render the inputs for the dimension values
*
* The names of the inputs are the name of the inputfield plus the dimension (e.g. dimensions_length).
*
* @return string
*/
public function ___render(): string
{
$out = '';
// step attribute and hint depending on the number of decimals
$decimals = intval($this->input_decimals);
if ($decimals >= 1) {
// singular or plural depending on the number of decimals
$hint = sprintf($this->_n('Please enter %d decimal.', 'Please enter %d decimals.', $decimals), $decimals);
$steps = '0.' . str_repeat('0', $decimals - 1) . '1';
} else {
$hint = $this->_('Please enter no decimals.');
$steps = '1';
}
// placeholder depending on the number of decimals (e.g. 0.00)
$placeholderStr = $decimals ? '0.' . str_repeat('0', $decimals) : '0';
// only the input dimensions (no volume, area and dimensions)
$dimensionItems = self::getLabels();
unset($dimensionItems['volume'], $dimensionItems['area'], $dimensionItems['dimensions']);
foreach ($dimensionItems as $key => $v) {
if ((($this->input_type == '2d') && ($key != 'height')) || ($this->input_type == '3d')) {
// prefix the id with the field name to keep it unique (e.g. multiple dimension fields or repeaters)
$out .= '<div id="' . $this->name . '_' . $key . '_dimension" class="dimension-field">';
// create the label tag
$label = InputfieldWrapper::getMarkup()['item_label'];
$unit = $this->input_sizeunit ? ' (' . $this->input_sizeunit . ')' : '';
// the unit is entered in the field configuration -> encode it for the output
$labelText = $this->wire()->sanitizer->entities($v . $unit);
$label = str_replace(['{out}', '{class}'], [$labelText, ''], $label);
$out .= str_replace('{for}', $this->name . '_' . $key, $label);
// create the input tag
/** @var InputfieldText $inputfield */
$inputfield = $this->wire()->modules->get('InputfieldText');
$inputfield->attr('id|name', $this->name . '_' . $key);
$inputfield->attr('class', 'objectdimensions-input');
$inputfield->attr('type', 'number');
$inputfield->value = $this->value->$key;
$inputfield->attr('step', $steps);
$inputfield->attr('min', '0');
// number of allowed decimals for the JS restriction (see assets/objectdimensions.js)
$inputfield->attr('data-decimals', (string)$decimals);
$inputfield->placeholder = $placeholderStr;
$out .= $inputfield->render();
$out .= '</div>';
}
}
// inform the user about how many decimals can be entered
if ($this->input_showhint) {
$notes = InputfieldWrapper::getMarkup()['item_notes'];
$notes = str_replace('{out}', $hint, $notes);
$out .= $notes;
}
return $out;
}
/**
* Sanitize and validate the submitted values of the dimensions
*
* All values are validated first. If at least one value is invalid, no value will be changed.
*
* @param WireInputData $input
* @return $this
*/
public function ___processInput(WireInputData $input): self
{
$fieldName = $this->attr('name');
$value = $this->attr('value');
if (!$value instanceof ObjectDimensions) {
$value = new ObjectDimensions();
$this->attr('value', $value);
}
$decimals = intval($this->input_decimals);
// dimensions to process: length and width, height only on 3 dimensions
$keys = ['length', 'width'];
if ($this->input_type == '3d') {
$keys[] = 'height';
}
// 1. validate all inputs first
$newValues = [];
$hasErrors = false;
foreach ($keys as $key) {
$inputName = $fieldName . '_' . $key;
$raw = $input->$inputName;
// skip dimensions which are not part of the submitted data (e.g. field was not rendered)
if ($raw === null) {
continue;
}
// sanitize the value to be a float with the number of decimals set in the configuration
$number = (float)number_format((float)$raw, $decimals, '.', '');
if ($number < 0) {
// negative values are not allowed
$this->error(sprintf(
$this->_('Field "%s" does not accept negative values.'),
self::getLabels()[$key]
));
$hasErrors = true;
} elseif ($this->required && $number == 0.0) {
// a required field must have a value higher than 0
$this->error(sprintf(
$this->_('Field "%s" is required and must contain a positive value higher than 0.'),
self::getLabels()[$key]
));
$hasErrors = true;
} else {
$newValues[$key] = $decimals > 0 ? $number : (int)$number;
}
}
// 2. do not change anything if at least one value is invalid (prevents partial updates)
if ($hasErrors) {
return $this;
}
// 3. set the values and track changes only if a value has been changed
foreach ($newValues as $key => $newValue) {
$oldValue = $value->get($key);
if ($oldValue === null || (float)$oldValue !== (float)$newValue) {
$value->set($key, $newValue);
$this->trackChange('value');
}
}
return $this;
}
/**
* Get the configuration fields of the inputfield
*
* Type, size unit and number of decimals are configured in the fieldtype, because they affect the database
* schema (see FieldtypeObjectDimensions::___getConfigInputfields()).
*
* @return InputfieldWrapper
* @throws WirePermissionException
*/
public function ___getConfigInputfields(): InputfieldWrapper
{
$inputfields = parent::___getConfigInputfields();
// checkbox to enable/disable the hint with the max number of decimals below the inputs
$f = $this->modules->get('InputfieldCheckbox');
$f->attr('name+id', 'input_showhint');
$f->attr('checked', $this->input_showhint == '1' ? 'checked' : '');
$f->checkboxLabel = $this->_('Show hint');
$f->label = $this->_('Show hint for max number of decimals below input fields.');
// phpcs:ignore Generic.Files.LineLength -- translatable strings must be a single string literal
$f->description = $this->_('If checked, a hint text will be displayed under the input fields to inform the user how many decimals are allowed.');
$inputfields->append($f);
return $inputfields;
}
/**
* Get the configuration settings which can be changed per template (context)
*
* The type (2d/3d) is not allowed per template, because all templates share the same database table.
*
* @param Field $field
* @return array
*/
public function ___getConfigAllowContext($field): array
{
// keep the default context settings of the parent (e.g. columnWidth, required, showIf)
return array_merge(parent::___getConfigAllowContext($field), [
'input_showhint'
]);
}
/**
* Check if the field is empty (no dimension value higher than 0)
*
* Used by the required check of ProcessWire. The default check uses the string value of the object (HTML output),
* this check uses the dimension values directly.
*
* @return bool
*/
public function isEmpty(): bool
{
$value = $this->attr('value');
if (!$value instanceof ObjectDimensions) {
return true;
}
return !(float)$value->length && !(float)$value->width && !(float)$value->height;
}
}