From 04f4a327c20db6b7915f337ee0c24d9679a10136 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 5 Feb 2021 15:57:38 +0200 Subject: [PATCH] implement date-duration select_unit="false" to show durations like "0:01:20" --- api/js/etemplate/et2_widget_date.js | 130 +++++++++++++++++++++-- api/js/etemplate/et2_widget_date.ts | 153 ++++++++++++++++++++++++--- api/src/Etemplate/Widget/Date.php | 8 +- api/templates/default/etemplate2.css | 7 +- 4 files changed, 269 insertions(+), 29 deletions(-) diff --git a/api/js/etemplate/et2_widget_date.js b/api/js/etemplate/et2_widget_date.js index ced5c6f931..f520acff32 100644 --- a/api/js/etemplate/et2_widget_date.js +++ b/api/js/etemplate/et2_widget_date.js @@ -23,6 +23,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.et2_date_range = exports.et2_date_ro = exports.et2_date_duration_ro = exports.et2_date_duration = exports.et2_date = void 0; /*egw:uses /vendor/bower-asset/jquery/dist/jquery.js; /vendor/bower-asset/jquery-ui/jquery-ui.js; @@ -593,16 +594,17 @@ var et2_date_duration = /** @class */ (function (_super) { _this.options.display_format = _this.options.display_format.replace("%", ""); } // Clean formats - _this.options.display_format = _this.options.display_format.replace(/[^dhm]/, ''); + _this.options.display_format = _this.options.display_format.replace(/[^dhms]/, ''); if (!_this.options.display_format) { // @ts-ignore _this.options.display_format = _this.attributes.display_format["default"]; } // Get translations _this.time_formats = { - "d": _this.options.short_labels ? _this.egw().lang("d") : _this.egw().lang("Days"), - "h": _this.options.short_labels ? _this.egw().lang("h") : _this.egw().lang("Hours"), - "m": _this.options.short_labels ? _this.egw().lang("m") : _this.egw().lang("Minutes") + d: _this.options.short_labels ? _this.egw().lang("d") : _this.egw().lang("Days"), + h: _this.options.short_labels ? _this.egw().lang("h") : _this.egw().lang("Hours"), + m: _this.options.short_labels ? _this.egw().lang("m") : _this.egw().lang("Minutes"), + s: _this.options.short_labels ? _this.egw().lang("s") : _this.egw().lang("Seconds") }, _this.createInputWidget(); return _this; @@ -611,9 +613,40 @@ var et2_date_duration = /** @class */ (function (_super) { // Create nodes this.node = jQuery(document.createElement("span")) .addClass('et2_date_duration'); - this.duration = jQuery(document.createElement("input")) + var inputs = []; + for (var i = this.options.select_unit ? 1 : this.options.display_format.length; i > 0; --i) { + var input = document.createElement("input"); + inputs.push(input); + if (!this.options.select_unit) { + var attr = { min: 0 }; + switch (this.options.display_format[this.options.display_format.length - i]) { + case 's': + attr.max = 60; + attr.title = this.egw().lang('Seconds'); + break; + case 'm': + attr.max = 60; + attr.title = this.egw().lang('Minutes'); + break; + case 'h': + attr.max = 24; + attr.title = this.egw().lang('Hours'); + break; + case 'd': + attr.title = this.egw().lang('Days'); + break; + } + jQuery(input).attr(attr); + } + } + this.duration = jQuery(inputs) .addClass('et2_date_duration') - .attr({ type: 'number', size: 3, step: this.options.step, lang: this.egw().preference('number_format')[0] === "," ? "en-150" : "en-001" }); + .attr({ + type: 'number', + size: 3, + step: this.options.step, + lang: this.egw().preference('number_format')[0] === "," ? "en-150" : "en-001" + }); this.node.append(this.duration); var self = this; // seems the 'invalid' event doesn't work in all browsers, eg. FF therefore @@ -672,8 +705,40 @@ var et2_date_duration = /** @class */ (function (_super) { } } }; + et2_date_duration.prototype._unit2seconds = function (_unit) { + switch (_unit) { + case 's': + return 1; + case 'm': + return 60; + case 'h': + return 3600; + case 'd': + return 3600 * this.options.hours_per_day; + } + }; + et2_date_duration.prototype._unit_from_value = function (_value, _unit) { + _value *= this._unit2seconds(this.data_format); + // get value for given _unit + switch (_unit) { + case 's': + return _value % 60; + case 'm': + return Math.floor(_value / 60) % 60; + case 'h': + return Math.floor(_value / 3600) % this.options.hours_per_day; + case 'd': + return Math.floor(_value / 3600 * this.options.hours_per_day); + } + }; et2_date_duration.prototype.set_value = function (_value) { this.options.value = _value; + if (!this.options.select_unit && this.options.display_format.length > 1) { + for (var i = this.options.display_format.length; --i >= 0;) { + jQuery(this.duration[i]).val(this._unit_from_value(_value, this.options.display_format[i])); + } + return; + } var display = this._convert_to_display(parseFloat(_value)); // Set display if (this.duration[0].nodeName == "INPUT") { @@ -699,13 +764,18 @@ var et2_date_duration = /** @class */ (function (_super) { this.format = null; } this.options.display_format = format; - if ((this.format == null || this.format.is('select')) && (this.options.display_format.length <= 1 || this.options.readonly)) { + if ((this.format == null || this.format.is('select')) && + (this.options.display_format.length <= 1 || this.options.readonly || !this.options.select_unit)) { if (this.format) { this.format.remove(); } this.format = jQuery(document.createElement('span')).appendTo(this.node); } - if (this.options.display_format.length > 1 && !this.options.readonly) { + if (!this.options.select_unit && this.options.display_format.length > 1) { + // no unit selection or display + this.format.hide(); + } + else if (this.options.display_format.length > 1 && !this.options.readonly) { if (this.format && !this.format.is('select')) { this.format.remove(); this.format = null; @@ -735,6 +805,20 @@ var et2_date_duration = /** @class */ (function (_super) { * @return Object {value: Value in display format, unit: unit for display} */ et2_date_duration.prototype._convert_to_display = function (_value) { + if (!this.options.select_unit) { + var vals = []; + for (var i = 0; i < this.options.display_format.length; ++i) { + var unit = this.options.display_format[i]; + var val = this._unit_from_value(_value, unit); + if (unit === 's' || unit === 'm' || unit === 'h' && this.options.display_format[0] === 'd') { + vals.push(sprintf('%02d', val)); + } + else { + vals.push(val); + } + } + return { value: vals.join(':'), unit: '' }; + } if (_value) { // Put value into minutes for further processing switch (this.options.data_format) { @@ -744,6 +828,9 @@ var et2_date_duration = /** @class */ (function (_super) { case 'h': _value *= 60; break; + case 's': + _value /= 60.0; + break; } } // Figure out best unit for display @@ -770,6 +857,16 @@ var et2_date_duration = /** @class */ (function (_super) { * Change displayed value into storage value and return */ et2_date_duration.prototype.getValue = function () { + if (!this.options.select_unit && this.options.display_format.length > 1) { + var value_1 = 0; + for (var i = this.options.display_format.length; --i >= 0;) { + value_1 += parseInt(jQuery(this.duration[i]).val()) * this._unit2seconds(this.options.display_format[i]); + } + if (this.options.data_format !== 's') { + value_1 /= this._unit2seconds(this.options.data_format); + } + return this.options.data_format === 'm' ? Math.round(value_1) : value_1; + } var value = this.duration.val().replace(',', '.'); if (value === '') { return this.options.empty_not_0 ? '' : 0; @@ -784,7 +881,9 @@ var et2_date_duration = /** @class */ (function (_super) { break; } // Minutes should be an integer. Floating point math. - value = Math.round(value); + if (this.options.data_format !== 's') { + value = Math.round(value); + } switch (this.options.data_format) { case 'd': value /= this.options.hours_per_day; @@ -792,6 +891,9 @@ var et2_date_duration = /** @class */ (function (_super) { case 'h': value /= 60.0; break; + case 's': + value = Math.round(value * 60.0); + break; } return value; }; @@ -800,13 +902,19 @@ var et2_date_duration = /** @class */ (function (_super) { "name": "Data format", "default": "m", "type": "string", - "description": "Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (int)." + "description": "Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (int), 's' = seconds (int)." }, "display_format": { "name": "Display format", "default": "dhm", "type": "string", - "description": "Permitted units for displaying the data. 'd' = days, 'h' = hours, 'm' = minutes. Use combinations to give a choice. Default is 'dh' = days or hours with selectbox." + "description": "Permitted units for displaying the data. 'd' = days, 'h' = hours, 'm' = minutes, 's' = seconds. Use combinations to give a choice. Default is 'dh' = days or hours with selectbox." + }, + "select_unit": { + "name": "Select unit or input per unit", + "default": true, + "type": "boolean", + "description": "Display a unit-selection for multiple units, or an input field per unit." }, "percent_allowed": { "name": "Percent allowed", diff --git a/api/js/etemplate/et2_widget_date.ts b/api/js/etemplate/et2_widget_date.ts index af77765406..e59d40320c 100644 --- a/api/js/etemplate/et2_widget_date.ts +++ b/api/js/etemplate/et2_widget_date.ts @@ -662,13 +662,19 @@ export class et2_date_duration extends et2_date "name": "Data format", "default": "m", "type": "string", - "description": "Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (int)." + "description": "Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (int), 's' = seconds (int)." }, "display_format": { "name": "Display format", "default": "dhm", "type": "string", - "description": "Permitted units for displaying the data. 'd' = days, 'h' = hours, 'm' = minutes. Use combinations to give a choice. Default is 'dh' = days or hours with selectbox." + "description": "Permitted units for displaying the data. 'd' = days, 'h' = hours, 'm' = minutes, 's' = seconds. Use combinations to give a choice. Default is 'dh' = days or hours with selectbox." + }, + "select_unit": { + "name": "Select unit or input per unit", + "default": true, + "type": "boolean", + "description": "Display a unit-selection for multiple units, or an input field per unit." }, "percent_allowed": { "name": "Percent allowed", @@ -704,7 +710,7 @@ export class et2_date_duration extends et2_date public static readonly legacyOptions: string[] = ["data_format","display_format", "hours_per_day", "empty_not_0", "short_labels"]; - time_formats: {"d":"d","h":"h","m":"m"}; + time_formats: {d:"d", h:"h", m:"m", s:"s"}; // @ts-ignore baseWidget defines node as HTMLElement node: JQuery; @@ -727,7 +733,7 @@ export class et2_date_duration extends et2_date } // Clean formats - this.options.display_format = this.options.display_format.replace(/[^dhm]/,''); + this.options.display_format = this.options.display_format.replace(/[^dhms]/,''); if(!this.options.display_format) { // @ts-ignore @@ -736,9 +742,10 @@ export class et2_date_duration extends et2_date // Get translations this.time_formats = { - "d": this.options.short_labels ? this.egw().lang("d") : this.egw().lang("Days"), - "h": this.options.short_labels ? this.egw().lang("h") : this.egw().lang("Hours"), - "m": this.options.short_labels ? this.egw().lang("m") : this.egw().lang("Minutes") + d: this.options.short_labels ? this.egw().lang("d") : this.egw().lang("Days"), + h: this.options.short_labels ? this.egw().lang("h") : this.egw().lang("Hours"), + m: this.options.short_labels ? this.egw().lang("m") : this.egw().lang("Minutes"), + s: this.options.short_labels ? this.egw().lang("s") : this.egw().lang("Seconds") }, this.createInputWidget(); } @@ -748,9 +755,43 @@ export class et2_date_duration extends et2_date // Create nodes this.node = jQuery(document.createElement("span")) .addClass('et2_date_duration'); - this.duration = jQuery(document.createElement("input")) + let inputs = []; + for (let i=this.options.select_unit?1:this.options.display_format.length; i > 0; --i) + { + let input = document.createElement("input"); + inputs.push(input); + if (!this.options.select_unit) + { + let attr :any = {min : 0}; + switch(this.options.display_format[this.options.display_format.length-i]) + { + case 's': + attr.max = 60; + attr.title = this.egw().lang('Seconds'); + break; + case 'm': + attr.max = 60; + attr.title = this.egw().lang('Minutes'); + break; + case 'h': + attr.max = 24; + attr.title = this.egw().lang('Hours'); + break; + case 'd': + attr.title = this.egw().lang('Days'); + break; + } + jQuery(input).attr(attr); + } + } + this.duration = jQuery(inputs) .addClass('et2_date_duration') - .attr({type: 'number', size: 3, step:this.options.step, lang: this.egw().preference('number_format')[0] === "," ? "en-150": "en-001"}); + .attr({ + type: 'number', + size: 3, + step:this.options.step, + lang: this.egw().preference('number_format')[0] === "," ? "en-150": "en-001" + }); this.node.append(this.duration); var self = this; @@ -827,10 +868,51 @@ export class et2_date_duration extends et2_date } } + private _unit2seconds(_unit) + { + switch(_unit) + { + case 's': + return 1; + case 'm': + return 60; + case 'h': + return 3600; + case 'd': + return 3600 * this.options.hours_per_day; + } + } + + private _unit_from_value(_value, _unit) + { + _value *= this._unit2seconds(this.data_format); + // get value for given _unit + switch(_unit) + { + case 's': + return _value % 60; + case 'm': + return Math.floor(_value / 60) % 60; + case 'h': + return Math.floor(_value / 3600) % this.options.hours_per_day; + case 'd': + return Math.floor(_value / 3600*this.options.hours_per_day); + } + } + set_value(_value) { this.options.value = _value; + if (!this.options.select_unit && this.options.display_format.length > 1) + { + for (let i = this.options.display_format.length; --i >= 0;) + { + jQuery(this.duration[i]).val(this._unit_from_value(_value, this.options.display_format[i])); + } + return; + } + var display = this._convert_to_display(parseFloat(_value)); // Set display @@ -865,7 +947,8 @@ export class et2_date_duration extends et2_date this.format = null; } this.options.display_format = format; - if((this.format == null || this.format.is('select')) && (this.options.display_format.length <= 1 || this.options.readonly)) + if((this.format == null || this.format.is('select')) && + (this.options.display_format.length <= 1 || this.options.readonly || !this.options.select_unit)) { if (this.format) { @@ -873,7 +956,12 @@ export class et2_date_duration extends et2_date } this.format = jQuery(document.createElement('span')).appendTo(this.node); } - if(this.options.display_format.length > 1 && !this.options.readonly) + if (!this.options.select_unit && this.options.display_format.length > 1) + { + // no unit selection or display + this.format.hide(); + } + else if(this.options.display_format.length > 1 && !this.options.readonly) { if(this.format && !this.format.is('select')) { this.format.remove(); @@ -910,6 +998,24 @@ export class et2_date_duration extends et2_date */ _convert_to_display(_value) { + if (!this.options.select_unit) + { + let vals = []; + for (let i=0; i < this.options.display_format.length; ++i) + { + let unit = this.options.display_format[i]; + let val = this._unit_from_value(_value, unit); + if (unit === 's' || unit === 'm' || unit === 'h' && this.options.display_format[0] === 'd' ) + { + vals.push(sprintf('%02d', val)); + } + else + { + vals.push(val); + } + } + return {value: vals.join(':'), unit: ''}; + } if (_value) { // Put value into minutes for further processing @@ -921,6 +1027,9 @@ export class et2_date_duration extends et2_date case 'h': _value *= 60; break; + case 's': + _value /= 60.0; + break; } } @@ -955,6 +1064,19 @@ export class et2_date_duration extends et2_date */ getValue() { + if (!this.options.select_unit && this.options.display_format.length > 1) + { + let value = 0; + for(let i=this.options.display_format.length; --i >= 0; ) + { + value += parseInt(jQuery(this.duration[i]).val()) * this._unit2seconds(this.options.display_format[i]); + } + if (this.options.data_format !== 's') + { + value /= this._unit2seconds(this.options.data_format); + } + return this.options.data_format === 'm' ? Math.round(value) : value; + } var value = this.duration.val().replace(',', '.'); if(value === '') { @@ -971,8 +1093,10 @@ export class et2_date_duration extends et2_date break; } // Minutes should be an integer. Floating point math. - value = Math.round(value); - + if (this.options.data_format !== 's') + { + value = Math.round(value); + } switch(this.options.data_format) { case 'd': @@ -981,6 +1105,9 @@ export class et2_date_duration extends et2_date case 'h': value /= 60.0; break; + case 's': + value = Math.round(value * 60.0); + break; } return value; } diff --git a/api/src/Etemplate/Widget/Date.php b/api/src/Etemplate/Widget/Date.php index f6895765f6..39054f2155 100644 --- a/api/src/Etemplate/Widget/Date.php +++ b/api/src/Etemplate/Widget/Date.php @@ -155,7 +155,7 @@ class Date extends Transformer $value = self::get_array($content, $form_name); $valid =& self::get_array($validated, $form_name, true); - if($value) + if ($value && $this->type !== 'date-duration') { try { @@ -233,7 +233,11 @@ class Date extends Transformer $value = $max; } } - if(!$value) + if ($this->type == 'date-duration') + { + $valid = (string)$value === '' ? '' : (int)$value; + } + elseif(!$value) { // Not null, blank $value = ''; diff --git a/api/templates/default/etemplate2.css b/api/templates/default/etemplate2.css index 691b6728a9..689d9ed510 100644 --- a/api/templates/default/etemplate2.css +++ b/api/templates/default/etemplate2.css @@ -521,8 +521,9 @@ span.et2_date span { text-align: center; font-size: 9pt; } -span.et2_date_duration input.et2_date_duration{ - width: 25%; +span.et2_date_duration input.et2_date_duration { + width: 3em; + text-align: right; } span.et2_date_duration span { vertical-align: -webkit-baseline-middle; @@ -2722,7 +2723,7 @@ span.et2_url_span input.et2_fullWidth { float: left; } .et2_fullWidth select.et2_date_duration { - width: 70%; + width: 75%; float: right; }