-
Notifications
You must be signed in to change notification settings - Fork 0
/
FieldtypeMeasurement.module
750 lines (695 loc) · 28.6 KB
/
FieldtypeMeasurement.module
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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
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
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
<?php namespace ProcessWire;
use MetaTunes\MeasurementClasses\{BaseMeasurement, Measurement, Dimension};
/**
* ProcessWire Measurement Fieldtype
*
*
* Field that stores 4 values for: quantity (Temperature, Mass etc.), unit (Celsius, kilogram etc.) and value (in base units and the specified units).
* Define quantity and unit options field settings. These may be different in different template contexts.
*
* @author Mark Evens
* @license Licensed under GNU/GPL v3
* @link https://processwire.com/talk/topic/26241-fieldtypemeasurement/
*
* ProcessWire 2.x, 3.x
* Copyright (C) 2016 by Ryan Cramer
* Licensed under GNU/GPL v2, see LICENSE.TXT
*
* http://processwire.com
*
* @version 0.0.22
* @since 0.0.21 allow use in getModuleConfigInputfields() method
* @since 0.0.20 bug fix Measurement.php
* @since 0.0.19 bug fix Mass.php
* @since 0.0.18 new Measurement methods baseMultiplyBy() & baseDivideBy()
* @since 0.0.17 allow optional suppression of null units warning on wakeup
* @since 0.0.16 allow use of RockCalculator, if installed
* @since 0.0.15 bug fix
* @since 0.0.14 improved operation inside repeater matrix items
* @since 0.0.13 bug fixes
* @since 0.0.12 minor enhancements & bug fixes
* @since 0.0.11 interactive dependent select in config, bug fixes
* @since 0.0.10 bug fixes to in-field conversion
* @since 0.0.9 bug fixes and enhancements to in-field conversion
* @since 0.0.8 changed in-field conversion method to use htmx rather than forcing save, plus numerous bug fixes
* @since 0.0.7 added 'note' box to be rendered as tooltip, if present
* @since 0.0.6 new namespaces, refactoring and extended dimensions
* @since 0.0.5 minor fixes and new Measurement methods
* @since 0.0.4 allowed specific formats in config file, additional units, dimensional analysis supported
* @since 0.0.3 added static function addMeasurement(), altered add() and subtract() methods to Measurement, bug fixes
* @since 0.0.2 revised db schema to hold base unit value
* @since 0.0.1 minor mods
* @since 0.0.0 initial version
*
*
*
* * MODULE CONFIGURATION PROPERTIES
* ===============================
* @property string $quantity The physical quantity to be measured
* @property string $units The units to be presented as options
* @property string $unit The current selected unit
* @property string $magnitude The magnitude (in terms of the selected unit)
* @property bool $hide_quantity Do not show quantity info in the inputfield
* @property bool $show_update Show the checkbox to convert units in the inputfield
* @property bool $set_default Set the first unit in the selection list as the default
*
* * ERROR STRINGS
* @property string $invalidUnits
*
*/
class FieldtypeMeasurement extends Fieldtype implements ConfigurableModule {
/**
* @return array
*/
public static function getModuleInfo(): array {
return array(
'title' => __('Measurement', __FILE__), // Module Title
'summary' => __('Field that stores values for: quantity (Temperature, Mass etc.), unit (Celsius, kilogram etc.) and magnitude (in base units and the specified units).', __FILE__), // Module Summary
'version' => '0.0.22',
'author' => 'Mark Evens',
'requires' => ['ProcessWire>=3.0.148'],
'installs' => 'InputfieldMeasurement',
'requiredBy' => 'InputfieldMeasurement',
'autoload' => true, // required so that ready() method runs
'icon' => 'balance-scale',
);
}
/**
* Construct the Fieldtype and populate default settings and column types
*
*/
public function __construct() {
parent::__construct();
require_once(wire('config')->paths->siteModules . basename(__DIR__) . '/Measurement.php');
require_once(wire('config')->paths->siteModules . basename(__DIR__) . '/Dimension.php');
}
/**
* Adds the unit as a selectable item in the given field
* If $template is given, save the field in that context only
*
* @param string $field
* @param string $unit
* @param string|null $template
* @throws WireException
*/
public static function addSelectableUnit(string $field, string $unit, ?string $template = null) {
$f = wire()->fields->get($field);
if($template) $f = $f->getContext($template);
$units = $f->get('units');
if(!$units || !in_array($unit, $units)) $units[] = $unit;
$f->set('units', $units);
if($template) {
$template = wire()->templates->get($template);
wire()->fields->saveFieldgroupContext($f, $template->fieldgroup);
$template->save();
} else {
$f->save();
}
}
/**
* Removes the unit as a selectable item in the given field
* If $template is given, remove the field in that context only
*
* @param string $field
* @param string $unit
* @param string|null $template
* @throws WireException
*/
public static function removeSelectableUnit(string $field, string $unit, ?string $template = null) {
$f = wire()->fields->get($field);
if($template) $f = $f->getContext($template);
$units = $f->get('units');
if(in_array($unit, $units)) {
$key = array_search($unit, $units);
unset($units[$key]);
}
$f->set('units', $units);
if($template) {
$template = wire()->templates->get($template);
wire()->fields->saveFieldgroupContext($f, $template->fieldgroup);
$template->save();
} else {
$f->save();
}
}
public function baseMeasurement($magnitude = null, $dimension = null) {
if(!$dimension) $dimension = new Dimension();
return new BaseMeasurement($magnitude, $dimension);
}
public function measurement_to_array($measurement) {
if(!is_object($measurement) || !is_a($measurement, Measurement::class)) return [];
$array = ['quantity' => $measurement->quantity, 'unit' => $measurement->unit, 'magnitude' => $measurement->magnitude, 'remark' => $measurement->remark];
return $array;
}
public function array_to_measurement($array) {
if(!is_array($array)) return null;
if(!isset($array['quantity']) || !$array['quantity']) return null;
$quantity = $array['quantity'];
$unit = (isset($array['unit'])) ? $array['unit'] : null;
$magnitude = (isset($array['magnitude'])) ? $array['magnitude'] : null;
$remark = (isset($array['remark'])) ? $array['remark'] : null;
return new Measurement($quantity, $unit, $magnitude, $remark);
}
/**
* Initialize this Fieldtype
*
*/
public function init() {
$info = $this->getModuleInfo();
$version = $info['version'];
$config = $this->wire()->config;
$ajax = wire('config')->ajax;
$this->wire()->config->scripts->append("https://unpkg.com/[email protected]");
$css = $config->urls->$this . "{$this}.css?v=$version";
$this->wire()->config->styles->append($css);
$this->invalidUnits = __('This unit is not valid for the quantity type of this field. Resetting value to zero.');
// magnitudes must be numbers, arrays of numbers or numbers separated by pipe | symbols
// The sanitizer will return an array of floats
$this->wire()->sanitizer->addHook('magnitude', function(HookEvent $event) {
$sanitizer = $event->object;
//bd('sanitizer in hook');
$value = $event->arguments(0); // get first argument given to method
if($value !== null) {
if(!is_array($value)) $value = explode('|', $value);
foreach($value as $key => $item) {
$value[$key] = (float)$item; //$sanitizer->float($item);
}
}
$event->return = $value;
});
if(!$ajax) {
$js = $config->urls->$this . "{$this}.js?v=$version";
$this->wire()->config->scripts->append($js);
}
if($this->wire->modules->isInstalled('RockCalculator')) {
$rc = wire()->modules->get('RockCalculator');
$this->wire->config->scripts->add($rc->m('lib/math.min.js'));
$this->wire->config->scripts->add($rc->m('lib/tooltip.js'));
$this->wire->config->scripts->add($rc->m($rc->className . '.js'));
$this->wire->config->styles->add($rc->m($rc->className . '.css'));
}
parent::init();
//bd('init done');
}
public function ___install() {
if(!$this->wire()->config->useFunctionsAPI) {
$this->wire->session->error($this->_('You need to use the functions API for this module. Set $config->useFunctionsAPI=true in your config.php file'));
}
}
public function ready() {
/* Hook to add module js and css to any front-end page
NB must be in ready() not init()
*/
wire()->addHookAfter('Page::render', function(HookEvent $event) {
$page = $event->object;
// don't add this to the admin pages
if($page->template == 'admin') return;
//bd($event->return, 'return in hook');
$info = $this->getModuleInfo();
$version = $info['version'];
$config = $this->wire()->config;
$css = $config->urls->$this . "{$this}.css?v=$version";
$event->return = str_replace("</head>", "<link rel='stylesheet' type='text/css' href='{$css}'/>\n</head>", $event->return);
$js = $config->urls->$this . "{$this}.js?v=$version";
$event->return = str_replace("</body>", "</body>\n<script src='{$js}'></script>", $event->return);
});
// Hook below is to ensure that html in Measurement remark (displayed as tooltip) does not get escaped.
$this->wire()->addHookAfter('Page::renderField', function(HookEvent $event) {
// Get the object the event occurred on, if needed
$page = $event->object;
// An 'after' hook can retrieve and/or modify the return value
$return = $event->return;
// Get values of arguments sent to hook (if needed)
$fieldName = $event->arguments(0);
$file = $event->arguments(1);
$value = $event->arguments(2);
/* Your code here, perhaps modifying the return value */
$f = $this->wire()->fields->get($fieldName);
// Populate back return value, if you have modified it
//bd($return, 'return in hook');
if($f->type == 'FieldtypeMeasurement') $event->return = html_entity_decode($return);
});
/*
* This hook is to process htmx attributes on change of quantity - to update the DOM with the new units info
*/
$this->wire()->addHookAfter('ProcessField::buildEditForm', function(HookEvent $event) {
$object = $event->object;
$field = $object->getField();
if(!$field || !$field->id || !$field->quantity) {
$this->session()->set('newField', true);
}
##################### HTMX LISTENER #############################
$input = wire('input');
$ajax = wire('config')->ajax;
if($ajax && $input->get('refresh') == 1) {
$quantity = $input->get('quantity');
$fs = $this->unitSet($quantity, $field);
$inputfields = parent::___getConfigInputfields($field);
$inputfields->append($fs);
/*
* In order to render AsmSelect and Toggle fields, we need to make sure the js and css is present
*/
// AsmSelect //
$js1 = $this->config->urls->modules . "Inputfield/InputfieldAsmSelect/asmselect/jquery.asmselect.js";
$out = "<script src='{$js1}'></script>";
$js2 = $this->config->urls->modules . "Inputfield/InputfieldAsmSelect/InputfieldAsmSelect.js";
$out .= "<script src='{$js2}'></script>";
$css1 = $this->config->urls->modules . "Inputfield/InputfieldAsmSelect/asmselect/jquery.asmselect.css";
$out .= "<link rel='stylesheet' href='{$css1}'/>";
$css2 = $this->config->urls->modules . "Inputfield/InputfieldAsmSelect/InputfieldAsmSelect.css";
$out .= "<link rel='stylesheet' href='{$css2}'/>";
// Toggle // We only want to add this for new fields as it persists afterwards
if($this->session()->get('newField')) {
$js3 = $this->config->urls->modules . "Inputfield/InputfieldToggle/InputfieldToggle.js";
$out .= "<script src='{$js3}'></script>";
$css3 = $this->config->urls->modules . "Inputfield/InputfieldToggle/InputfieldToggle.css";
$out .= "<link rel='stylesheet' href='{$css3}'/>";
$this->session->remove('newField');
// newField session var is removed in save hook below
}
// Finally we can render the html to be swapped into 'unit_set'
$out .= $inputfields->render();
echo $out;
die();
}
});
}
/**
* Format value for output
* See Measurement::format() for details of format options
*
* @param Page $page
* @param Field $field
* @param int|object|string $value
* @return int|mixed|object|string|null
* @see MetaTunes\MeasurementClasses\Measurement::format()
*
*/
public function ___formatValue(Page $page, Field $field, $value) {
//bd($value, 'value in formatValue 1');
if(!$value instanceof Measurement) return null;
//bd($value, 'value in formatValue 2');
return $value->render();
}
/**
* Return the Inputfield for this fieldtype
*
* @param Page $page
* @param Field $field
* @return Inputfield
*
* @throws WireException
*/
public function getInputfield(Page $page, Field $field): Inputfield {
$inputfield = $this->wire('modules')->get('InputfieldMeasurement');
//NB $field will have the template context. If it is in a repeater matrix item, it needs the (namespaced) context for the repeater matrix type
if(($page->template && $page->template->pageClass == 'RepeaterMatrixPage') || ($page instanceof RepeaterMatrixPage)) {
if($page->getField($field->name)) {
$field_in_context = $page->fieldgroup->getFieldContext($field, "matrix$page->repeater_matrix_type");
if($field_in_context) $field = $field_in_context;
}
}
$inputfield->setField($field);
$inputfield->setPage($page);
return $inputfield;
}
/**
* Get compatible fieldtypes
* No known compatible types
*
* @param Field $field
* @throws WireException
*/
public function ___getCompatibleFieldtypes(Field $field) {
$fieldtypes = $this->wire(new Fieldtypes());
foreach($this->wire('fieldtypes') as $fieldtype) {
if($fieldtype instanceof self) {
$fieldtypes->add($fieldtype);
}
}
return $fieldtypes;
}
/**
* Given a raw value (value as stored in DB), return the value as it would appear in a Page object
*
* @param Page $page
* @param Field $field
* @param string|int|array $value
* @param boolean $warnNull optional argument to allow suppression of null warning - not part of parent method
* @return string|int|array|object $value
*
* @throws WireException
*/
public function ___wakeupValue(Page $page, Field $field, $value, $warnNull = true) {
// if for some reason we already get a valid value, then just return it
if($value instanceof Measurement) return $value;
//bd($value, 'wakeup value');
// start a blank value to be populated
$measurement = $this->getBlankValue($page, $field);
// if we were given a blank value, then we've got nothing to do: just return a blank Measurement object
if(empty($value) || !is_array($value)) return $measurement;
// create new Measurement object
// bd($measurement, 'measurement0 in ___wakeupValue'); //debug
if($value['quantity'] != $measurement->quantity) {
$this->warning(sprintf($this->_(
'Quantity (%1$s) in database is different from quantity (%2$s) required in this context. Setting quantity to the context quantity.'),
$value['quantity'], $measurement->quantity));
// $measurement->quantity will already have been set to $context->quantity in getBlankValue
}
if(!in_array($measurement->quantity, Measurement::getQuantities())) {
$this->error(sprintf($this->_(
'Quantity (%s) in database has no Config file'),
$measurement->quantity));
return $measurement;
}
$measurement->magnitude = $measurement->baseMagnitude = $value['data'];
if($value['unit']) {
//bd($value['unit']);
if(!$measurement->unit) $measurement->set('unit', $measurement->baseUnit);
$measurement->convertTo($value['unit']);
$units = $measurement->getUnits();
if(array_key_exists($value['unit'], $units) && isset($value['magnitude'])) {
$measurement->magnitude = $this->wire()->sanitizer->magnitude($value['magnitude']); // sanitizer added in hook in init
} else {
$measurement->unit = $measurement->units->base;
$this->error(sprintf($this->_(
'The previously saved unit (%1$s) is not (or no longer) compatible with this quantity (%2$s). Perhaps a changed config file? Value is shown as base units.'),
$value['unit'], $measurement->quantity) . ($this->_("\nThe best way to fix this may be to save the base unit value shown and then convert to the required unit.")));
}
} else {
$measurement->unit = null;
//bd($value, 'unit not known');
if($warnNull) $this->warning(sprintf($this->_(
'Unit is not known for field %s. Value is shown as base units. Measurements with null unit will not be rendered. Please amend if appropriate.'),
$field->name));
}
if(!is_array($measurement->magnitude)) $measurement->magnitude = [$measurement->magnitude];
// bd($measurement, 'measurement1 in ___wakeupValue'); //debug
// get the current shortLabel and plural from the config file - not a saved version
if($measurement->unit and $measurement->quantity) {
$units = $measurement->getUnits();
foreach($units[$measurement->unit] as $key => $item) {
if($key == 'conversion') continue;
if(isset($units[$measurement->unit][$key])) {
$measurement->set($key, $item);
} else {
if($measurement->get($key)) $measurement->remove($key);
}
}
if($this->unit and !is_null($this->magnitude)) {
$this->convertFrom($this->magnitude, $this->unit);
}
}
if(isset($value['note'])) $measurement->remark = $value['note'];
$measurement->resetTrackChanges();
//bd($measurement, 'measurement2 in ___wakeupValue'); //debug
return $measurement;
}
/**
* Return a blank ready-to-populate version of a field of this type
*
* @param Page $page
* @param Field $field
* @return Measurement
*/
public function getBlankValue(Page $page, Field $field): Measurement {
//NB Field details may differ between templates so we need to get the field in context
$context = ($page && $page->id) ? $field->getContext($page->template) : $field;
//bd($page, 'page in getblank');
//NB 2 Override the template context with the repeater matrix type context, if applicable
if(($page->template && $page->template->pageClass == 'RepeaterMatrixPage') || ($page instanceof RepeaterMatrixPage)) {
if($page->getField($field->name)) {
$field_in_context = $page->fieldgroup->getFieldContext($field, "matrix$page->repeater_matrix_type");
if($field_in_context) $context = $field_in_context;
}
}
//bd($context, 'CONTEXT IN getBlankValue');
/* @var $context FieldtypeMeasurement */
$measurement = new Measurement($context->quantity, null, []);
if($context->quantity) $measurement->set('quantity', $context->quantity);
$measurement->set('magnitude', []);
$measurement->set('remarks', null);
$measurement->set('shortLabel', null);
$measurement->set('plural', null);
return $measurement;
}
/**
* Given an 'awake' value, as set by wakeupValue, convert the value back to a basic type for storage in DB.
*
* @param Page $page
* @param Field $field
* @param array|float|int|object|string $value
* @return array
* @throws WireException
*/
public function ___sleepValue(Page $page, Field $field, $value): array {
$sleepValue = array();
// bd($value, 'value to sleep'); //debug
// if we are given something other than a Measurement object,
// then just return a blank array
if(!$value instanceof Measurement) return $sleepValue;
// If $value does not have a quantity, reconstruct it
if(!$value->quantity) {
$context = $field->getContext($page->template);
$value->quantity = $context->quantity;
$value = new Measurement($value->quantity, $value->unit, $value->magnitude);
}
if($value->get('magnitude') === []) {
$value->set('magnitude', null);
}
if($value->get('unit')) {
// Use up-to-date conversion to save base magnitude, if we have a unit
$data = (is_array($value->valueAsBase()) ? $value->valueAsBase()[0] : $value->valueAsBase());
} else if($value->get('baseMagnitude')) {
$data = $value->get('baseMagnitude');
} else {
$data = 0;
}
$magnitude = (is_array($value->get('magnitude'))) ? implode('|', $value->get('magnitude')) : $value->get('magnitude');
$sleepValue = array(
'data' => $data,
'magnitude' => $magnitude,
'unit' => $value->unit,
'quantity' => $value->quantity,
'note' => $value->remark
);
//bd($sleepValue, 'sleepValue'); //debug
return $sleepValue;
}
/**
* Given a value, make it clean for storage within a Page
*
* @param Page $page
* @param Field $field
* @param int|object|WireArray|string $value
* @return int|Measurement|object|WireArray|string
* @throws WireException
*/
public function sanitizeValue(Page $page, Field $field, $value) {
// if given a blank value, return a valid blank value
if(empty($value)) return $this->getBlankValue($page, $field);
// if given something other than a Measurement object, throw an error
if(!$value instanceof Measurement) {
throw new WireException(sprintf($this->_('Value set to field %s must be a Measurement object'), $field->name));
}
// note that sanitization of individual fields within a given measurement is already
// performed in the Measurement class, so we don't need to do anything else here.
return $value;
}
/**
* Return the database schema that defines a Measurement
*
* @param Field $field
* @return array
*/
public function getDatabaseSchema(Field $field): array {
$schema = parent::getDatabaseSchema($field);
$schema['data'] = 'double NOT NULL'; // value in base units
$schema['magnitude'] = 'varchar(64)'; // value in current units - needs to be text to store composite values
$schema['unit'] = 'text NOT NULL';
$schema['quantity'] = 'text NOT NULL';
$schema['note'] = 'text';
return $schema;
}
/**
*
* Method called when the field is database-queried from a selector
* For Measurement objects, the baseMagnitude setting is stored in the DB as 'data'
*
* @throws WireException
* @see Fieldtype::getMatchQuery()
*/
public function getMatchQuery($query, $table, $subfield, $operator, $value) {
// If searching 'magnitude' then assume our default (data) field
if($subfield == 'baseMagnitude') $subfield = 'data';
return parent::getMatchQuery($query, $table, $subfield, $operator, $value);
}
/**
* Get any inputfields used for configuration of this Fieldtype.
* This is in addition any configuration fields supplied by the parent Inputfield.
*
* @param Field $field
* @return InputfieldWrapper
*
* @throws WirePermissionException
* @see Inputfield::getConfigInputfields()
*/
public function ___getConfigInputfields(Field $field): InputfieldWrapper {
/* @var $field \ProcessWire\FieldtypeMeasurement */
$inputfields = parent::___getConfigInputfields($field);
$input = $this->wire('input');
if($input->post('quantity')) $field->set('quantity', $input->post('quantity'));
$f = $this->modules->get("InputfieldSelect");
$quantities = Measurement::getQuantities();
$f->label = __("Quantity");
$f_name = 'quantity';
$f->name = $f_name;
$f->columnWidth = 50;
$f->description = __("Type of physical quantity to be measured.");
//$f->notes = __("Save after changing to see the options for units applicable to this quantity."); // No longer necessary with htmx
foreach($quantities as $quantity) {
$f->addOption($quantity);
}
$f->value = $field->get('quantity');
#### add HTMX to change the units selection ####
$adminEditURL = $this->wire('config')->urls->admin . "setup/field/edit/";
$adminEdit = "{$adminEditURL}?id={$field->id}&refresh=1";
$f->attr([
'hx-get' => $adminEdit, // caught by HTMX AJAX LISTENER in hook
'hx-target' => "#Inputfield_unit_set", // our element to target with swap
'hx-swap' => 'innerHTML swap:150ms settle:250ms', //
'hx-trigger' => 'change', //
'hx-include' => "[name=quantity]", // the new quantity
]);
########## end of hx- attributes ########################
$f->addClass('no-selectize');
$inputfields->append($f);
$f = $this->modules->get('InputfieldCheckbox');
$f->attr('name', 'hide_quantity');
$f->label = __('Hide quantity display in the input field.');
$f->attr('checked', $field->hide_quantity ? 'checked' : '');
$f->columnWidth = 50;
$inputfields->append($f);
$quantity = $field->get('quantity');
$fs = $this->unitSet($quantity, $field);
$inputfields->append($fs);
$f = $this->modules->get('InputfieldCheckbox');
$f->attr('name', 'show_remark');
$f->label = __('Show remark');
$f->notes = __("Show a remark entry box (default front-end rendering is as a tooltip).");
$f->attr('checked', $field->show_remark ? 'checked' : '');
$f->columnWidth = 100;
$inputfields->append($f);
return $inputfields;
}
/**
* Create a Measurement object for API usage
* E.g. Get the module: ````$mm = $modules->get('FieldtypeMeasurement');````
* then use this to construct a new measurement: ````$measure = $mm->measurement();````
* The arguments may be null and set later, but errors may occur if using methods for objects without all properties set. Use set and get thus:
* ````
* $measurement->set('quantity', 'Area');
* $measurement->get('quantity');
* ````
* For combination units, the magnitude must be an array or a string of numbers with pipe joins, e.g. '2|3.5'
*
* @param string|null $quantity
* @param string|null $unit
* @param null $magnitude
* @return Measurement
* @see MetaTunes\MeasurementClasses\Measurement::__construct
*
*/
public function measurement(?string $quantity = null, ?string $unit = null, $magnitude = null, $note = null) {
return new Measurement($quantity, $unit, $magnitude, $note);
}
protected function unitSet($quantity, $field) {
$ajax = wire('config')->ajax;
//bd($ajax, 'AJAX?');
//bd($field, 'field');
$fs = $this->wire()->modules->get('InputfieldFieldset');
$fs->name = 'unit_set';
$fs->label = __('Units & magnitude');
if($quantity) {
$m = $this->measurement($quantity);
$units = $m->units->definitions;
$notes = ($m->units->notes) ? '<p>' . $m->units->notes . '</p>' : '';
foreach($units as $key => $unit) {
$extraNotes = isset($unit->notes) ? "<h4>{$key}</h4><p>{$unit->notes}</p>" : null;
$notes .= $extraNotes;
}
if($notes) {
$f = $this->modules->get('InputfieldMarkup');
$f->attr('name', 'notes');
$f->label = __("Notes about $quantity");
$f->value = $notes;
$f->collapsed = Inputfield::collapsedYes;
$fs->append($f);
}
$f = $this->modules->get('InputfieldAsmSelect');
$f_name = 'units';
$f->name = $f_name;
$f->label = __('Select the units you want to appear as options for this field.');
$f->columnWidth = 100;
foreach($units as $key => $unit) {
$shortLabel = $unit['shortLabel'];
if($shortLabel) $shortLabel = "($shortLabel)";
$alias = (isset($unit['alias']) && $unit['alias']) ? $unit['alias'] : $key;
$f->addOption($key, "$alias $shortLabel");
}
$f->value = $field->get('units');
//bd($f, 'Unit select field');
$fs->append($f);
$f = $this->modules->get('InputfieldCheckbox');
$f->attr('name', 'set_default');
$f->label = __('Set the first unit in the selection list as the default');
$f->notes = __("If not checked, the default will be the base unit, if that is in the selection list");
$f->attr('checked', $field->set_default ? 'checked' : '');
$f->columnWidth = 33;
$fs->append($f);
$f = $this->modules->get('InputfieldToggle');
$f->attr('name', 'enable_conversion');
$f->label = __('Convert magnitude on change of unit?');
$f->notes = __("Select 'Always' to automatically convert whenever the unit is changed, 'Never' to leave the magnitude unchanged and 'Ask' to get a choice each time.");
// Set custom labels:
$f->labelType = InputfieldToggle::labelTypeCustom;
$f->yesLabel = $this->_("Always");
$f->noLabel = $this->_("Never");
// Add an "other" option with label "Ask":
$f->useOther = true;
$f->otherLabel = $this->_("Ask");
// Set default
$f->value = ($field->get('enable_conversion') != '') ? $field->get('enable_conversion') : 2; // Default is 'Ask'
$f->columnWidth = 33;
$fs->append($f);
$f = $this->modules->get('InputfieldCheckbox');
$f->attr('name', 'hide_magnitude');
$f->label = __('Hide magnitude display in the input field.');
$f->notes = __("Usage example: To just display a units selector - e.g for selecting default units for use in multiple related fields (via API).
Any conversion options will be irrelevant.");
$f->attr('checked', $field->hide_magnitude ? 'checked' : '');
$f->columnWidth = 33;
$fs->append($f);
}
//bd($fs, 'unit_set');
return $fs;
}
/**
* Allow field settings to be modified on a template by template basis
* Context-modifiable field settings are ````['quantity', 'units', 'hide_quantity', 'show_update', 'show_remark']````
*
* @param Field $field
* @return array|string[]
* @see Fieldtype::getConfigAllowContext()
*/
public function ___getConfigAllowContext(Field $field): array {
$a = array('quantity', 'units', 'hide_quantity', 'hide_magnitude', 'show_update', 'enable_conversion', 'show_remark', 'set_default', 'unit_set');
return array_merge(parent::___getConfigAllowContext($field), $a);
}
public function getModuleConfigInputfields($data) {
return new InputfieldWrapper();
}
// public function ___getModuleConfigArray() {
//
// }
}