diff --git a/api/js/etemplate/et2_core_inputWidget.ts b/api/js/etemplate/et2_core_inputWidget.ts
index f3ea3d778a..27a9676050 100644
--- a/api/js/etemplate/et2_core_inputWidget.ts
+++ b/api/js/etemplate/et2_core_inputWidget.ts
@@ -189,7 +189,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
*
* @param {string} _value value to set
*/
- set_value(_value)
+ set_value(_value : any | null)
{
var node = this.getInputNode();
if (node)
diff --git a/api/js/etemplate/et2_types.d.ts b/api/js/etemplate/et2_types.d.ts
index 349ae11812..21cef642a7 100644
--- a/api/js/etemplate/et2_types.d.ts
+++ b/api/js/etemplate/et2_types.d.ts
@@ -7,7 +7,7 @@ declare class et2_widget{}
declare class et2_DOMWidget extends et2_widget{}
declare class et2_baseWidget extends et2_DOMWidget{}
declare class et2_valueWidget extends et2_baseWidget{}
-declare class et2_inputWidget{
+declare class et2_inputWidget extends et2_valueWidget{
getInputNode() : HTMLElement
}
declare class et2_tabbox extends et2_valueWidget {
@@ -119,7 +119,12 @@ declare var et2_radioGroup : any;
declare var et2_script : any;
declare var et2_selectAccount : any;
declare var et2_selectAccount_ro : any;
-declare var et2_selectbox : any;
+declare class et2_selectbox extends et2_inputWidget {
+ loadingFinished()
+ getDOMNode()
+ set_value(s: string)
+ getValue()
+}
declare var et2_selectbox_ro : any;
declare var et2_menulist : any;
declare var et2_split : any;
diff --git a/api/js/etemplate/et2_widget_date.js b/api/js/etemplate/et2_widget_date.js
index 9ae1a00fcc..9e10c732fc 100644
--- a/api/js/etemplate/et2_widget_date.js
+++ b/api/js/etemplate/et2_widget_date.js
@@ -1,3 +1,4 @@
+"use strict";
/**
* EGroupware eTemplate2 - JS Date object
*
@@ -7,17 +8,37 @@
* @link http://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2011
- * @version $Id$
*/
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
/*egw:uses
- /vendor/bower-asset/jquery/dist/jquery.js;
- /vendor/bower-asset/jquery-ui/jquery-ui.js;
- lib/date;
- et2_core_inputWidget;
- et2_core_valueWidget;
+ /vendor/bower-asset/jquery/dist/jquery.js;
+ /vendor/bower-asset/jquery-ui/jquery-ui.js;
+ lib/date;
+ et2_core_inputWidget;
+ et2_core_valueWidget;
*/
-
+require("./et2_core_common");
+var et2_core_inheritance_1 = require("./et2_core_inheritance");
+var et2_core_widget_1 = require("./et2_core_widget");
+var et2_core_valueWidget_1 = require("./et2_core_valueWidget");
+var et2_core_inputWidget_1 = require("./et2_core_inputWidget");
+require("./et2_types");
+var et2_core_DOMWidget_1 = require("./et2_core_DOMWidget");
+// all calls to jQueryUI.datetimepicker as jQuery.datepicker give errors which are currently suppressed with @ts-ignore
+// adding npm package @types/jquery.ui.datetimepicker did NOT help :(
/**
* Class which implements the "date" XET-Tag
*
@@ -26,1519 +47,1318 @@
*
* Widgets uses jQuery date- and time-picker for desktop browsers and
* HTML5 input fields for mobile devices to get their native UI for date/time entry.
- *
- * @augments et2_inputWidget
*/
-var et2_date = (function(){ "use strict"; return et2_inputWidget.extend(
-{
- attributes: {
- "value": {
- "type": "any"
- },
- "type": {
- "ignore": false
- },
- "blur": {
- "name": "Placeholder",
- "type": "string",
- "default": "",
- "description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text."
- },
- "data_format": {
- "ignore": true,
- "description": "Date/Time format. Can be set as an options to date widget",
- "default": ''
- },
- year_range: {
- name: "Year range",
- type: "string",
- default: "c-10:c+10",
- description: "The range of years displayed in the year drop-down: either relative to today's year (\"-nn:+nn\"), relative to the currently selected year (\"c-nn:c+nn\"), absolute (\"nnnn:nnnn\"), or combinations of these formats (\"nnnn:-nn\"). Note that this option only affects what appears in the drop-down, to restrict which dates may be selected use the min and/or max options."
- },
- min: {
- "name": "Minimum",
- "type": "any",
- "default": et2_no_init,
- "description": 'Minimum allowed date. Multiple types supported:\
+var et2_date = /** @class */ (function (_super) {
+ __extends(et2_date, _super);
+ /**
+ * Constructor
+ */
+ function et2_date(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_core_DOMWidget_1.et2_DOMWidget._attributes, _child || {})) || this;
+ _this.legacyOptions = ["data_format"];
+ _this.input_date = null;
+ _this.is_mobile = false;
+ _this.date = new Date();
+ _this.date.setUTCHours(0);
+ _this.date.setMinutes(0);
+ _this.date.setSeconds(0);
+ _this.createInputWidget();
+ return _this;
+ }
+ et2_date.prototype.createInputWidget = function () {
+ this.span = jQuery(document.createElement(this.options.inline ? 'div' : "span")).addClass("et2_date");
+ this.input_date = jQuery(document.createElement(this.options.inline ? "div" : "input"));
+ if (this.options.blur)
+ this.input_date.attr('placeholder', this.egw().lang(this.options.blur));
+ this.input_date.addClass("et2_date").attr("type", "text")
+ .attr("size", 7) // strlen("10:00pm")=7
+ .appendTo(this.span);
+ this.setDOMNode(this.span[0]);
+ // inline calendar is not existing in html5, so allways use datepicker instead
+ this.is_mobile = egwIsMobile() && !this.options.inline;
+ if (this.is_mobile) {
+ this.dateFormat = 'yy-mm-dd';
+ this.timeFormat = 'HH:mm';
+ switch (this.getType()) {
+ case 'date':
+ this.input_date.attr('type', 'date');
+ break;
+ case 'date-time':
+ this.input_date.attr('type', 'datetime-local');
+ break;
+ case 'date-timeonly':
+ this.input_date.addClass("et2_time");
+ this.input_date.attr('type', 'time');
+ break;
+ }
+ }
+ else {
+ this.dateFormat = this.egw().dateTimeFormat(this.egw().preference("dateformat"));
+ this.timeFormat = this.egw().preference("timeformat") == 12 ? "h:mmtt" : "HH:mm";
+ // jQuery-UI date picker
+ if (this.getType() != 'date-timeonly') {
+ this.egw().calendar(this.input_date, this.getType() == "date-time");
+ }
+ else {
+ this.input_date.addClass("et2_time");
+ this.egw().time(this.input_date);
+ }
+ // Avoid collision of datepicker dialog with input field
+ var widget = this;
+ this.input_date.datepicker('option', 'beforeShow', function (input, inst) {
+ var cal = inst.dpDiv;
+ setTimeout(function () {
+ var $input = jQuery(input);
+ var inputOffset = $input.offset();
+ // position the datepicker in freespace zone
+ // avoid datepicker calendar collision with input field
+ if (cal.height() + inputOffset.top > window.innerHeight) {
+ cal.position({
+ my: "left center",
+ at: 'right bottom',
+ collision: 'flip fit',
+ of: input
+ });
+ }
+ // Add tooltip to Today/Now button
+ jQuery('[data-handler="today"]', cal).attr('title', widget.getType() == 'date' ? egw.lang('Today') : egw.lang('Now'));
+ }, 0);
+ })
+ .datepicker('option', 'onClose', function (dateText, inst) {
+ // Lose focus, avoids an issue with focus
+ // not allowing datepicker to re-open
+ inst.input.blur();
+ });
+ }
+ // Update internal value when changed
+ var self = this;
+ this.input_date.bind('change', function (e) {
+ self.set_value(this.value);
+ return false;
+ });
+ // Framewok skips nulls, but null needs to be processed here
+ if (this.options.value == null) {
+ this.set_value(null);
+ }
+ };
+ et2_date.prototype.set_type = function (_type) {
+ if (_type != this.getType()) {
+ _super.prototype.setType.call(this, _type);
+ this.createInputWidget();
+ }
+ };
+ /**
+ * Dynamic disable or enable datepicker
+ *
+ * @param {boolean} _ro
+ */
+ et2_date.prototype.set_readonly = function (_ro) {
+ if (this.input_date && !this.input_date.attr('disabled') != !_ro) {
+ this.input_date.attr('disabled', !_ro ? 0 : 1)
+ .datepicker('option', 'disabled', !!_ro);
+ }
+ };
+ /**
+ * Set (full) year of current date
+ *
+ * @param {number} _value 4-digit year
+ */
+ et2_date.prototype.set_year = function (_value) {
+ this.date.setUTCFullYear(_value);
+ this.set_value(this.date);
+ };
+ /**
+ * Set month (1..12) of current date
+ *
+ * @param {number} _value 1..12
+ */
+ et2_date.prototype.set_month = function (_value) {
+ this.date.setUTCMonth(_value - 1);
+ this.set_value(this.date);
+ };
+ /**
+ * Set day of current date
+ *
+ * @param {number} _value 1..31
+ */
+ et2_date.prototype.set_date = function (_value) {
+ this.date.setUTCDate(_value);
+ this.set_value(this.date);
+ };
+ /**
+ * Set hour (0..23) of current date
+ *
+ * @param {number} _value 0..23
+ */
+ et2_date.prototype.set_hours = function (_value) {
+ this.date.setUTCHours(_value);
+ this.set_value(this.date);
+ };
+ /**
+ * Set minute (0..59) of current date
+ *
+ * @param {number} _value 0..59
+ */
+ et2_date.prototype.set_minutes = function (_value) {
+ this.date.setUTCMinutes(_value);
+ this.set_value(this.date);
+ };
+ /**
+ * Get (full) year of current date
+ *
+ * @return {number|null} 4-digit year or null for empty
+ */
+ et2_date.prototype.get_year = function () {
+ return this.input_date.val() == "" ? null : this.date.getUTCFullYear();
+ };
+ /**
+ * Get month (1..12) of current date
+ *
+ * @return {number|null} 1..12 or null for empty
+ */
+ et2_date.prototype.get_month = function () {
+ return this.input_date.val() == "" ? null : this.date.getUTCMonth() + 1;
+ };
+ /**
+ * Get day of current date
+ *
+ * @return {number|null} 1..31 or null for empty
+ */
+ et2_date.prototype.get_date = function () {
+ return this.input_date.val() == "" ? null : this.date.getUTCDate();
+ };
+ /**
+ * Get hour (0..23) of current date
+ *
+ * @return {number|null} 0..23 or null for empty
+ */
+ et2_date.prototype.get_hours = function () {
+ return this.input_date.val() == "" ? null : this.date.getUTCHours();
+ };
+ /**
+ * Get minute (0..59) of current date
+ *
+ * @return {number|null} 0..59 or null for empty
+ */
+ et2_date.prototype.get_minutes = function () {
+ return this.input_date.val() == "" ? null : this.date.getUTCMinutes();
+ };
+ /**
+ * Get timestamp
+ *
+ * You can use set_value to set a timestamp.
+ *
+ * @return {number|null} timestamp (seconds since 1970-01-01)
+ */
+ et2_date.prototype.get_time = function () {
+ return this.input_date.val() == "" ? null : this.date.getTime();
+ };
+ /**
+ * The range of years displayed in the year drop-down: either relative
+ * to today's year ("-nn:+nn"), relative to the currently selected year
+ * ("c-nn:c+nn"), absolute ("nnnn:nnnn"), or combinations of these formats
+ * ("nnnn:-nn"). Note that this option only affects what appears in the
+ * drop-down, to restrict which dates may be selected use the min_date
+ * and/or max_date options.
+ * @param {string} _value
+ */
+ et2_date.prototype.set_year_range = function (_value) {
+ if (this.input_date && this.getType() == 'date' && !this.is_mobile) {
+ this.input_date.datepicker('option', 'yearRange', _value);
+ }
+ this.options.year_range = _value;
+ };
+ /**
+ * Set the minimum allowed date
+ *
+ * The minimum selectable date. When set to null, there is no minimum.
+ * Multiple types supported:
+ * Date: A date object containing the minimum date.
+ * Number: A number of days from today. For example 2 represents two days
+ * from today and -1 represents yesterday.
+ * String: A string in the format defined by the dateFormat option, or a
+ * relative date. Relative dates must contain value and period pairs;
+ * valid periods are "y" for years, "m" for months, "w" for weeks, and
+ * "d" for days. For example, "+1m +7d" represents one month and seven
+ * days from today.
+ * @param {Date|Number|String} _value
+ */
+ et2_date.prototype.set_min = function (_value) {
+ if (this.input_date) {
+ if (this.is_mobile) {
+ this.input_date.attr('min', this._relativeDate(_value));
+ }
+ else {
+ // Check for full timestamp
+ if (typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/)) {
+ _value = new Date(_value);
+ // Add timezone offset back in, or formatDate will lose those hours
+ var formatDate = new Date(_value.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
+ if (this.getType() == 'date') {
+ _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
+ }
+ }
+ this.input_date.datepicker('option', 'minDate', _value);
+ }
+ }
+ this.options.min = _value;
+ };
+ /**
+ * Convert non html5 min or max attributes described above to timestamps
+ *
+ * @param {string|Date} _value
+ */
+ et2_date.prototype._relativeDate = function (_value) {
+ if (typeof _value == 'string' && _value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/))
+ return _value;
+ // @ts-ignore
+ return jQuery.datepicker._determineDate(jQuery.datepicker, _value, this.date).toJSON();
+ };
+ /**
+ * Set the maximum allowed date
+ *
+ * The maximum selectable date. When set to null, there is no maximum.
+ * Multiple types supported:
+ * Date: A date object containing the maximum date.
+ * Number: A number of days from today. For example 2 represents two days
+ * from today and -1 represents yesterday.
+ * String: A string in the format defined by the dateFormat option, or a
+ * relative date. Relative dates must contain value and period pairs;
+ * valid periods are "y" for years, "m" for months, "w" for weeks, and
+ * "d" for days. For example, "+1m +7d" represents one month and seven
+ * days from today.
+ * @param {Date|Number|String} _value
+ */
+ et2_date.prototype.set_max = function (_value) {
+ if (this.input_date) {
+ if (this.is_mobile) {
+ this.input_date.attr('max', this._relativeDate(_value));
+ }
+ else {
+ // Check for full timestamp
+ if (typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/)) {
+ _value = new Date(_value);
+ // Add timezone offset back in, or formatDate will lose those hours
+ var formatDate = new Date(_value.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
+ if (this.getType() == 'date') {
+ _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
+ }
+ }
+ this.input_date.datepicker('option', 'maxDate', _value);
+ }
+ }
+ this.options.max = _value;
+ };
+ /**
+ * Setting date
+ *
+ * @param {string|number|Date} _value supported are the following formats:
+ * - Date object with usertime as UTC value
+ * - string like Date.toJSON()
+ * - string or number with timestamp in usertime like server-side uses it
+ * - string starting with + or - to add/substract given number of seconds from current value, "+600" to add 10 minutes
+ */
+ et2_date.prototype.set_value = function (_value) {
+ var old_value = this._oldValue;
+ if (_value === null || _value === "" || _value === undefined ||
+ // allow 0 as empty-value for date and date-time widgets, as that is used a lot eg. in InfoLog
+ _value == 0 && (this.getType() == 'date-time' || this.getType() == 'date')) {
+ if (this.input_date) {
+ this.input_date.val("");
+ }
+ if (this._oldValue !== et2_no_init && old_value !== _value) {
+ this.change(this.input_date);
+ }
+ this._oldValue = _value;
+ return;
+ }
+ // timestamp in usertime, convert to 'Y-m-d\\TH:i:s\\Z', as we do on server-side with equivalent of PHP date()
+ if (typeof _value == 'number' || typeof _value == 'string' && !isNaN(_value) && _value[0] != '+' && _value[0] != '-') {
+ _value = date('Y-m-d\\TH:i:s\\Z', _value);
+ }
+ // Check for full timestamp
+ if (typeof _value == 'string' && _value.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2})|)$/)) {
+ _value = new Date(_value);
+ }
+ // Handle just time as a string in the form H:i
+ if (typeof _value == 'string' && isNaN(_value)) {
+ try {
+ // silently fix skiped minutes or times with just one digit, as parser is quite pedantic ;-)
+ var fix_reg = new RegExp((this.getType() == "date-timeonly" ? '^' : ' ') + '([0-9]+)(:[0-9]*)?( ?(a|p)m?)?$', 'i');
+ var matches = _value.match(fix_reg);
+ if (matches && (matches[1].length < 2 || matches[2] === undefined || matches[2].length < 3 ||
+ matches[3] && matches[3] != 'am' && matches[3] != 'pm')) {
+ if (matches[1].length < 2 && !matches[3])
+ matches[1] = '0' + matches[1];
+ if (matches[2] === undefined)
+ matches[2] = ':00';
+ while (matches[2].length < 3)
+ matches[2] = ':0' + matches[2].substr(1);
+ _value = _value.replace(fix_reg, (this.getType() == "date-timeonly" ? '' : ' ') + matches[1] + matches[2] + matches[3]);
+ if (matches[4] !== undefined)
+ matches[3] = matches[4].toLowerCase() == 'a' ? 'am' : 'pm';
+ }
+ switch (this.getType()) {
+ case "date-timeonly":
+ // @ts-ignore
+ var parsed = jQuery.datepicker.parseTime(this.timeFormat, _value);
+ if (!parsed) // parseTime returns false
+ {
+ this.set_validation_error(this.egw().lang("'%1' has an invalid format !!!", _value));
+ return;
+ }
+ this.set_validation_error(false);
+ // this.date is on current date, changing it in get_value() to 1970-01-01, gives a time-difference, if we are currently on DST
+ this.date.setDate(1);
+ this.date.setMonth(0);
+ this.date.setFullYear(1970);
+ // Avoid javascript timezone offset, hour is in 'user time'
+ this.date.setUTCHours(parsed.hour);
+ this.date.setMinutes(parsed.minute);
+ if (this.input_date.val() != _value) {
+ this.input_date.val(_value);
+ // @ts-ignore
+ this.input_date.timepicker('setTime', _value);
+ if (this._oldValue !== et2_no_init) {
+ this.change(this.input_date);
+ }
+ }
+ this._oldValue = this.date.toJSON();
+ return;
+ default:
+ // Parse customfields's date with storage data_format to date object
+ // Or generally any date widgets with fixed date/time format
+ if (this.id.match(/^#/g) && this.options.value == _value || (this.options.data_format && this.options.value == _value)) {
+ switch (this.getType()) {
+ case 'date':
+ var parsed = jQuery.datepicker.parseDate(this.egw().dateTimeFormat(this.options.data_format), _value);
+ break;
+ case 'date-time':
+ var DTformat = this.options.data_format.split(' ');
+ // @ts-ignore
+ var parsed = jQuery.datepicker.parseDateTime(this.egw().dateTimeFormat(DTformat[0]), this.egw().dateTimeFormat(DTformat[1]), _value);
+ }
+ }
+ else // Parse other date widgets date with timepicker date/time format to date onject
+ {
+ // @ts-ignore
+ var parsed = jQuery.datepicker.parseDateTime(this.dateFormat, this.timeFormat, _value.replace('T', ' '));
+ if (!parsed) {
+ this.set_validation_error(this.egw().lang("%1' han an invalid format !!!", _value));
+ return;
+ }
+ }
+ // Update local variable, but remove the timezone offset that
+ // javascript adds when we parse
+ if (parsed) {
+ this.date = new Date(parsed.valueOf() - parsed.getTimezoneOffset() * 60000);
+ }
+ this.set_validation_error(false);
+ }
+ }
+ // catch exception from unparsable date and display it empty instead
+ catch (e) {
+ return this.set_value(null);
+ }
+ }
+ else if (typeof _value == 'object' && _value.date) {
+ this.date = _value.date;
+ }
+ else if (typeof _value == 'object' && _value.valueOf) {
+ this.date = _value;
+ }
+ else
+ // string starting with + or - --> add/substract number of seconds from current value
+ {
+ this.date.setTime(this.date.getTime() + 1000 * parseInt(_value));
+ }
+ // Update input - popups do, but framework doesn't
+ _value = '';
+ // Add timezone offset back in, or formatDate will lose those hours
+ var formatDate = new Date(this.date.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
+ if (this.getType() != 'date-timeonly') {
+ _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
+ }
+ if (this.getType() != 'date') {
+ if (this.getType() != 'date-timeonly')
+ _value += this.is_mobile ? 'T' : ' ';
+ // @ts-ignore
+ _value += jQuery.datepicker.formatTime(this.timeFormat, {
+ hour: formatDate.getHours(),
+ minute: formatDate.getMinutes(),
+ seconds: 0,
+ timezone: 0
+ });
+ }
+ if (this.options.inline) {
+ this.input_date.datepicker("setDate", formatDate);
+ }
+ else {
+ this.input_date.val(_value);
+ }
+ if (this._oldValue !== et2_no_init && old_value != this.getValue()) {
+ this.change(this.input_date);
+ }
+ this._oldValue = _value;
+ };
+ et2_date.prototype.getValue = function () {
+ if (this.input_date.val() == "") {
+ // User blanked the box
+ return null;
+ }
+ // date-timeonly returns just the seconds, without any date!
+ if (this.getType() == 'date-timeonly') {
+ this.date.setUTCDate(1);
+ this.date.setUTCMonth(0);
+ this.date.setUTCFullYear(1970);
+ }
+ else if (this.getType() == 'date') {
+ this.date.setUTCHours(0);
+ this.date.setUTCMinutes(0);
+ }
+ // Convert to timestamp - no seconds
+ this.date.setSeconds(0, 0);
+ return (this.date && typeof this.date.toJSON != 'undefined' && this.date.toJSON()) ? this.date.toJSON().replace(/\.\d{3}Z$/, 'Z') : this.date;
+ };
+ et2_date._attributes = {
+ "value": {
+ "type": "any"
+ },
+ "type": {
+ "ignore": false
+ },
+ "blur": {
+ "name": "Placeholder",
+ "type": "string",
+ "default": "",
+ "description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text."
+ },
+ "data_format": {
+ "ignore": true,
+ "description": "Date/Time format. Can be set as an options to date widget",
+ "default": ''
+ },
+ year_range: {
+ name: "Year range",
+ type: "string",
+ default: "c-10:c+10",
+ description: "The range of years displayed in the year drop-down: either relative to today's year (\"-nn:+nn\"), relative to the currently selected year (\"c-nn:c+nn\"), absolute (\"nnnn:nnnn\"), or combinations of these formats (\"nnnn:-nn\"). Note that this option only affects what appears in the drop-down, to restrict which dates may be selected use the min and/or max options."
+ },
+ min: {
+ "name": "Minimum",
+ "type": "any",
+ "default": et2_no_init,
+ "description": 'Minimum allowed date. Multiple types supported:\
Date: A date object containing the minimum date.\
Number: A number of days from today. For example 2 represents two days from today and -1 represents yesterday.\
String: A string in the user\'s date format, or a relative date. Relative dates must contain value and period pairs; valid periods are "y" for years, "m" for months, "w" for weeks, and "d" for days. For example, "+1m +7d" represents one month and seven days from today.'
- },
- max: {
- "name": "Maximum",
- "type": "any",
- "default": et2_no_init,
- "description": 'Maximum allowed date. Multiple types supported:\
+ },
+ max: {
+ "name": "Maximum",
+ "type": "any",
+ "default": et2_no_init,
+ "description": 'Maximum allowed date. Multiple types supported:\
Date: A date object containing the maximum date.\
Number: A number of days from today. For example 2 represents two days from today and -1 represents yesterday.\
String: A string in the user\'s date format, or a relative date. Relative dates must contain value and period pairs; valid periods are "y" for years, "m" for months, "w" for weeks, and "d" for days. For example, "+1m +7d" represents one month and seven days from today.'
- },
- inline: {
- "name": "Inline",
- "type": "boolean",
- "default": false,
- "description": "Instead of an input field with a popup calendar, the calendar is displayed inline, with no input field"
- }
- },
-
- legacyOptions: ["data_format"],
-
- /**
- * Constructor
- *
- * @memberOf et2_date
- */
- init: function()
- {
- this._super.apply(this, arguments);
-
- this.date = new Date();
- this.date.setUTCHours(0);
- this.date.setMinutes(0);
- this.date.setSeconds(0);
- this.input = null;
-
- this.createInputWidget();
- },
-
- createInputWidget: function()
- {
- this.span = jQuery(document.createElement(this.options.inline ? 'div' : "span")).addClass("et2_date");
-
- this.input_date = jQuery(document.createElement(this.options.inline ? "div" : "input"));
- if (this.options.blur) this.input_date.attr('placeholder', this.egw().lang(this.options.blur));
- this.input_date.addClass("et2_date").attr("type", "text")
- .attr("size", 7) // strlen("10:00pm")=7
- .appendTo(this.span);
-
- this.setDOMNode(this.span[0]);
-
- // inline calendar is not existing in html5, so allways use datepicker instead
- this.is_mobile = egwIsMobile() && !this.options.inline;
-
- if (this.is_mobile)
- {
- this.dateFormat = 'yy-mm-dd';
- this.timeFormat = 'HH:mm';
- switch(this._type)
- {
- case 'date':
- this.input_date.attr('type', 'date');
- break;
- case 'date-time':
- this.input_date.attr('type', 'datetime-local');
- break;
- case 'date-timeonly':
- this.input_date.addClass("et2_time");
- this.input_date.attr('type', 'time');
- break;
- }
- }
- else
- {
- this.dateFormat = this.egw().dateTimeFormat(this.egw().preference("dateformat"));
- this.timeFormat = this.egw().preference("timeformat") == 12 ? "h:mmtt" : "HH:mm";
- // jQuery-UI date picker
- if(this._type != 'date-timeonly')
- {
- this.egw().calendar(this.input_date, this._type == "date-time");
- }
- else
- {
- this.input_date.addClass("et2_time");
- this.egw().time(this.input_date);
- }
-
- // Avoid collision of datepicker dialog with input field
- var widget = this;
- this.input_date.datepicker('option', 'beforeShow', function(input, inst){
- var cal = inst.dpDiv;
- setTimeout(function () {
- var $input = jQuery(input);
- var inputOffset = $input.offset();
- // position the datepicker in freespace zone
- // avoid datepicker calendar collision with input field
- if (cal.height() + inputOffset.top > window.innerHeight)
- {
- cal.position({
- my: "left center",
- at: 'right bottom',
- collision: 'flip fit',
- of: input
- });
- }
- // Add tooltip to Today/Now button
- jQuery('[data-handler="today"]',cal).attr('title',
- widget._type == 'date' ? egw.lang('Today') : egw.lang('Now')
- );
-
- },0);
- })
- .datepicker('option','onClose', function(dateText, inst) {
- // Lose focus, avoids an issue with focus
- // not allowing datepicker to re-open
- inst.input.blur();
- });
- }
-
- // Update internal value when changed
- var self = this;
- this.input_date.bind('change', function(e){
- self.set_value(this.value);
- return false;
- });
-
- // Framewok skips nulls, but null needs to be processed here
- if(this.options.value == null)
- {
- this.set_value(null);
- }
- },
-
- set_type: function(_type) {
- if(_type != this._type)
- {
- this._type = _type;
- this.createInputWidget();
- }
- },
-
- /**
- * Dynamic disable or enable datepicker
- *
- * @param {boolean} _ro
- */
- set_readonly: function(_ro)
- {
- if (this.input_date && !this.input_date.attr('disabled') != !_ro)
- {
- this.input_date.attr('disabled', !!_ro)
- .datepicker('option', 'disabled', !!_ro);
- }
- },
-
- /**
- * Set (full) year of current date
- *
- * @param {number} _value 4-digit year
- */
- set_year: function(_value)
- {
- this.date.setUTCFullYear(_value);
- this.set_value(this.date);
- },
- /**
- * Set month (1..12) of current date
- *
- * @param {number} _value 1..12
- */
- set_month: function(_value)
- {
- this.date.setUTCMonth(_value-1);
- this.set_value(this.date);
- },
- /**
- * Set day of current date
- *
- * @param {number} _value 1..31
- */
- set_date: function(_value)
- {
- this.date.setUTCDate(_value);
- this.set_value(this.date);
- },
- /**
- * Set hour (0..23) of current date
- *
- * @param {number} _value 0..23
- */
- set_hours: function(_value)
- {
- this.date.setUTCHours(_value);
- this.set_value(this.date);
- },
- /**
- * Set minute (0..59) of current date
- *
- * @param {number} _value 0..59
- */
- set_minutes: function(_value)
- {
- this.date.setUTCMinutes(_value);
- this.set_value(this.date);
- },
- /**
- * Get (full) year of current date
- *
- * @return {number|null} 4-digit year or null for empty
- */
- get_year: function()
- {
- return this.input_date.val() == "" ? null : this.date.getUTCFullYear();
- },
- /**
- * Get month (1..12) of current date
- *
- * @return {number|null} 1..12 or null for empty
- */
- get_month: function()
- {
- return this.input_date.val() == "" ? null : this.date.getUTCMonth()+1;
- },
- /**
- * Get day of current date
- *
- * @return {number|null} 1..31 or null for empty
- */
- get_date: function()
- {
- return this.input_date.val() == "" ? null : this.date.getUTCDate();
- },
- /**
- * Get hour (0..23) of current date
- *
- * @return {number|null} 0..23 or null for empty
- */
- get_hours: function()
- {
- return this.input_date.val() == "" ? null : this.date.getUTCHours();
- },
- /**
- * Get minute (0..59) of current date
- *
- * @return {number|null} 0..59 or null for empty
- */
- get_minutes: function()
- {
- return this.input_date.val() == "" ? null : this.date.getUTCMinutes();
- },
- /**
- * Get timestamp
- *
- * You can use set_value to set a timestamp.
- *
- * @return {number|null} timestamp (seconds since 1970-01-01)
- */
- get_time: function()
- {
- return this.input_date.val() == "" ? null : this.date.getTime();
- },
-
- /**
- * The range of years displayed in the year drop-down: either relative
- * to today's year ("-nn:+nn"), relative to the currently selected year
- * ("c-nn:c+nn"), absolute ("nnnn:nnnn"), or combinations of these formats
- * ("nnnn:-nn"). Note that this option only affects what appears in the
- * drop-down, to restrict which dates may be selected use the min_date
- * and/or max_date options.
- * @param {string} _value
- */
- set_year_range: function(_value)
- {
- if(this.input_date && this._type == 'date' && !this.is_mobile)
- {
- this.input_date.datepicker('option','yearRange',_value);
- }
- this.options.year_range = _value;
- },
-
- /**
- * Set the minimum allowed date
- *
- * The minimum selectable date. When set to null, there is no minimum.
- * Multiple types supported:
- * Date: A date object containing the minimum date.
- * Number: A number of days from today. For example 2 represents two days
- * from today and -1 represents yesterday.
- * String: A string in the format defined by the dateFormat option, or a
- * relative date. Relative dates must contain value and period pairs;
- * valid periods are "y" for years, "m" for months, "w" for weeks, and
- * "d" for days. For example, "+1m +7d" represents one month and seven
- * days from today.
- * @param {Date|Number|String} _value
- */
- set_min: function(_value)
- {
- if(this.input_date)
- {
- if (this.is_mobile)
- {
- this.input_date.attr('min', this._relativeDate(_value));
- }
- else
- {
- // Check for full timestamp
- if(typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/))
- {
- _value = new Date(_value);
- // Add timezone offset back in, or formatDate will lose those hours
- var formatDate = new Date(_value.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
- if(this._type == 'date')
- {
- _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
- }
- }
- this.input_date.datepicker('option','minDate',_value);
- }
- }
- this.options.min = _value;
- },
-
- /**
- * Convert non html5 min or max attributes described above to timestamps
- *
- * @param {string|Date} _value
- */
- _relativeDate: function(_value)
- {
- if (typeof _value == 'string' && _value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/)) return _value;
-
- return jQuery.datepicker._determineDate(jQuery.datepicker, _value, this.date).toJSON();
- },
-
- /**
- * Set the maximum allowed date
- *
- * The maximum selectable date. When set to null, there is no maximum.
- * Multiple types supported:
- * Date: A date object containing the maximum date.
- * Number: A number of days from today. For example 2 represents two days
- * from today and -1 represents yesterday.
- * String: A string in the format defined by the dateFormat option, or a
- * relative date. Relative dates must contain value and period pairs;
- * valid periods are "y" for years, "m" for months, "w" for weeks, and
- * "d" for days. For example, "+1m +7d" represents one month and seven
- * days from today.
- * @param {Date|Number|String} _value
- */
- set_max: function(_value)
- {
- if(this.input_date)
- {
- if (this.is_mobile)
- {
- this.input_date.attr('max', this._relativeDate(_value));
- }
- else
- {
- // Check for full timestamp
- if(typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/))
- {
- _value = new Date(_value);
- // Add timezone offset back in, or formatDate will lose those hours
- var formatDate = new Date(_value.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
- if(this._type == 'date')
- {
- _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
- }
- }
- this.input_date.datepicker('option','maxDate',_value);
- }
- }
- this.options.max = _value;
- },
-
- /**
- * Setting date
- *
- * @param {string|number|Date} _value supported are the following formats:
- * - Date object with usertime as UTC value
- * - string like Date.toJSON()
- * - string or number with timestamp in usertime like server-side uses it
- * - string starting with + or - to add/substract given number of seconds from current value, "+600" to add 10 minutes
- */
- set_value: function(_value)
- {
- var old_value = this._oldValue;
- if(_value === null || _value === "" || _value === undefined ||
- // allow 0 as empty-value for date and date-time widgets, as that is used a lot eg. in InfoLog
- _value == 0 && (this._type == 'date-time' || this._type == 'date'))
- {
- if(this.input_date)
- {
- this.input_date.val("");
- }
- if(this._oldValue !== et2_no_init && old_value !== _value)
- {
- this.change(this.input_date);
- }
- this._oldValue = _value;
- return;
- }
-
- // timestamp in usertime, convert to 'Y-m-d\\TH:i:s\\Z', as we do on server-side with equivalent of PHP date()
- if (typeof _value == 'number' || typeof _value == 'string' && !isNaN(_value) && _value[0] != '+' && _value[0] != '-')
- {
- _value = date('Y-m-d\\TH:i:s\\Z', _value);
- }
- // Check for full timestamp
- if(typeof _value == 'string' && _value.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2})|)$/))
- {
- _value = new Date(_value);
- }
- // Handle just time as a string in the form H:i
- if(typeof _value == 'string' && isNaN(_value))
- {
- try {
- // silently fix skiped minutes or times with just one digit, as parser is quite pedantic ;-)
- var fix_reg = new RegExp((this._type == "date-timeonly"?'^':' ')+'([0-9]+)(:[0-9]*)?( ?(a|p)m?)?$','i');
- var matches = _value.match(fix_reg);
- if (matches && (matches[1].length < 2 || matches[2] === undefined || matches[2].length < 3 ||
- matches[3] && matches[3] != 'am' && matches[3] != 'pm'))
- {
- if (matches[1].length < 2 && !matches[3]) matches[1] = '0'+matches[1];
- if (matches[2] === undefined) matches[2] = ':00';
- while (matches[2].length < 3) matches[2] = ':0'+matches[2].substr(1);
- _value = _value.replace(fix_reg, (this._type == "date-timeonly"?'':' ')+matches[1]+matches[2]+matches[3]);
- if (matches[4] !== undefined) matches[3] = matches[4].toLowerCase() == 'a' ? 'am' : 'pm';
- }
- switch(this._type)
- {
- case "date-timeonly":
- var parsed = jQuery.datepicker.parseTime(this.timeFormat, _value);
- if (!parsed) // parseTime returns false
- {
- this.set_validation_error(this.egw().lang("'%1' has an invalid format !!!",_value));
- return;
- }
- this.set_validation_error(false);
- // this.date is on current date, changing it in get_value() to 1970-01-01, gives a time-difference, if we are currently on DST
- this.date.setDate(1);
- this.date.setMonth(0);
- this.date.setFullYear(1970);
- // Avoid javascript timezone offset, hour is in 'user time'
- this.date.setUTCHours(parsed.hour);
- this.date.setMinutes(parsed.minute);
- if(this.input_date.val() != _value)
- {
- this.input_date.val(_value);
- this.input_date.timepicker('setTime',_value);
- if (this._oldValue !== et2_no_init)
- {
- this.change(this.input_date);
- }
- }
- this._oldValue = this.date.toJSON();
- return;
- default:
- // Parse customfields's date with storage data_format to date object
- // Or generally any date widgets with fixed date/time format
- if (this.id.match(/^#/g) && this.options.value == _value || (this.options.data_format && this.options.value == _value))
- {
- switch (this._type)
- {
- case 'date':
- var parsed = jQuery.datepicker.parseDate(this.egw().dateTimeFormat(this.options.data_format), _value);
- break;
- case 'date-time':
- var DTformat = this.options.data_format.split(' ');
- var parsed = jQuery.datepicker.parseDateTime(this.egw().dateTimeFormat(DTformat[0]),this.egw().dateTimeFormat(DTformat[1]), _value);
- }
- }
- else // Parse other date widgets date with timepicker date/time format to date onject
- {
- var parsed = jQuery.datepicker.parseDateTime(this.dateFormat,
- this.timeFormat, _value.replace('T', ' '));
- if(!parsed)
- {
- this.set_validation_error(this.egw().lang("%1' han an invalid format !!!",_value));
- return;
- }
- }
- // Update local variable, but remove the timezone offset that
- // javascript adds when we parse
- if(parsed)
- {
- this.date = new Date(parsed.valueOf() - parsed.getTimezoneOffset() * 60000);
- }
-
- this.set_validation_error(false);
- }
- }
- // catch exception from unparsable date and display it empty instead
- catch(e) {
- return this.set_value(null);
- }
- } else if (typeof _value == 'object' && _value.date) {
- this.date = _value.date;
- } else if (typeof _value == 'object' && _value.valueOf) {
- this.date = _value;
- } else
- // string starting with + or - --> add/substract number of seconds from current value
- {
- this.date.setTime(this.date.getTime()+1000*parseInt(_value));
- }
-
- // Update input - popups do, but framework doesn't
- _value = '';
- // Add timezone offset back in, or formatDate will lose those hours
- var formatDate = new Date(this.date.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
- if(this._type != 'date-timeonly')
- {
- _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
- }
- if(this._type != 'date')
- {
- if(this._type != 'date-timeonly') _value += this.is_mobile ? 'T' : ' ';
-
- _value += jQuery.datepicker.formatTime(this.timeFormat, {
- hour: formatDate.getHours(),
- minute: formatDate.getMinutes(),
- seconds: 0,
- timezone: 0
- });
- }
- if(this.options.inline )
- {
- this.input_date.datepicker("setDate",formatDate);
- }
- else
- {
- this.input_date.val(_value);
- }
- if(this._oldValue !== et2_no_init && old_value != this.getValue())
- {
- this.change(this.input_date);
- }
- this._oldValue = _value;
- },
-
- getValue: function() {
- if(this.input_date.val() == "")
- {
- // User blanked the box
- return null;
- }
- // date-timeonly returns just the seconds, without any date!
- if (this._type == 'date-timeonly')
- {
- this.date.setUTCDate(1);
- this.date.setUTCMonth(0);
- this.date.setUTCFullYear(1970);
- }
- else if (this._type == 'date')
- {
- this.date.setUTCHours(0);
- this.date.setUTCMinutes(0);
- }
-
- // Convert to timestamp - no seconds
- this.date.setSeconds(0,0);
- return (this.date && typeof this.date.toJSON != 'undefined' && this.date.toJSON())?this.date.toJSON().replace(/\.\d{3}Z$/, 'Z'):this.date;
- }
-});}).call(this);
-et2_register_widget(et2_date, ["date", "date-time", "date-timeonly"]);
-
+ },
+ inline: {
+ "name": "Inline",
+ "type": "boolean",
+ "default": false,
+ "description": "Instead of an input field with a popup calendar, the calendar is displayed inline, with no input field"
+ }
+ };
+ return et2_date;
+}(et2_core_inputWidget_1.et2_inputWidget));
+et2_core_widget_1.et2_register_widget(et2_date, ["date", "date-time", "date-timeonly"]);
/**
- * @augments et2_date
+ * Class which implements the "date-duration" XET-Tag
*/
-var et2_date_duration = (function(){ "use strict"; return et2_date.extend(
-{
- attributes: {
- "data_format": {
- "name": "Data format",
- "default": "m",
- "type": "string",
- "description": "Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (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."
- },
- "percent_allowed": {
- "name": "Percent allowed",
- "default": false,
- "type": "boolean",
- "description": "Allows to enter a percentage."
- },
- "hours_per_day": {
- "name": "Hours per day",
- "default": 8,
- "type": "integer",
- "description": "Number of hours in a day, for converting between hours and (working) days."
- },
- "empty_not_0": {
- "name": "0 or empty",
- "default": false,
- "type": "boolean",
- "description": "Should the widget differ between 0 and empty, which get then returned as NULL"
- },
- "short_labels": {
- "name": "Short labels",
- "default": false,
- "type": "boolean",
- "description": "use d/h/m instead of day/hour/minute"
- },
- "step" : {
- "name": "Step limit",
- "default": 'any',
- "type": "string",
- "description": "Works with the min and max attributes to limit the increments at which a numeric or date-time value can be set."
- }
- },
-
- legacyOptions: ["data_format","display_format", "hours_per_day", "empty_not_0", "short_labels"],
-
- time_formats: {"d":"d","h":"h","m":"m"},
-
- /**
- * Constructor
- *
- * @memberOf et2_date_duration
- */
- init: function() {
- this._super.apply(this, arguments);
-
- this.input = null;
-
- // Legacy option put percent in with display format
- if(this.options.display_format.indexOf("%") != -1)
- {
- this.options.percent_allowed = true;
- this.options.display_format = this.options.display_format.replace("%","");
- }
-
- // Clean formats
- this.options.display_format = this.options.display_format.replace(/[^dhm]/,'');
- if(!this.options.display_format)
- {
- 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")
- },
- this.createInputWidget();
- },
-
- createInputWidget: function() {
- // Create nodes
- this.node = jQuery(document.createElement("span"))
- .addClass('et2_date_duration');
- this.duration = jQuery(document.createElement("input"))
- .addClass('et2_date_duration')
- .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);
-
- if(this.options.display_format.length > 1)
- {
- this.format = jQuery(document.createElement("select"))
- .addClass('et2_date_duration');
- this.node.append(this.format);
-
- for(var i = 0; i < this.options.display_format.length; i++) {
- this.format.append("");
- }
- }
- else if (this.time_formats[this.options.display_format])
- {
- this.format = jQuery(""+this.time_formats[this.options.display_format]+"").appendTo(this.node);
- }
- else
- {
- this.format = jQuery(""+this.time_formats["m"]+"").appendTo(this.node);
- }
- var self = this;
- // seems the 'invalid' event doesn't work in all browsers, eg. FF therefore
- // we use focusout event to check the valifdity of input right after user
- // enters the value.
- this.duration.on('focusout', function(){if(!self.duration[0].checkValidity()) return self.duration.change();});
- },
-
- /**
- * Clientside validation
- *
- * @param {array} _messages
- */
- isValid: function(_messages)
- {
- var ok = true;
- // if we have a html5 validation error, show it, as this.input.val() will be empty!
- if (this.duration && this.duration[0] && this.duration[0].validationMessage && !this.duration[0].validity.stepMismatch)
- {
- _messages.push(this.duration[0].validationMessage);
- ok = false;
- }
- return this._super.apply(this, arguments) && ok;
- },
-
- attachToDOM: function() {
- var node = this.getInputNode();
- if (node)
- {
- jQuery(node).bind("change.et2_inputWidget", this, function(e) {
- e.data.change(this);
- });
- }
- et2_DOMWidget.prototype.attachToDOM.apply(this, arguments);
- },
- getDOMNode: function() {
- return this.node[0];
- },
- getInputNode: function() {
- return this.duration[0];
- },
-
- /**
- * Use id on node, same as DOMWidget
- *
- * @param {string} _value id to set
- */
- set_id: function(_value) {
- this.id = _value;
-
- var node = this.getDOMNode(this);
- if (node)
- {
- if (_value != "")
- {
- node.setAttribute("id", this.getInstanceManager().uniqueId+'_'+this.id);
- }
- else
- {
- node.removeAttribute("id");
- }
- }
- },
- set_value: function(_value) {
- this.options.value = _value;
-
- var display = this._convert_to_display(_value);
-
- // Set display
- if(this.duration[0].nodeName == "INPUT")
- {
- this.duration.val(display.value);
- }
- else
- {
- this.duration.text(display.value + " ");
- }
-
- // Set unit as figured for display
- if(display.unit != this.options.display_format)
- {
- if(this.format && this.format.children().length > 1) {
- jQuery("option[value='"+display.unit+"']",this.format).attr('selected','selected');
- }
- else
- {
- this.format.text(display.unit ? this.time_formats[display.unit] : '');
- }
- }
- },
-
- /**
- * Converts the value in data format into value in display format.
- *
- * @param _value int/float Data in data format
- *
- * @return Object {value: Value in display format, unit: unit for display}
- */
- _convert_to_display: function(_value) {
- if (_value)
- {
- // Put value into minutes for further processing
- switch(this.options.data_format)
- {
- case 'd':
- _value *= this.options.hours_per_day;
- // fall-through
- case 'h':
- _value *= 60;
- break;
- }
- }
-
- // Figure out best unit for display
- var _unit = this.options.display_format == "d" ? "d" : "h";
- if (this.options.display_format.indexOf('m') > -1 && _value && _value < 60)
- {
- _unit = 'm';
- }
- else if (this.options.display_format.indexOf('d') > -1 && _value >= 60*this.options.hours_per_day)
- {
- _unit = 'd';
- }
- _value = this.options.empty_not_0 && _value === '' || !this.options.empty_not_0 && !_value ? '' :
- (_unit == 'm' ? parseInt( _value) : (Math.round((_value / 60.0 / (_unit == 'd' ? this.options.hours_per_day : 1))*100)/100));
-
- if(_value === '') _unit = '';
-
- // use decimal separator from user prefs
- var format = this.egw().preference('number_format');
- var sep = format ? format[0] : '.';
- if (typeof _value == 'string' && format && sep && sep != '.')
- {
- _value = _value.replace('.',sep);
- }
-
- return {value: _value, unit:_unit};
- },
-
- /**
- * Change displayed value into storage value and return
- */
- getValue: function() {
- var value = this.duration.val().replace(',', '.');
- if(value === '')
- {
- return this.options.empty_not_0 ? '' : 0;
- }
- // Put value into minutes for further processing
- switch(this.format && this.format.val() ? this.format.val() : this.options.display_format)
- {
- case 'd':
- value *= this.options.hours_per_day;
- // fall-through
- case 'h':
- value *= 60;
- break;
- }
- // Minutes should be an integer. Floating point math.
- value = Math.round(value);
-
- switch(this.options.data_format)
- {
- case 'd':
- value /= this.options.hours_per_day;
- // fall-through
- case 'h':
- value /= 60.0;
- break;
- }
- return value;
- }
-});}).call(this);
-et2_register_widget(et2_date_duration, ["date-duration"]);
-
+var et2_date_duration = /** @class */ (function (_super) {
+ __extends(et2_date_duration, _super);
+ /**
+ * Constructor
+ */
+ function et2_date_duration(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_core_DOMWidget_1.et2_DOMWidget._attributes, _child || {})) || this;
+ _this.legacyOptions = ["data_format", "display_format", "hours_per_day", "empty_not_0", "short_labels"];
+ // Legacy option put percent in with display format
+ if (_this.options.display_format.indexOf("%") != -1) {
+ _this.options.percent_allowed = true;
+ _this.options.display_format = _this.options.display_format.replace("%", "");
+ }
+ // Clean formats
+ _this.options.display_format = _this.options.display_format.replace(/[^dhm]/, '');
+ 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")
+ },
+ _this.createInputWidget();
+ return _this;
+ }
+ et2_date_duration.prototype.createInputWidget = function () {
+ // Create nodes
+ this.node = jQuery(document.createElement("span"))
+ .addClass('et2_date_duration');
+ this.duration = jQuery(document.createElement("input"))
+ .addClass('et2_date_duration')
+ .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);
+ if (this.options.display_format.length > 1) {
+ this.format = jQuery(document.createElement("select"))
+ .addClass('et2_date_duration');
+ this.node.append(this.format);
+ for (var i = 0; i < this.options.display_format.length; i++) {
+ this.format.append("");
+ }
+ }
+ else if (this.time_formats[this.options.display_format]) {
+ this.format = jQuery("" + this.time_formats[this.options.display_format] + "").appendTo(this.node);
+ }
+ else {
+ this.format = jQuery("" + this.time_formats["m"] + "").appendTo(this.node);
+ }
+ var self = this;
+ // seems the 'invalid' event doesn't work in all browsers, eg. FF therefore
+ // we use focusout event to check the valifdity of input right after user
+ // enters the value.
+ this.duration.on('focusout', function () {
+ if (!self.duration[0].checkValidity())
+ return self.duration.change();
+ });
+ };
+ /**
+ * Clientside validation
+ *
+ * @param {array} _messages
+ */
+ et2_date_duration.prototype.isValid = function (_messages) {
+ var ok = true;
+ // if we have a html5 validation error, show it, as this.input.val() will be empty!
+ if (this.duration && this.duration[0] &&
+ this.duration[0].validationMessage &&
+ !this.duration[0].validity.stepMismatch) {
+ _messages.push(this.duration[0].validationMessage);
+ ok = false;
+ }
+ return _super.prototype.isValid.call(this, _messages) && ok;
+ };
+ et2_date_duration.prototype.attachToDOM = function () {
+ var node = this.getInputNode();
+ if (node) {
+ jQuery(node).bind("change.et2_inputWidget", this, function (e) {
+ e.data.change(this);
+ });
+ }
+ return et2_core_DOMWidget_1.et2_DOMWidget.prototype.attachToDOM.apply(this, arguments);
+ };
+ et2_date_duration.prototype.getDOMNode = function () {
+ return this.node[0];
+ };
+ et2_date_duration.prototype.getInputNode = function () {
+ return this.duration[0];
+ };
+ /**
+ * Use id on node, same as DOMWidget
+ *
+ * @param {string} _value id to set
+ */
+ et2_date_duration.prototype.set_id = function (_value) {
+ this.id = _value;
+ var node = this.getDOMNode();
+ if (node) {
+ if (_value != "") {
+ node.setAttribute("id", this.getInstanceManager().uniqueId + '_' + this.id);
+ }
+ else {
+ node.removeAttribute("id");
+ }
+ }
+ };
+ et2_date_duration.prototype.set_value = function (_value) {
+ this.options.value = _value;
+ var display = this._convert_to_display(_value);
+ // Set display
+ if (this.duration[0].nodeName == "INPUT") {
+ this.duration.val(display.value);
+ }
+ else {
+ this.duration.text(display.value + " ");
+ }
+ // Set unit as figured for display
+ if (display.unit != this.options.display_format) {
+ if (this.format && this.format.children().length > 1) {
+ jQuery("option[value='" + display.unit + "']", this.format).attr('selected', 'selected');
+ }
+ else {
+ this.format.text(display.unit ? this.time_formats[display.unit] : '');
+ }
+ }
+ };
+ /**
+ * Converts the value in data format into value in display format.
+ *
+ * @param _value int/float Data in data format
+ *
+ * @return Object {value: Value in display format, unit: unit for display}
+ */
+ et2_date_duration.prototype._convert_to_display = function (_value) {
+ if (_value) {
+ // Put value into minutes for further processing
+ switch (this.options.data_format) {
+ case 'd':
+ _value *= this.options.hours_per_day;
+ // fall-through
+ case 'h':
+ _value *= 60;
+ break;
+ }
+ }
+ // Figure out best unit for display
+ var _unit = this.options.display_format == "d" ? "d" : "h";
+ if (this.options.display_format.indexOf('m') > -1 && _value && _value < 60) {
+ _unit = 'm';
+ }
+ else if (this.options.display_format.indexOf('d') > -1 && _value >= 60 * this.options.hours_per_day) {
+ _unit = 'd';
+ }
+ _value = this.options.empty_not_0 && _value === '' || !this.options.empty_not_0 && !_value ? '' :
+ (_unit == 'm' ? parseInt(_value) : (Math.round((_value / 60.0 / (_unit == 'd' ? this.options.hours_per_day : 1)) * 100) / 100));
+ if (_value === '')
+ _unit = '';
+ // use decimal separator from user prefs
+ var format = this.egw().preference('number_format');
+ var sep = format ? format[0] : '.';
+ if (typeof _value == 'string' && format && sep && sep != '.') {
+ _value = _value.replace('.', sep);
+ }
+ return { value: _value, unit: _unit };
+ };
+ /**
+ * Change displayed value into storage value and return
+ */
+ et2_date_duration.prototype.getValue = function () {
+ var value = this.duration.val().replace(',', '.');
+ if (value === '') {
+ return this.options.empty_not_0 ? '' : 0;
+ }
+ // Put value into minutes for further processing
+ switch (this.format && this.format.val() ? this.format.val() : this.options.display_format) {
+ case 'd':
+ value *= this.options.hours_per_day;
+ // fall-through
+ case 'h':
+ value *= 60;
+ break;
+ }
+ // Minutes should be an integer. Floating point math.
+ value = Math.round(value);
+ switch (this.options.data_format) {
+ case 'd':
+ value /= this.options.hours_per_day;
+ // fall-through
+ case 'h':
+ value /= 60.0;
+ break;
+ }
+ return value;
+ };
+ et2_date_duration._attributes = {
+ "data_format": {
+ "name": "Data format",
+ "default": "m",
+ "type": "string",
+ "description": "Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (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."
+ },
+ "percent_allowed": {
+ "name": "Percent allowed",
+ "default": false,
+ "type": "boolean",
+ "description": "Allows to enter a percentage."
+ },
+ "hours_per_day": {
+ "name": "Hours per day",
+ "default": 8,
+ "type": "integer",
+ "description": "Number of hours in a day, for converting between hours and (working) days."
+ },
+ "empty_not_0": {
+ "name": "0 or empty",
+ "default": false,
+ "type": "boolean",
+ "description": "Should the widget differ between 0 and empty, which get then returned as NULL"
+ },
+ "short_labels": {
+ "name": "Short labels",
+ "default": false,
+ "type": "boolean",
+ "description": "use d/h/m instead of day/hour/minute"
+ },
+ "step": {
+ "name": "Step limit",
+ "default": 'any',
+ "type": "string",
+ "description": "Works with the min and max attributes to limit the increments at which a numeric or date-time value can be set."
+ }
+ };
+ return et2_date_duration;
+}(et2_date));
+et2_core_widget_1.et2_register_widget(et2_date_duration, ["date-duration"]);
/**
- * @augments et2_date_duration
+ * r/o date-duration
*/
-var et2_date_duration_ro = (function(){ "use strict"; return et2_date_duration.extend([et2_IDetachedDOM],
-{
- /**
- * @memberOf et2_date_duration_ro
- */
- createInputWidget: function() {
- this.node = jQuery(document.createElement("span"));
- this.duration = jQuery(document.createElement("span")).appendTo(this.node);
- this.format = jQuery(document.createElement("span")).appendTo(this.node);
- },
-
- /**
- * Code for implementing et2_IDetachedDOM
- * Fast-clonable read-only widget that only deals with DOM nodes, not the widget tree
- */
-
- /**
- * Build a list of attributes which can be set when working in the
- * "detached" mode in the _attrs array which is provided
- * by the calling code.
- *
- * @param {array} _attrs array to add further attributes to
- */
- getDetachedAttributes: function(_attrs) {
- _attrs.push("value");
- },
-
- /**
- * Returns an array of DOM nodes. The (relativly) same DOM-Nodes have to be
- * passed to the "setDetachedAttributes" function in the same order.
- *
- * @return {array}
- */
- getDetachedNodes: function() {
- return [this.duration[0], this.format[0]];
- },
-
- /**
- * Sets the given associative attribute->value array and applies the
- * attributes to the given DOM-Node.
- *
- * @param _nodes is an array of nodes which has to be in the same order as
- * the nodes returned by "getDetachedNodes"
- * @param _values is an associative array which contains a subset of attributes
- * returned by the "getDetachedAttributes" function and sets them to the
- * given values.
- */
- setDetachedAttributes: function(_nodes, _values) {
- for(var i = 0; i < _nodes.length; i++) {
- // Clear the node
- for (var j = _nodes[i].childNodes.length - 1; j >= 0; j--)
- {
- _nodes[i].removeChild(_nodes[i].childNodes[j]);
- }
- }
- if(typeof _values.value !== 'undefined')
- {
- _values.value = parseFloat(_values.value);
- }
- if(_values.value)
- {
- var display = this._convert_to_display(_values.value);
- _nodes[0].appendChild(document.createTextNode(display.value));
- _nodes[1].appendChild(document.createTextNode(display.unit));
- }
- }
-
-});}).call(this);
-et2_register_widget(et2_date_duration_ro, ["date-duration_ro"]);
-
+var et2_date_duration_ro = /** @class */ (function (_super) {
+ __extends(et2_date_duration_ro, _super);
+ function et2_date_duration_ro() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ et2_date_duration_ro.prototype.createInputWidget = function () {
+ this.node = jQuery(document.createElement("span"));
+ this.duration = jQuery(document.createElement("span")).appendTo(this.node);
+ this.format = jQuery(document.createElement("span")).appendTo(this.node);
+ };
+ /**
+ * Code for implementing et2_IDetachedDOM
+ * Fast-clonable read-only widget that only deals with DOM nodes, not the widget tree
+ */
+ /**
+ * Build a list of attributes which can be set when working in the
+ * "detached" mode in the _attrs array which is provided
+ * by the calling code.
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ et2_date_duration_ro.prototype.getDetachedAttributes = function (_attrs) {
+ _attrs.push("value");
+ };
+ /**
+ * Returns an array of DOM nodes. The (relativly) same DOM-Nodes have to be
+ * passed to the "setDetachedAttributes" function in the same order.
+ *
+ * @return {array}
+ */
+ et2_date_duration_ro.prototype.getDetachedNodes = function () {
+ return [this.duration[0], this.format[0]];
+ };
+ /**
+ * Sets the given associative attribute->value array and applies the
+ * attributes to the given DOM-Node.
+ *
+ * @param _nodes is an array of nodes which has to be in the same order as
+ * the nodes returned by "getDetachedNodes"
+ * @param _values is an associative array which contains a subset of attributes
+ * returned by the "getDetachedAttributes" function and sets them to the
+ * given values.
+ */
+ et2_date_duration_ro.prototype.setDetachedAttributes = function (_nodes, _values) {
+ for (var i = 0; i < _nodes.length; i++) {
+ // Clear the node
+ for (var j = _nodes[i].childNodes.length - 1; j >= 0; j--) {
+ _nodes[i].removeChild(_nodes[i].childNodes[j]);
+ }
+ }
+ if (typeof _values.value !== 'undefined') {
+ _values.value = parseFloat(_values.value);
+ }
+ if (_values.value) {
+ var display = this._convert_to_display(_values.value);
+ _nodes[0].appendChild(document.createTextNode(display.value));
+ _nodes[1].appendChild(document.createTextNode(display.unit));
+ }
+ };
+ return et2_date_duration_ro;
+}(et2_date_duration));
+et2_core_widget_1.et2_register_widget(et2_date_duration_ro, ["date-duration_ro"]);
/**
* et2_date_ro is the readonly implementation of some date widget.
- * @augments et2_valueWidget
*/
-var et2_date_ro = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDetachedDOM],
-{
- /**
- * Ignore all more advanced attributes.
- */
- attributes: {
- "value": {
- "type": "string"
- },
- "type": {
- "ignore": false
- },
- "data_format": {
- "ignore": true,
- "description": "Format data is in. This is not used client-side because it's always a timestamp client side."
- },
- min: {ignore: true},
- max: {ignore: true},
- year_range: {ignore: true}
- },
-
- legacyOptions: ["data_format"],
-
- /**
- * Internal container for working easily with dates
- */
- date: new Date(),
-
- /**
- * Constructor
- *
- * @memberOf et2_date_ro
- */
- init: function() {
- this._super.apply(this, arguments);
- this._labelContainer = jQuery(document.createElement("label"))
- .addClass("et2_label");
- this.value = "";
- this.span = jQuery(document.createElement(this._type == "date-since" || this._type == "date-time_today" ? "span" : "time"))
- .addClass("et2_date_ro et2_label")
- .appendTo(this._labelContainer);
-
- this.setDOMNode(this._labelContainer[0]);
- },
-
- set_value: function(_value) {
- if(typeof _value == 'undefined') _value = 0;
-
- this.value = _value;
-
- if(_value == 0 || _value == null)
- {
- this.span.attr("datetime", "").text("");
- return;
- }
-
- if(typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/))
- {
- this.date = new Date(_value);
- this.date = new Date(this.date.valueOf() + (this.date.getTimezoneOffset()*60*1000));
- }
- else if(typeof _value == 'string' && (isNaN(_value) ||
- this.options.data_format && this.options.data_format.substr(0,3) === 'Ymd'))
- {
- try {
- // data_format is not handled server-side for custom-fields in nextmatch
- // as parseDateTime requires a separate between date and time, we fix the value here
- switch (this.options.data_format)
- {
- case 'Ymd':
- case 'YmdHi':
- case 'YmdHis':
- _value = _value.substr(0, 4)+'-'+_value.substr(4, 2)+'-'+_value.substr(6, 2)+' '+
- (_value.substr(8, 2) || '00')+':'+(_value.substr(10, 2) || '00')+':'+(_value.substr(12, 2) || '00');
- break;
- }
- // parseDateTime to handle string PHP: DateTime local date/time format
- var parsed = (typeof jQuery.datepicker.parseDateTime("yy-mm-dd","hh:mm:ss", _value) !='undefined')?
- jQuery.datepicker.parseDateTime("yy-mm-dd","hh:mm:ss", _value):
- jQuery.datepicker.parseDateTime(this.egw().preference('dateformat'),this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', _value);
- }
- // display unparsable dates as empty
- catch(e) {
- this.span.attr("datetime", "").text("");
- return;
- }
- var text = new Date(parsed);
-
- // Update local variable, but remove the timezone offset that javascript adds
- if(parsed)
- {
- this.date = new Date(text.valueOf() - (text.getTimezoneOffset()*60*1000));
- }
-
- // JS dates use milliseconds
- this.date.setTime(text.valueOf());
- }
- else
- {
- // _value is timestamp in usertime, ready to be used with date() function identical to PHP date()
- this.date = _value;
- }
- var display = this.date.toString();
-
- switch(this._type) {
- case "time_or_date":
- case "date-time_today":
- // Today - just the time
- if(date('Y-m-d', this.date) == date('Y-m-d'))
- {
- display = date(this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', this.date);
- }
- else if (this._type === "time_or_date")
- {
- display = date(this.egw().preference('dateformat'), this.date);
- }
- // Before today - date and time
- else
- {
- display = date(this.egw().preference('dateformat') + " " +
- (this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a'), this.date);
- }
- break;
- case "date":
- display = date(this.egw().preference('dateformat'), this.date);
- break;
- case "date-timeonly":
- display = date(this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', this.date);
- break;
- case "date-time":
- display = date(this.egw().preference('dateformat') + " " +
- (this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a'), this.date);
- break;
- case "date-since":
- var unit2label = {
- 'Y': 'years',
- 'm': 'month',
- 'd': 'days',
- 'H': 'hours',
- 'i': 'minutes',
- 's': 'seconds'
- };
- var unit2s = {
- 'Y': 31536000,
- 'm': 2628000,
- 'd': 86400,
- 'H': 3600,
- 'i': 60,
- 's': 1
- };
- var d = new Date();
- var diff = Math.round(d.valueOf() / 1000) - Math.round(this.date.valueOf()/1000);
- display = '';
-
- for(var unit in unit2s)
- {
- var unit_s = unit2s[unit];
- if (diff >= unit_s || unit == 's')
- {
- display = Math.round(diff/unit_s,1)+' '+this.egw().lang(unit2label[unit]);
- break;
- }
- }
- break;
- }
- this.span.attr("datetime", date("Y-m-d H:i:s",this.date)).text(display);
- },
-
- set_label: function(label)
- {
- // Remove current label
- this._labelContainer.contents()
- .filter(function(){ return this.nodeType == 3; }).remove();
-
- var parts = et2_csvSplit(label, 2, "%s");
- this._labelContainer.prepend(parts[0]);
- this._labelContainer.append(parts[1]);
- this.label = label;
-
- // add class if label is empty
- this._labelContainer.toggleClass('et2_label_empty', !label || !parts[0]);
- },
-
- /**
- * Creates a list of attributes which can be set when working in the
- * "detached" mode. The result is stored in the _attrs array which is provided
- * by the calling code.
- *
- * @param {array} _attrs array to add further attributes to
- */
- getDetachedAttributes: function(_attrs) {
- _attrs.push("label", "value","class");
- },
-
- /**
- * Returns an array of DOM nodes. The (relatively) same DOM-Nodes have to be
- * passed to the "setDetachedAttributes" function in the same order.
- *
- * @return {array}
- */
- getDetachedNodes: function() {
- return [this._labelContainer[0], this.span[0]];
- },
-
- /**
- * Sets the given associative attribute->value array and applies the
- * attributes to the given DOM-Node.
- *
- * @param _nodes is an array of nodes which have to be in the same order as
- * the nodes returned by "getDetachedNodes"
- * @param _values is an associative array which contains a subset of attributes
- * returned by the "getDetachedAttributes" function and sets them to the
- * given values.
- */
- setDetachedAttributes: function(_nodes, _values) {
- this._labelContainer = jQuery(_nodes[0]);
- this.span = jQuery(_nodes[1]);
-
- this.set_value(_values["value"]);
- if(_values["label"])
- {
- this.set_label(_values["label"]);
- }
- if(_values["class"])
- {
- this.span.addClass(_values["class"]);
- }
- }
-});}).call(this);
-et2_register_widget(et2_date_ro, ["date_ro", "date-time_ro", "date-since", "date-time_today", "time_or_date", "date-timeonly_ro"]);
-
-
+var et2_date_ro = /** @class */ (function (_super) {
+ __extends(et2_date_ro, _super);
+ /**
+ * Constructor
+ */
+ function et2_date_ro(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_core_DOMWidget_1.et2_DOMWidget._attributes, _child || {})) || this;
+ /**
+ * Internal container for working easily with dates
+ */
+ _this.date = new Date();
+ _this.value = "";
+ _this._labelContainer = jQuery(document.createElement("label"))
+ .addClass("et2_label");
+ _this.span = jQuery(document.createElement(_this.getType() == "date-since" || _this.getType() == "date-time_today" ? "span" : "time"))
+ .addClass("et2_date_ro et2_label")
+ .appendTo(_this._labelContainer);
+ _this.setDOMNode(_this._labelContainer[0]);
+ return _this;
+ }
+ et2_date_ro.prototype.set_value = function (_value) {
+ if (typeof _value == 'undefined')
+ _value = 0;
+ this.value = _value;
+ if (_value == 0 || _value == null) {
+ this.span.attr("datetime", "").text("");
+ return;
+ }
+ if (typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/)) {
+ this.date = new Date(_value);
+ this.date = new Date(this.date.valueOf() + (this.date.getTimezoneOffset() * 60 * 1000));
+ }
+ else if (typeof _value == 'string' && (isNaN(_value) ||
+ this.options.data_format && this.options.data_format.substr(0, 3) === 'Ymd')) {
+ try {
+ // data_format is not handled server-side for custom-fields in nextmatch
+ // as parseDateTime requires a separate between date and time, we fix the value here
+ switch (this.options.data_format) {
+ case 'Ymd':
+ case 'YmdHi':
+ case 'YmdHis':
+ _value = _value.substr(0, 4) + '-' + _value.substr(4, 2) + '-' + _value.substr(6, 2) + ' ' +
+ (_value.substr(8, 2) || '00') + ':' + (_value.substr(10, 2) || '00') + ':' + (_value.substr(12, 2) || '00');
+ break;
+ }
+ // parseDateTime to handle string PHP: DateTime local date/time format
+ // @ts-ignore
+ var parsed = (typeof jQuery.datepicker.parseDateTime("yy-mm-dd", "hh:mm:ss", _value) != 'undefined') ?
+ // @ts-ignore
+ jQuery.datepicker.parseDateTime("yy-mm-dd", "hh:mm:ss", _value) :
+ // @ts-ignore
+ jQuery.datepicker.parseDateTime(this.egw().preference('dateformat'), this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', _value);
+ }
+ // display unparsable dates as empty
+ catch (e) {
+ this.span.attr("datetime", "").text("");
+ return;
+ }
+ var text = new Date(parsed);
+ // Update local variable, but remove the timezone offset that javascript adds
+ if (parsed) {
+ this.date = new Date(text.valueOf() - (text.getTimezoneOffset() * 60 * 1000));
+ }
+ // JS dates use milliseconds
+ this.date.setTime(text.valueOf());
+ }
+ else {
+ // _value is timestamp in usertime, ready to be used with date() function identical to PHP date()
+ this.date = _value;
+ }
+ var display = this.date.toString();
+ switch (this.getType()) {
+ case "time_or_date":
+ case "date-time_today":
+ // Today - just the time
+ if (date('Y-m-d', this.date) == date('Y-m-d')) {
+ display = date(this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', this.date);
+ }
+ else if (this.getType() === "time_or_date") {
+ display = date(this.egw().preference('dateformat'), this.date);
+ }
+ // Before today - date and time
+ else {
+ display = date(this.egw().preference('dateformat') + " " +
+ (this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a'), this.date);
+ }
+ break;
+ case "date":
+ display = date(this.egw().preference('dateformat'), this.date);
+ break;
+ case "date-timeonly":
+ display = date(this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', this.date);
+ break;
+ case "date-time":
+ display = date(this.egw().preference('dateformat') + " " +
+ (this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a'), this.date);
+ break;
+ case "date-since":
+ var unit2label = {
+ 'Y': 'years',
+ 'm': 'month',
+ 'd': 'days',
+ 'H': 'hours',
+ 'i': 'minutes',
+ 's': 'seconds'
+ };
+ var unit2s = {
+ 'Y': 31536000,
+ 'm': 2628000,
+ 'd': 86400,
+ 'H': 3600,
+ 'i': 60,
+ 's': 1
+ };
+ var d = new Date();
+ var diff = Math.round(d.valueOf() / 1000) - Math.round(this.date.valueOf() / 1000);
+ display = '';
+ for (var unit in unit2s) {
+ var unit_s = unit2s[unit];
+ if (diff >= unit_s || unit == 's') {
+ display = Math.round(diff / unit_s) + ' ' + this.egw().lang(unit2label[unit]);
+ break;
+ }
+ }
+ break;
+ }
+ this.span.attr("datetime", date("Y-m-d H:i:s", this.date)).text(display);
+ };
+ et2_date_ro.prototype.set_label = function (label) {
+ // Remove current label
+ this._labelContainer.contents()
+ .filter(function () { return this.nodeType == 3; }).remove();
+ var parts = et2_csvSplit(label, 2, "%s");
+ this._labelContainer.prepend(parts[0]);
+ this._labelContainer.append(parts[1]);
+ this.label = label;
+ // add class if label is empty
+ this._labelContainer.toggleClass('et2_label_empty', !label || !parts[0]);
+ };
+ /**
+ * Creates a list of attributes which can be set when working in the
+ * "detached" mode. The result is stored in the _attrs array which is provided
+ * by the calling code.
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ et2_date_ro.prototype.getDetachedAttributes = function (_attrs) {
+ _attrs.push("label", "value", "class");
+ };
+ /**
+ * Returns an array of DOM nodes. The (relatively) same DOM-Nodes have to be
+ * passed to the "setDetachedAttributes" function in the same order.
+ *
+ * @return {array}
+ */
+ et2_date_ro.prototype.getDetachedNodes = function () {
+ return [this._labelContainer[0], this.span[0]];
+ };
+ /**
+ * Sets the given associative attribute->value array and applies the
+ * attributes to the given DOM-Node.
+ *
+ * @param _nodes is an array of nodes which have to be in the same order as
+ * the nodes returned by "getDetachedNodes"
+ * @param _values is an associative array which contains a subset of attributes
+ * returned by the "getDetachedAttributes" function and sets them to the
+ * given values.
+ */
+ et2_date_ro.prototype.setDetachedAttributes = function (_nodes, _values) {
+ this._labelContainer = jQuery(_nodes[0]);
+ this.span = jQuery(_nodes[1]);
+ this.set_value(_values["value"]);
+ if (_values["label"]) {
+ this.set_label(_values["label"]);
+ }
+ if (_values["class"]) {
+ this.span.addClass(_values["class"]);
+ }
+ };
+ /**
+ * Ignore all more advanced attributes.
+ */
+ et2_date_ro._attributes = {
+ "value": {
+ "type": "string"
+ },
+ "type": {
+ "ignore": false
+ },
+ "data_format": {
+ "ignore": true,
+ "description": "Format data is in. This is not used client-side because it's always a timestamp client side."
+ },
+ min: { ignore: true },
+ max: { ignore: true },
+ year_range: { ignore: true }
+ };
+ return et2_date_ro;
+}(et2_core_valueWidget_1.et2_valueWidget));
+et2_core_widget_1.et2_register_widget(et2_date_ro, ["date_ro", "date-time_ro", "date-since", "date-time_today", "time_or_date", "date-timeonly_ro"]);
/**
* Widget for selecting a date range
- *
- * @augments et2_inputWidget
*/
-var et2_date_range = (function(){ "use strict"; return et2_inputWidget.extend({
- attributes: {
- value: {
- "type": "any",
- "description": "An object with keys 'from' and 'to' for absolute ranges, or a relative range string"
- },
- relative: {
- name: 'Relative',
- type: 'boolean',
- description: 'Is the date range relative (this week) or absolute (2016-02-15 - 2016-02-21). This will affect the value returned.'
- }
- },
-
- /**
- * Constructor
- *
- * @memberOf et2_number
- */
- init: function init() {
- this._super.apply(this, arguments);
-
- this.div = jQuery(document.createElement('div'))
- .attr({ class:'et2_date_range'});
-
- this.from = null;
- this.to = null;
- this.select = null;
-
- // Set domid
- this.set_id(this.id);
-
- this.setDOMNode(this.div[0]);
- this._createWidget();
-
- this.set_relative(this.options.relative || false);
- },
-
- _createWidget: function createInputWidget() {
- var widget = this;
-
- this.from = et2_createWidget('date',{
- id: this.id+'[from]',
- blur: egw.lang('From'),
- onchange: function() { widget.to.set_min(widget.from.getValue()); }
- },this);
- this.to = et2_createWidget('date',{
- id: this.id+'[to]',
- blur: egw.lang('To'),
- onchange: function() {widget.from.set_max(widget.to.getValue()); }
- },this);
- this.select = et2_createWidget('select',{
- id: this.id+'[relative]',
- select_options: et2_date_range.relative_dates,
- empty_label: this.options.blur || 'All'
- },this);
- this.select.loadingFinished();
- },
-
- /**
- * Function which allows iterating over the complete widget tree.
- * Overridden here to avoid problems with children when getting value
- *
- * @param _callback is the function which should be called for each widget
- * @param _context is the context in which the function should be executed
- * @param _type is an optional parameter which specifies a class/interface
- * the elements have to be instanceOf.
- */
- iterateOver: function(_callback, _context, _type) {
- if (typeof _type == "undefined")
- {
- _type = et2_widget;
- }
-
- if (this.isInTree() && this.instanceOf(_type))
- {
- _callback.call(_context, this);
- }
- },
-
- /**
- * Toggles relative or absolute dates
- *
- * @param {boolean} _value
- */
- set_relative: function set_relative(_value)
- {
- this.options.relative = _value;
- if(this.options.relative)
- {
- jQuery(this.from.getDOMNode()).hide();
- jQuery(this.to.getDOMNode()).hide();
- }
- else
- {
- jQuery(this.select.getDOMNode()).hide();
- }
- },
-
- set_value: function set_value(value)
- {
- if(!value || typeof value == 'null')
- {
- this.select.set_value('');
- this.from.set_value(null);
- this.to.set_value(null);
- }
-
- // Relative
- if(value && typeof value === 'string')
- {
- this._set_relative_value(value);
-
- }
- else if(value && typeof value.from === 'undefined' && value[0])
- {
- value = {
- from: value[0],
- to: value[1] || new Date().valueOf()/1000
- };
- }
- else if (value && value.from && value.to)
- {
- this.from.set_value(value.from);
- this.to.set_value(value.to);
- }
- },
-
- getValue: function getValue()
- {
- return this.options.relative ?
- this.select.getValue() :
- { from: this.from.getValue(), to: this.to.getValue() };
- },
-
- _set_relative_value: function(_value)
- {
- if(this.options.relative)
- {
- jQuery(this.select.getDOMNode()).show();
- }
- // Show description
- this.select.set_value(_value);
-
- var tempDate = new Date();
- var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(),0,-tempDate.getTimezoneOffset(),0);
-
- // Use strings to avoid references
- this.from.set_value(today.toJSON());
- this.to.set_value(today.toJSON());
-
- var relative = null;
- for(var index in et2_date_range.relative_dates)
- {
- if(et2_date_range.relative_dates[index].value === _value)
- {
- relative = et2_date_range.relative_dates[index];
- break;
- }
- }
- if(relative)
- {
- var dates = ["from","to"];
- var value = today.toJSON();
- for(var i = 0; i < dates.length; i++)
- {
- var date = dates[i];
- if(typeof relative[date] == "function")
- {
- value = relative[date](new Date(value));
- }
- else
- {
- value = this[date]._relativeDate(relative[date]);
- }
- this[date].set_value(value);
- }
- }
- }
-});}).call(this);
-et2_register_widget(et2_date_range, ["date-range"]);
-// Static part of the date range class
-jQuery.extend(et2_date_range,
-{
- // Class Constants
- relative_dates: [
- // Start and end are relative offsets, see et2_date.set_min()
- // or Date objects
- {
- value: 'Today',
- label: 'Today',
- from: function(date) {return date;},
- to: function(date) {return date;}
- },
- {
- label: 'Yesterday',
- value: 'Yesterday',
- from: function(date) {
- date.setUTCDate(date.getUTCDate() - 1);
- return date;
- },
- to: ''
- },
- {
- label: 'This week',
- value: 'This week',
- from: function(date) {return egw.week_start(date);},
- to: function(date) {
- date.setUTCDate(date.getUTCDate() + 6);
- return date;
- }
- },
- {
- label: 'Last week',
- value: 'Last week',
- from: function(date) {
- var d = egw.week_start(date);
- d.setUTCDate(d.getUTCDate() - 7);
- return d;
- },
- to: function(date) {
- date.setUTCDate(date.getUTCDate() + 6);
- return date;
- }
- },
- {
- label: 'This month',
- value: 'This month',
- from: function(date)
- {
- date.setUTCDate(1);
- return date;
- },
- to: function(date)
- {
- date.setUTCMonth(date.getUTCMonth()+1);
- date.setUTCDate(0);
- return date;
- }
- },
- {
- label: 'Last month',
- value: 'Last month',
- from: function(date)
- {
- date.setUTCMonth(date.getUTCMonth() - 1);
- date.setUTCDate(1);
- return date;
- },
- to: function(date)
- {
- date.setUTCMonth(date.getUTCMonth()+1);
- date.setUTCDate(0);
- return date;
- }
- },
- {
- label: 'Last 3 months',
- value: 'Last 3 months',
- from: function(date)
- {
- date.setUTCMonth(date.getUTCMonth() - 2);
- date.setUTCDate(1);
- return date;
- },
- to: function(date)
- {
- date.setUTCMonth(date.getUTCMonth()+3);
- date.setUTCDate(0);
- return date;
- }
- },
- /*
- 'This quarter'=> array(0,0,0,0, 0,0,0,0), // Just a marker, needs special handling
- 'Last quarter'=> array(0,-4,0,0, 0,-4,0,0), // Just a marker
- */
- {
- label: 'This year',
- value: 'This year',
- from: function(d) {
- d.setUTCMonth(0);
- d.setUTCDate(1);
- return d;
- },
- to: function(d) {
- d.setUTCMonth(11);
- d.setUTCDate(31);
- return d;
- }
- },
- {
- label: 'Last year',
- value: 'Last year',
- from: function(d) {
- d.setUTCMonth(0);
- d.setUTCDate(1);
- d.setUTCYear(d.getUTCYear() - 1);
- return d;
- },
- to: function(d) {
- d.setUTCMonth(11);
- d.setUTCDate(31);
- d.setUTCYear(d.getUTCYear() - 1);
- return d;
- }
- }
- /* Still needed?
- '2 years ago' => array(-2,0,0,0, -1,0,0,0),
- '3 years ago' => array(-3,0,0,0, -2,0,0,0),
- */
- ]
-});
+var et2_date_range = /** @class */ (function (_super) {
+ __extends(et2_date_range, _super);
+ /**
+ * Constructor
+ */
+ function et2_date_range(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_core_DOMWidget_1.et2_DOMWidget._attributes, _child || {})) || this;
+ _this.div = jQuery(document.createElement('div'))
+ .attr({ class: 'et2_date_range' });
+ _this.from = null;
+ _this.to = null;
+ _this.select = null;
+ // Set domid
+ _this.set_id(_this.id);
+ _this.setDOMNode(_this.div[0]);
+ _this._createWidget();
+ _this.set_relative(_this.options.relative || false);
+ return _this;
+ }
+ et2_date_range.prototype._createWidget = function () {
+ var widget = this;
+ this.from = et2_core_widget_1.et2_createWidget('date', {
+ id: this.id + '[from]',
+ blur: egw.lang('From'),
+ onchange: function () { widget.to.set_min(widget.from.getValue()); }
+ }, this);
+ this.to = et2_core_widget_1.et2_createWidget('date', {
+ id: this.id + '[to]',
+ blur: egw.lang('To'),
+ onchange: function () { widget.from.set_max(widget.to.getValue()); }
+ }, this);
+ this.select = et2_core_widget_1.et2_createWidget('select', {
+ id: this.id + '[relative]',
+ select_options: et2_date_range.relative_dates,
+ empty_label: this.options.blur || 'All'
+ }, this);
+ this.select.loadingFinished();
+ };
+ /**
+ * Function which allows iterating over the complete widget tree.
+ * Overridden here to avoid problems with children when getting value
+ *
+ * @param _callback is the function which should be called for each widget
+ * @param _context is the context in which the function should be executed
+ * @param _type is an optional parameter which specifies a class/interface
+ * the elements have to be instanceOf.
+ */
+ et2_date_range.prototype.iterateOver = function (_callback, _context, _type) {
+ if (typeof _type == "undefined") {
+ _type = et2_core_widget_1.et2_widget;
+ }
+ if (this.isInTree() && this.instanceOf(_type)) {
+ _callback.call(_context, this);
+ }
+ };
+ /**
+ * Toggles relative or absolute dates
+ *
+ * @param {boolean} _value
+ */
+ et2_date_range.prototype.set_relative = function (_value) {
+ this.options.relative = _value;
+ if (this.options.relative) {
+ jQuery(this.from.getDOMNode()).hide();
+ jQuery(this.to.getDOMNode()).hide();
+ }
+ else {
+ jQuery(this.select.getDOMNode()).hide();
+ }
+ };
+ et2_date_range.prototype.set_value = function (value) {
+ // @ts-ignore
+ if (!value || typeof value == 'null') {
+ this.select.set_value('');
+ this.from.set_value(null);
+ this.to.set_value(null);
+ }
+ // Relative
+ if (value && typeof value === 'string') {
+ this._set_relative_value(value);
+ }
+ else if (value && typeof value.from === 'undefined' && value[0]) {
+ value = {
+ from: value[0],
+ to: value[1] || new Date().valueOf() / 1000
+ };
+ }
+ else if (value && value.from && value.to) {
+ this.from.set_value(value.from);
+ this.to.set_value(value.to);
+ }
+ };
+ et2_date_range.prototype.getValue = function () {
+ return this.options.relative ?
+ this.select.getValue() :
+ { from: this.from.getValue(), to: this.to.getValue() };
+ };
+ et2_date_range.prototype._set_relative_value = function (_value) {
+ if (this.options.relative) {
+ jQuery(this.select.getDOMNode()).show();
+ }
+ // Show description
+ this.select.set_value(_value);
+ var tempDate = new Date();
+ var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(), 0, -tempDate.getTimezoneOffset(), 0);
+ // Use strings to avoid references
+ this.from.set_value(today.toJSON());
+ this.to.set_value(today.toJSON());
+ var relative = null;
+ for (var index in et2_date_range.relative_dates) {
+ if (et2_date_range.relative_dates[index].value === _value) {
+ relative = et2_date_range.relative_dates[index];
+ break;
+ }
+ }
+ if (relative) {
+ var dates = ["from", "to"];
+ var value = today.toJSON();
+ for (var i = 0; i < dates.length; i++) {
+ var date = dates[i];
+ if (typeof relative[date] == "function") {
+ value = relative[date](new Date(value));
+ }
+ else {
+ value = this[date]._relativeDate(relative[date]);
+ }
+ this[date].set_value(value);
+ }
+ }
+ };
+ et2_date_range._attributes = {
+ value: {
+ "type": "any",
+ "description": "An object with keys 'from' and 'to' for absolute ranges, or a relative range string"
+ },
+ relative: {
+ name: 'Relative',
+ type: 'boolean',
+ description: 'Is the date range relative (this week) or absolute (2016-02-15 - 2016-02-21). This will affect the value returned.'
+ }
+ };
+ // Class Constants
+ et2_date_range.relative_dates = [
+ // Start and end are relative offsets, see et2_date.set_min()
+ // or Date objects
+ {
+ value: 'Today',
+ label: 'Today',
+ from: function (date) { return date; },
+ to: function (date) { return date; }
+ },
+ {
+ label: 'Yesterday',
+ value: 'Yesterday',
+ from: function (date) {
+ date.setUTCDate(date.getUTCDate() - 1);
+ return date;
+ },
+ to: ''
+ },
+ {
+ label: 'This week',
+ value: 'This week',
+ from: function (date) { return egw.week_start(date); },
+ to: function (date) {
+ date.setUTCDate(date.getUTCDate() + 6);
+ return date;
+ }
+ },
+ {
+ label: 'Last week',
+ value: 'Last week',
+ from: function (date) {
+ var d = egw.week_start(date);
+ d.setUTCDate(d.getUTCDate() - 7);
+ return d;
+ },
+ to: function (date) {
+ date.setUTCDate(date.getUTCDate() + 6);
+ return date;
+ }
+ },
+ {
+ label: 'This month',
+ value: 'This month',
+ from: function (date) {
+ date.setUTCDate(1);
+ return date;
+ },
+ to: function (date) {
+ date.setUTCMonth(date.getUTCMonth() + 1);
+ date.setUTCDate(0);
+ return date;
+ }
+ },
+ {
+ label: 'Last month',
+ value: 'Last month',
+ from: function (date) {
+ date.setUTCMonth(date.getUTCMonth() - 1);
+ date.setUTCDate(1);
+ return date;
+ },
+ to: function (date) {
+ date.setUTCMonth(date.getUTCMonth() + 1);
+ date.setUTCDate(0);
+ return date;
+ }
+ },
+ {
+ label: 'Last 3 months',
+ value: 'Last 3 months',
+ from: function (date) {
+ date.setUTCMonth(date.getUTCMonth() - 2);
+ date.setUTCDate(1);
+ return date;
+ },
+ to: function (date) {
+ date.setUTCMonth(date.getUTCMonth() + 3);
+ date.setUTCDate(0);
+ return date;
+ }
+ },
+ /*
+ 'This quarter'=> array(0,0,0,0, 0,0,0,0), // Just a marker, needs special handling
+ 'Last quarter'=> array(0,-4,0,0, 0,-4,0,0), // Just a marker
+ */
+ {
+ label: 'This year',
+ value: 'This year',
+ from: function (d) {
+ d.setUTCMonth(0);
+ d.setUTCDate(1);
+ return d;
+ },
+ to: function (d) {
+ d.setUTCMonth(11);
+ d.setUTCDate(31);
+ return d;
+ }
+ },
+ {
+ label: 'Last year',
+ value: 'Last year',
+ from: function (d) {
+ d.setUTCMonth(0);
+ d.setUTCDate(1);
+ d.setUTCYear(d.getUTCYear() - 1);
+ return d;
+ },
+ to: function (d) {
+ d.setUTCMonth(11);
+ d.setUTCDate(31);
+ d.setUTCYear(d.getUTCYear() - 1);
+ return d;
+ }
+ }
+ /* Still needed?
+ '2 years ago' => array(-2,0,0,0, -1,0,0,0),
+ '3 years ago' => array(-3,0,0,0, -2,0,0,0),
+ */
+ ];
+ return et2_date_range;
+}(et2_core_inputWidget_1.et2_inputWidget));
+et2_core_widget_1.et2_register_widget(et2_date_range, ["date-range"]);
+//# sourceMappingURL=et2_widget_date.js.map
\ No newline at end of file
diff --git a/api/js/etemplate/et2_widget_date.ts b/api/js/etemplate/et2_widget_date.ts
new file mode 100644
index 0000000000..e08d14a6b5
--- /dev/null
+++ b/api/js/etemplate/et2_widget_date.ts
@@ -0,0 +1,1601 @@
+/**
+ * EGroupware eTemplate2 - JS Date object
+ *
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @package etemplate
+ * @subpackage api
+ * @link http://www.egroupware.org
+ * @author Nathan Gray
+ * @copyright Nathan Gray 2011
+ */
+
+/*egw:uses
+ /vendor/bower-asset/jquery/dist/jquery.js;
+ /vendor/bower-asset/jquery-ui/jquery-ui.js;
+ lib/date;
+ et2_core_inputWidget;
+ et2_core_valueWidget;
+*/
+
+import './et2_core_common';
+import { ClassWithAttributes } from "./et2_core_inheritance";
+import { et2_widget, et2_createWidget, et2_register_widget, WidgetConfig } from "./et2_core_widget";
+import { et2_valueWidget } from './et2_core_valueWidget'
+import { et2_inputWidget } from './et2_core_inputWidget'
+import './et2_types';
+import {et2_DOMWidget} from "./et2_core_DOMWidget";
+
+// lib/date.js:
+declare function date (format : string, timestamp? : string | number | Date);
+
+// all calls to jQueryUI.datetimepicker as jQuery.datepicker give errors which are currently suppressed with @ts-ignore
+// adding npm package @types/jquery.ui.datetimepicker did NOT help :(
+
+/**
+ * Class which implements the "date" XET-Tag
+ *
+ * Dates are passed to the server in ISO8601 format ("Y-m-d\TH:i:sP"), and data_format is
+ * handled server-side.
+ *
+ * Widgets uses jQuery date- and time-picker for desktop browsers and
+ * HTML5 input fields for mobile devices to get their native UI for date/time entry.
+ */
+class et2_date extends et2_inputWidget
+{
+ static readonly _attributes: any = {
+ "value": {
+ "type": "any"
+ },
+ "type": {
+ "ignore": false
+ },
+ "blur": {
+ "name": "Placeholder",
+ "type": "string",
+ "default": "",
+ "description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text."
+ },
+ "data_format": {
+ "ignore": true,
+ "description": "Date/Time format. Can be set as an options to date widget",
+ "default": ''
+ },
+ year_range: {
+ name: "Year range",
+ type: "string",
+ default: "c-10:c+10",
+ description: "The range of years displayed in the year drop-down: either relative to today's year (\"-nn:+nn\"), relative to the currently selected year (\"c-nn:c+nn\"), absolute (\"nnnn:nnnn\"), or combinations of these formats (\"nnnn:-nn\"). Note that this option only affects what appears in the drop-down, to restrict which dates may be selected use the min and/or max options."
+ },
+ min: {
+ "name": "Minimum",
+ "type": "any",
+ "default": et2_no_init,
+ "description": 'Minimum allowed date. Multiple types supported:\
+Date: A date object containing the minimum date.\
+Number: A number of days from today. For example 2 represents two days from today and -1 represents yesterday.\
+String: A string in the user\'s date format, or a relative date. Relative dates must contain value and period pairs; valid periods are "y" for years, "m" for months, "w" for weeks, and "d" for days. For example, "+1m +7d" represents one month and seven days from today.'
+ },
+ max: {
+ "name": "Maximum",
+ "type": "any",
+ "default": et2_no_init,
+ "description": 'Maximum allowed date. Multiple types supported:\
+Date: A date object containing the maximum date.\
+Number: A number of days from today. For example 2 represents two days from today and -1 represents yesterday.\
+String: A string in the user\'s date format, or a relative date. Relative dates must contain value and period pairs; valid periods are "y" for years, "m" for months, "w" for weeks, and "d" for days. For example, "+1m +7d" represents one month and seven days from today.'
+ },
+ inline: {
+ "name": "Inline",
+ "type": "boolean",
+ "default": false,
+ "description": "Instead of an input field with a popup calendar, the calendar is displayed inline, with no input field"
+ }
+ };
+
+ legacyOptions: string[] = ["data_format"];
+ date: Date;
+ span: JQuery;
+ input_date: JQuery = null;
+ is_mobile: boolean = false;
+ dateFormat: string;
+ timeFormat: string;
+
+ /**
+ * Constructor
+ */
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {}));
+
+ this.date = new Date();
+ this.date.setUTCHours(0);
+ this.date.setMinutes(0);
+ this.date.setSeconds(0);
+
+ this.createInputWidget();
+ }
+
+ createInputWidget()
+ {
+ this.span = jQuery(document.createElement(this.options.inline ? 'div' : "span")).addClass("et2_date");
+
+ this.input_date = jQuery(document.createElement(this.options.inline ? "div" : "input"));
+ if (this.options.blur) this.input_date.attr('placeholder', this.egw().lang(this.options.blur));
+ this.input_date.addClass("et2_date").attr("type", "text")
+ .attr("size", 7) // strlen("10:00pm")=7
+ .appendTo(this.span);
+
+ this.setDOMNode(this.span[0]);
+
+ // inline calendar is not existing in html5, so allways use datepicker instead
+ this.is_mobile = egwIsMobile() && !this.options.inline;
+
+ if (this.is_mobile)
+ {
+ this.dateFormat = 'yy-mm-dd';
+ this.timeFormat = 'HH:mm';
+ switch(this.getType())
+ {
+ case 'date':
+ this.input_date.attr('type', 'date');
+ break;
+ case 'date-time':
+ this.input_date.attr('type', 'datetime-local');
+ break;
+ case 'date-timeonly':
+ this.input_date.addClass("et2_time");
+ this.input_date.attr('type', 'time');
+ break;
+ }
+ }
+ else
+ {
+ this.dateFormat = this.egw().dateTimeFormat(this.egw().preference("dateformat"));
+ this.timeFormat = this.egw().preference("timeformat") == 12 ? "h:mmtt" : "HH:mm";
+ // jQuery-UI date picker
+ if(this.getType() != 'date-timeonly')
+ {
+ this.egw().calendar(this.input_date, this.getType() == "date-time");
+ }
+ else
+ {
+ this.input_date.addClass("et2_time");
+ this.egw().time(this.input_date);
+ }
+
+ // Avoid collision of datepicker dialog with input field
+ var widget = this;
+ this.input_date.datepicker('option', 'beforeShow', function(input, inst){
+ var cal = inst.dpDiv;
+ setTimeout(function () {
+ var $input = jQuery(input);
+ var inputOffset = $input.offset();
+ // position the datepicker in freespace zone
+ // avoid datepicker calendar collision with input field
+ if (cal.height() + inputOffset.top > window.innerHeight)
+ {
+ cal.position({
+ my: "left center",
+ at: 'right bottom',
+ collision: 'flip fit',
+ of: input
+ });
+ }
+ // Add tooltip to Today/Now button
+ jQuery('[data-handler="today"]',cal).attr('title',
+ widget.getType() == 'date' ? egw.lang('Today') : egw.lang('Now')
+ );
+
+ },0);
+ })
+ .datepicker('option','onClose', function(dateText, inst) {
+ // Lose focus, avoids an issue with focus
+ // not allowing datepicker to re-open
+ inst.input.blur();
+ });
+ }
+
+ // Update internal value when changed
+ var self = this;
+ this.input_date.bind('change', function(e){
+ self.set_value(this.value);
+ return false;
+ });
+
+ // Framewok skips nulls, but null needs to be processed here
+ if(this.options.value == null)
+ {
+ this.set_value(null);
+ }
+ }
+
+ set_type(_type)
+ {
+ if(_type != this.getType())
+ {
+ super.setType(_type);
+ this.createInputWidget();
+ }
+ }
+
+ /**
+ * Dynamic disable or enable datepicker
+ *
+ * @param {boolean} _ro
+ */
+ set_readonly(_ro)
+ {
+ if (this.input_date && !this.input_date.attr('disabled') != !_ro)
+ {
+ this.input_date.attr('disabled', !_ro ? 0 : 1)
+ .datepicker('option', 'disabled', !!_ro);
+ }
+ }
+
+ /**
+ * Set (full) year of current date
+ *
+ * @param {number} _value 4-digit year
+ */
+ set_year(_value)
+ {
+ this.date.setUTCFullYear(_value);
+ this.set_value(this.date);
+ }
+ /**
+ * Set month (1..12) of current date
+ *
+ * @param {number} _value 1..12
+ */
+ set_month(_value)
+ {
+ this.date.setUTCMonth(_value-1);
+ this.set_value(this.date);
+ }
+ /**
+ * Set day of current date
+ *
+ * @param {number} _value 1..31
+ */
+ set_date(_value)
+ {
+ this.date.setUTCDate(_value);
+ this.set_value(this.date);
+ }
+ /**
+ * Set hour (0..23) of current date
+ *
+ * @param {number} _value 0..23
+ */
+ set_hours(_value)
+ {
+ this.date.setUTCHours(_value);
+ this.set_value(this.date);
+ }
+ /**
+ * Set minute (0..59) of current date
+ *
+ * @param {number} _value 0..59
+ */
+ set_minutes(_value)
+ {
+ this.date.setUTCMinutes(_value);
+ this.set_value(this.date);
+ }
+ /**
+ * Get (full) year of current date
+ *
+ * @return {number|null} 4-digit year or null for empty
+ */
+ get_year()
+ {
+ return this.input_date.val() == "" ? null : this.date.getUTCFullYear();
+ }
+ /**
+ * Get month (1..12) of current date
+ *
+ * @return {number|null} 1..12 or null for empty
+ */
+ get_month()
+ {
+ return this.input_date.val() == "" ? null : this.date.getUTCMonth()+1;
+ }
+ /**
+ * Get day of current date
+ *
+ * @return {number|null} 1..31 or null for empty
+ */
+ get_date()
+ {
+ return this.input_date.val() == "" ? null : this.date.getUTCDate();
+ }
+ /**
+ * Get hour (0..23) of current date
+ *
+ * @return {number|null} 0..23 or null for empty
+ */
+ get_hours()
+ {
+ return this.input_date.val() == "" ? null : this.date.getUTCHours();
+ }
+ /**
+ * Get minute (0..59) of current date
+ *
+ * @return {number|null} 0..59 or null for empty
+ */
+ get_minutes()
+ {
+ return this.input_date.val() == "" ? null : this.date.getUTCMinutes();
+ }
+ /**
+ * Get timestamp
+ *
+ * You can use set_value to set a timestamp.
+ *
+ * @return {number|null} timestamp (seconds since 1970-01-01)
+ */
+ get_time()
+ {
+ return this.input_date.val() == "" ? null : this.date.getTime();
+ }
+
+ /**
+ * The range of years displayed in the year drop-down: either relative
+ * to today's year ("-nn:+nn"), relative to the currently selected year
+ * ("c-nn:c+nn"), absolute ("nnnn:nnnn"), or combinations of these formats
+ * ("nnnn:-nn"). Note that this option only affects what appears in the
+ * drop-down, to restrict which dates may be selected use the min_date
+ * and/or max_date options.
+ * @param {string} _value
+ */
+ set_year_range(_value)
+ {
+ if(this.input_date && this.getType() == 'date' && !this.is_mobile)
+ {
+ this.input_date.datepicker('option','yearRange',_value);
+ }
+ this.options.year_range = _value;
+ }
+
+ /**
+ * Set the minimum allowed date
+ *
+ * The minimum selectable date. When set to null, there is no minimum.
+ * Multiple types supported:
+ * Date: A date object containing the minimum date.
+ * Number: A number of days from today. For example 2 represents two days
+ * from today and -1 represents yesterday.
+ * String: A string in the format defined by the dateFormat option, or a
+ * relative date. Relative dates must contain value and period pairs;
+ * valid periods are "y" for years, "m" for months, "w" for weeks, and
+ * "d" for days. For example, "+1m +7d" represents one month and seven
+ * days from today.
+ * @param {Date|Number|String} _value
+ */
+ set_min(_value)
+ {
+ if(this.input_date)
+ {
+ if (this.is_mobile)
+ {
+ this.input_date.attr('min', this._relativeDate(_value));
+ }
+ else
+ {
+ // Check for full timestamp
+ if(typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/))
+ {
+ _value = new Date(_value);
+ // Add timezone offset back in, or formatDate will lose those hours
+ var formatDate = new Date(_value.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
+ if(this.getType() == 'date')
+ {
+ _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
+ }
+ }
+ this.input_date.datepicker('option','minDate',_value);
+ }
+ }
+ this.options.min = _value;
+ }
+
+ /**
+ * Convert non html5 min or max attributes described above to timestamps
+ *
+ * @param {string|Date} _value
+ */
+ _relativeDate(_value)
+ {
+ if (typeof _value == 'string' && _value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/)) return _value;
+
+ // @ts-ignore
+ return jQuery.datepicker._determineDate(jQuery.datepicker, _value, this.date).toJSON();
+ }
+
+ /**
+ * Set the maximum allowed date
+ *
+ * The maximum selectable date. When set to null, there is no maximum.
+ * Multiple types supported:
+ * Date: A date object containing the maximum date.
+ * Number: A number of days from today. For example 2 represents two days
+ * from today and -1 represents yesterday.
+ * String: A string in the format defined by the dateFormat option, or a
+ * relative date. Relative dates must contain value and period pairs;
+ * valid periods are "y" for years, "m" for months, "w" for weeks, and
+ * "d" for days. For example, "+1m +7d" represents one month and seven
+ * days from today.
+ * @param {Date|Number|String} _value
+ */
+ set_max(_value)
+ {
+ if(this.input_date)
+ {
+ if (this.is_mobile)
+ {
+ this.input_date.attr('max', this._relativeDate(_value));
+ }
+ else
+ {
+ // Check for full timestamp
+ if(typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/))
+ {
+ _value = new Date(_value);
+ // Add timezone offset back in, or formatDate will lose those hours
+ var formatDate = new Date(_value.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
+ if(this.getType() == 'date')
+ {
+ _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
+ }
+ }
+ this.input_date.datepicker('option','maxDate',_value);
+ }
+ }
+ this.options.max = _value;
+ }
+
+ /**
+ * Setting date
+ *
+ * @param {string|number|Date} _value supported are the following formats:
+ * - Date object with usertime as UTC value
+ * - string like Date.toJSON()
+ * - string or number with timestamp in usertime like server-side uses it
+ * - string starting with + or - to add/substract given number of seconds from current value, "+600" to add 10 minutes
+ */
+ set_value(_value)
+ {
+ var old_value = this._oldValue;
+ if(_value === null || _value === "" || _value === undefined ||
+ // allow 0 as empty-value for date and date-time widgets, as that is used a lot eg. in InfoLog
+ _value == 0 && (this.getType() == 'date-time' || this.getType() == 'date'))
+ {
+ if(this.input_date)
+ {
+ this.input_date.val("");
+ }
+ if(this._oldValue !== et2_no_init && old_value !== _value)
+ {
+ this.change(this.input_date);
+ }
+ this._oldValue = _value;
+ return;
+ }
+
+ // timestamp in usertime, convert to 'Y-m-d\\TH:i:s\\Z', as we do on server-side with equivalent of PHP date()
+ if (typeof _value == 'number' || typeof _value == 'string' && !isNaN(_value) && _value[0] != '+' && _value[0] != '-')
+ {
+ _value = date('Y-m-d\\TH:i:s\\Z', _value);
+ }
+ // Check for full timestamp
+ if(typeof _value == 'string' && _value.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2})|)$/))
+ {
+ _value = new Date(_value);
+ }
+ // Handle just time as a string in the form H:i
+ if(typeof _value == 'string' && isNaN(_value))
+ {
+ try {
+ // silently fix skiped minutes or times with just one digit, as parser is quite pedantic ;-)
+ var fix_reg = new RegExp((this.getType() == "date-timeonly"?'^':' ')+'([0-9]+)(:[0-9]*)?( ?(a|p)m?)?$','i');
+ var matches = _value.match(fix_reg);
+ if (matches && (matches[1].length < 2 || matches[2] === undefined || matches[2].length < 3 ||
+ matches[3] && matches[3] != 'am' && matches[3] != 'pm'))
+ {
+ if (matches[1].length < 2 && !matches[3]) matches[1] = '0'+matches[1];
+ if (matches[2] === undefined) matches[2] = ':00';
+ while (matches[2].length < 3) matches[2] = ':0'+matches[2].substr(1);
+ _value = _value.replace(fix_reg, (this.getType() == "date-timeonly"?'':' ')+matches[1]+matches[2]+matches[3]);
+ if (matches[4] !== undefined) matches[3] = matches[4].toLowerCase() == 'a' ? 'am' : 'pm';
+ }
+ switch(this.getType())
+ {
+ case "date-timeonly":
+ // @ts-ignore
+ var parsed = jQuery.datepicker.parseTime(this.timeFormat, _value);
+ if (!parsed) // parseTime returns false
+ {
+ this.set_validation_error(this.egw().lang("'%1' has an invalid format !!!",_value));
+ return;
+ }
+ this.set_validation_error(false);
+ // this.date is on current date, changing it in get_value() to 1970-01-01, gives a time-difference, if we are currently on DST
+ this.date.setDate(1);
+ this.date.setMonth(0);
+ this.date.setFullYear(1970);
+ // Avoid javascript timezone offset, hour is in 'user time'
+ this.date.setUTCHours(parsed.hour);
+ this.date.setMinutes(parsed.minute);
+ if(this.input_date.val() != _value)
+ {
+ this.input_date.val(_value);
+ // @ts-ignore
+ this.input_date.timepicker('setTime',_value);
+ if (this._oldValue !== et2_no_init)
+ {
+ this.change(this.input_date);
+ }
+ }
+ this._oldValue = this.date.toJSON();
+ return;
+ default:
+ // Parse customfields's date with storage data_format to date object
+ // Or generally any date widgets with fixed date/time format
+ if (this.id.match(/^#/g) && this.options.value == _value || (this.options.data_format && this.options.value == _value))
+ {
+ switch (this.getType())
+ {
+ case 'date':
+ var parsed = jQuery.datepicker.parseDate(this.egw().dateTimeFormat(this.options.data_format), _value);
+ break;
+ case 'date-time':
+ var DTformat = this.options.data_format.split(' ');
+ // @ts-ignore
+ var parsed = jQuery.datepicker.parseDateTime(this.egw().dateTimeFormat(DTformat[0]),this.egw().dateTimeFormat(DTformat[1]), _value);
+ }
+ }
+ else // Parse other date widgets date with timepicker date/time format to date onject
+ {
+ // @ts-ignore
+ var parsed = jQuery.datepicker.parseDateTime(this.dateFormat,
+ this.timeFormat, _value.replace('T', ' '));
+ if(!parsed)
+ {
+ this.set_validation_error(this.egw().lang("%1' han an invalid format !!!",_value));
+ return;
+ }
+ }
+ // Update local variable, but remove the timezone offset that
+ // javascript adds when we parse
+ if(parsed)
+ {
+ this.date = new Date(parsed.valueOf() - parsed.getTimezoneOffset() * 60000);
+ }
+
+ this.set_validation_error(false);
+ }
+ }
+ // catch exception from unparsable date and display it empty instead
+ catch(e) {
+ return this.set_value(null);
+ }
+ } else if (typeof _value == 'object' && _value.date) {
+ this.date = _value.date;
+ } else if (typeof _value == 'object' && _value.valueOf) {
+ this.date = _value;
+ } else
+ // string starting with + or - --> add/substract number of seconds from current value
+ {
+ this.date.setTime(this.date.getTime()+1000*parseInt(_value));
+ }
+
+ // Update input - popups do, but framework doesn't
+ _value = '';
+ // Add timezone offset back in, or formatDate will lose those hours
+ var formatDate = new Date(this.date.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
+ if(this.getType() != 'date-timeonly')
+ {
+ _value = jQuery.datepicker.formatDate(this.dateFormat, formatDate);
+ }
+ if(this.getType() != 'date')
+ {
+ if(this.getType() != 'date-timeonly') _value += this.is_mobile ? 'T' : ' ';
+
+ // @ts-ignore
+ _value += jQuery.datepicker.formatTime(this.timeFormat, {
+ hour: formatDate.getHours(),
+ minute: formatDate.getMinutes(),
+ seconds: 0,
+ timezone: 0
+ });
+ }
+ if(this.options.inline )
+ {
+ this.input_date.datepicker("setDate",formatDate);
+ }
+ else
+ {
+ this.input_date.val(_value);
+ }
+ if(this._oldValue !== et2_no_init && old_value != this.getValue())
+ {
+ this.change(this.input_date);
+ }
+ this._oldValue = _value;
+ }
+
+ getValue()
+ {
+ if(this.input_date.val() == "")
+ {
+ // User blanked the box
+ return null;
+ }
+ // date-timeonly returns just the seconds, without any date!
+ if (this.getType() == 'date-timeonly')
+ {
+ this.date.setUTCDate(1);
+ this.date.setUTCMonth(0);
+ this.date.setUTCFullYear(1970);
+ }
+ else if (this.getType() == 'date')
+ {
+ this.date.setUTCHours(0);
+ this.date.setUTCMinutes(0);
+ }
+
+ // Convert to timestamp - no seconds
+ this.date.setSeconds(0,0);
+ return (this.date && typeof this.date.toJSON != 'undefined' && this.date.toJSON())?this.date.toJSON().replace(/\.\d{3}Z$/, 'Z'):this.date;
+ }
+}
+et2_register_widget(et2_date, ["date", "date-time", "date-timeonly"]);
+
+/**
+ * Class which implements the "date-duration" XET-Tag
+ */
+class et2_date_duration extends et2_date
+{
+ static readonly _attributes: any = {
+ "data_format": {
+ "name": "Data format",
+ "default": "m",
+ "type": "string",
+ "description": "Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (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."
+ },
+ "percent_allowed": {
+ "name": "Percent allowed",
+ "default": false,
+ "type": "boolean",
+ "description": "Allows to enter a percentage."
+ },
+ "hours_per_day": {
+ "name": "Hours per day",
+ "default": 8,
+ "type": "integer",
+ "description": "Number of hours in a day, for converting between hours and (working) days."
+ },
+ "empty_not_0": {
+ "name": "0 or empty",
+ "default": false,
+ "type": "boolean",
+ "description": "Should the widget differ between 0 and empty, which get then returned as NULL"
+ },
+ "short_labels": {
+ "name": "Short labels",
+ "default": false,
+ "type": "boolean",
+ "description": "use d/h/m instead of day/hour/minute"
+ },
+ "step" : {
+ "name": "Step limit",
+ "default": 'any',
+ "type": "string",
+ "description": "Works with the min and max attributes to limit the increments at which a numeric or date-time value can be set."
+ }
+ };
+
+ legacyOptions: string[] = ["data_format","display_format", "hours_per_day", "empty_not_0", "short_labels"];
+
+ time_formats: {"d":"d","h":"h","m":"m"};
+
+ // @ts-ignore baseWidget defines node as HTMLElement
+ node: JQuery;
+ duration: JQuery;
+ format: JQuery;
+
+ /**
+ * Constructor
+ */
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {}));
+
+ // Legacy option put percent in with display format
+ if(this.options.display_format.indexOf("%") != -1)
+ {
+ this.options.percent_allowed = true;
+ this.options.display_format = this.options.display_format.replace("%","");
+ }
+
+ // Clean formats
+ this.options.display_format = this.options.display_format.replace(/[^dhm]/,'');
+ 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")
+ },
+ this.createInputWidget();
+ }
+
+ createInputWidget()
+ {
+ // Create nodes
+ this.node = jQuery(document.createElement("span"))
+ .addClass('et2_date_duration');
+ this.duration = jQuery(document.createElement("input"))
+ .addClass('et2_date_duration')
+ .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);
+
+ if(this.options.display_format.length > 1)
+ {
+ this.format = jQuery(document.createElement("select"))
+ .addClass('et2_date_duration');
+ this.node.append(this.format);
+
+ for(var i = 0; i < this.options.display_format.length; i++) {
+ this.format.append("");
+ }
+ }
+ else if (this.time_formats[this.options.display_format])
+ {
+ this.format = jQuery(""+this.time_formats[this.options.display_format]+"").appendTo(this.node);
+ }
+ else
+ {
+ this.format = jQuery(""+this.time_formats["m"]+"").appendTo(this.node);
+ }
+ var self = this;
+ // seems the 'invalid' event doesn't work in all browsers, eg. FF therefore
+ // we use focusout event to check the valifdity of input right after user
+ // enters the value.
+ this.duration.on('focusout', function() {
+ if(!(self.duration[0]).checkValidity())
+ return self.duration.change();
+ });
+ }
+
+ /**
+ * Clientside validation
+ *
+ * @param {array} _messages
+ */
+ isValid(_messages)
+ {
+ var ok = true;
+ // if we have a html5 validation error, show it, as this.input.val() will be empty!
+ if (this.duration && this.duration[0] &&
+ (this.duration[0]).validationMessage &&
+ !(this.duration[0]).validity.stepMismatch)
+ {
+ _messages.push((this.duration[0]).validationMessage);
+ ok = false;
+ }
+ return super.isValid(_messages) && ok;
+ }
+
+ attachToDOM()
+ {
+ var node = this.getInputNode();
+ if (node)
+ {
+ jQuery(node).bind("change.et2_inputWidget", this, function(e) {
+ e.data.change(this);
+ });
+ }
+ return et2_DOMWidget.prototype.attachToDOM.apply(this, arguments);
+ }
+
+ getDOMNode()
+ {
+ return this.node[0];
+ }
+
+ getInputNode()
+ {
+ return this.duration[0];
+ }
+
+ /**
+ * Use id on node, same as DOMWidget
+ *
+ * @param {string} _value id to set
+ */
+ set_id(_value)
+ {
+ this.id = _value;
+
+ var node = this.getDOMNode();
+ if (node)
+ {
+ if (_value != "")
+ {
+ node.setAttribute("id", this.getInstanceManager().uniqueId+'_'+this.id);
+ }
+ else
+ {
+ node.removeAttribute("id");
+ }
+ }
+ }
+
+ set_value(_value)
+ {
+ this.options.value = _value;
+
+ var display = this._convert_to_display(_value);
+
+ // Set display
+ if(this.duration[0].nodeName == "INPUT")
+ {
+ this.duration.val(display.value);
+ }
+ else
+ {
+ this.duration.text(display.value + " ");
+ }
+
+ // Set unit as figured for display
+ if(display.unit != this.options.display_format)
+ {
+ if(this.format && this.format.children().length > 1) {
+ jQuery("option[value='"+display.unit+"']",this.format).attr('selected','selected');
+ }
+ else
+ {
+ this.format.text(display.unit ? this.time_formats[display.unit] : '');
+ }
+ }
+ }
+
+ /**
+ * Converts the value in data format into value in display format.
+ *
+ * @param _value int/float Data in data format
+ *
+ * @return Object {value: Value in display format, unit: unit for display}
+ */
+ _convert_to_display(_value)
+ {
+ if (_value)
+ {
+ // Put value into minutes for further processing
+ switch(this.options.data_format)
+ {
+ case 'd':
+ _value *= this.options.hours_per_day;
+ // fall-through
+ case 'h':
+ _value *= 60;
+ break;
+ }
+ }
+
+ // Figure out best unit for display
+ var _unit = this.options.display_format == "d" ? "d" : "h";
+ if (this.options.display_format.indexOf('m') > -1 && _value && _value < 60)
+ {
+ _unit = 'm';
+ }
+ else if (this.options.display_format.indexOf('d') > -1 && _value >= 60*this.options.hours_per_day)
+ {
+ _unit = 'd';
+ }
+ _value = this.options.empty_not_0 && _value === '' || !this.options.empty_not_0 && !_value ? '' :
+ (_unit == 'm' ? parseInt( _value) : (Math.round((_value / 60.0 / (_unit == 'd' ? this.options.hours_per_day : 1))*100)/100));
+
+ if(_value === '') _unit = '';
+
+ // use decimal separator from user prefs
+ var format = this.egw().preference('number_format');
+ var sep = format ? format[0] : '.';
+ if (typeof _value == 'string' && format && sep && sep != '.')
+ {
+ _value = _value.replace('.',sep);
+ }
+
+ return {value: _value, unit:_unit};
+ }
+
+ /**
+ * Change displayed value into storage value and return
+ */
+ getValue()
+ {
+ var value = this.duration.val().replace(',', '.');
+ if(value === '')
+ {
+ return this.options.empty_not_0 ? '' : 0;
+ }
+ // Put value into minutes for further processing
+ switch(this.format && this.format.val() ? this.format.val() : this.options.display_format)
+ {
+ case 'd':
+ value *= this.options.hours_per_day;
+ // fall-through
+ case 'h':
+ value *= 60;
+ break;
+ }
+ // Minutes should be an integer. Floating point math.
+ value = Math.round(value);
+
+ switch(this.options.data_format)
+ {
+ case 'd':
+ value /= this.options.hours_per_day;
+ // fall-through
+ case 'h':
+ value /= 60.0;
+ break;
+ }
+ return value;
+ }
+}
+et2_register_widget(et2_date_duration, ["date-duration"]);
+
+/**
+ * r/o date-duration
+ */
+class et2_date_duration_ro extends et2_date_duration implements et2_IDetachedDOM
+{
+ createInputWidget()
+ {
+ this.node = jQuery(document.createElement("span"));
+ this.duration = jQuery(document.createElement("span")).appendTo(this.node);
+ this.format = jQuery(document.createElement("span")).appendTo(this.node);
+ }
+
+ /**
+ * Code for implementing et2_IDetachedDOM
+ * Fast-clonable read-only widget that only deals with DOM nodes, not the widget tree
+ */
+
+ /**
+ * Build a list of attributes which can be set when working in the
+ * "detached" mode in the _attrs array which is provided
+ * by the calling code.
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ getDetachedAttributes(_attrs)
+ {
+ _attrs.push("value");
+ }
+
+ /**
+ * Returns an array of DOM nodes. The (relativly) same DOM-Nodes have to be
+ * passed to the "setDetachedAttributes" function in the same order.
+ *
+ * @return {array}
+ */
+ getDetachedNodes()
+ {
+ return [this.duration[0], this.format[0]];
+ }
+
+ /**
+ * Sets the given associative attribute->value array and applies the
+ * attributes to the given DOM-Node.
+ *
+ * @param _nodes is an array of nodes which has to be in the same order as
+ * the nodes returned by "getDetachedNodes"
+ * @param _values is an associative array which contains a subset of attributes
+ * returned by the "getDetachedAttributes" function and sets them to the
+ * given values.
+ */
+ setDetachedAttributes(_nodes, _values)
+ {
+ for(var i = 0; i < _nodes.length; i++) {
+ // Clear the node
+ for (var j = _nodes[i].childNodes.length - 1; j >= 0; j--)
+ {
+ _nodes[i].removeChild(_nodes[i].childNodes[j]);
+ }
+ }
+ if(typeof _values.value !== 'undefined')
+ {
+ _values.value = parseFloat(_values.value);
+ }
+ if(_values.value)
+ {
+ var display = this._convert_to_display(_values.value);
+ _nodes[0].appendChild(document.createTextNode(display.value));
+ _nodes[1].appendChild(document.createTextNode(display.unit));
+ }
+ }
+
+}
+et2_register_widget(et2_date_duration_ro, ["date-duration_ro"]);
+
+/**
+ * et2_date_ro is the readonly implementation of some date widget.
+ */
+class et2_date_ro extends et2_valueWidget implements et2_IDetachedDOM
+{
+ /**
+ * Ignore all more advanced attributes.
+ */
+ static readonly _attributes: any = {
+ "value": {
+ "type": "string"
+ },
+ "type": {
+ "ignore": false
+ },
+ "data_format": {
+ "ignore": true,
+ "description": "Format data is in. This is not used client-side because it's always a timestamp client side."
+ },
+ min: {ignore: true},
+ max: {ignore: true},
+ year_range: {ignore: true}
+ };
+
+ legacyOptions: ["data_format"];
+
+ /**
+ * Internal container for working easily with dates
+ */
+ date: Date = new Date();
+ value: string = "";
+ span: JQuery;
+
+ /**
+ * Constructor
+ */
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {}));
+
+ this._labelContainer = jQuery(document.createElement("label"))
+ .addClass("et2_label");
+
+ this.span = jQuery(document.createElement(this.getType() == "date-since" || this.getType() == "date-time_today" ? "span" : "time"))
+ .addClass("et2_date_ro et2_label")
+ .appendTo(this._labelContainer);
+
+ this.setDOMNode(this._labelContainer[0]);
+ }
+
+ set_value(_value)
+ {
+ if(typeof _value == 'undefined') _value = 0;
+
+ this.value = _value;
+
+ if(_value == 0 || _value == null)
+ {
+ this.span.attr("datetime", "").text("");
+ return;
+ }
+
+ if(typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(?:\.\d{3})?(?:Z|[+-](\d{2})\:(\d{2}))/))
+ {
+ this.date = new Date(_value);
+ this.date = new Date(this.date.valueOf() + (this.date.getTimezoneOffset()*60*1000));
+ }
+ else if(typeof _value == 'string' && (isNaN(_value) ||
+ this.options.data_format && this.options.data_format.substr(0,3) === 'Ymd'))
+ {
+ try {
+ // data_format is not handled server-side for custom-fields in nextmatch
+ // as parseDateTime requires a separate between date and time, we fix the value here
+ switch (this.options.data_format)
+ {
+ case 'Ymd':
+ case 'YmdHi':
+ case 'YmdHis':
+ _value = _value.substr(0, 4)+'-'+_value.substr(4, 2)+'-'+_value.substr(6, 2)+' '+
+ (_value.substr(8, 2) || '00')+':'+(_value.substr(10, 2) || '00')+':'+(_value.substr(12, 2) || '00');
+ break;
+ }
+ // parseDateTime to handle string PHP: DateTime local date/time format
+ // @ts-ignore
+ var parsed = (typeof jQuery.datepicker.parseDateTime("yy-mm-dd","hh:mm:ss", _value) !='undefined')?
+ // @ts-ignore
+ jQuery.datepicker.parseDateTime("yy-mm-dd","hh:mm:ss", _value):
+ // @ts-ignore
+ jQuery.datepicker.parseDateTime(this.egw().preference('dateformat'),this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', _value);
+ }
+ // display unparsable dates as empty
+ catch(e) {
+ this.span.attr("datetime", "").text("");
+ return;
+ }
+ var text = new Date(parsed);
+
+ // Update local variable, but remove the timezone offset that javascript adds
+ if(parsed)
+ {
+ this.date = new Date(text.valueOf() - (text.getTimezoneOffset()*60*1000));
+ }
+
+ // JS dates use milliseconds
+ this.date.setTime(text.valueOf());
+ }
+ else
+ {
+ // _value is timestamp in usertime, ready to be used with date() function identical to PHP date()
+ this.date = _value;
+ }
+ var display = this.date.toString();
+
+ switch(this.getType()) {
+ case "time_or_date":
+ case "date-time_today":
+ // Today - just the time
+ if(date('Y-m-d', this.date) == date('Y-m-d'))
+ {
+ display = date(this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', this.date);
+ }
+ else if (this.getType() === "time_or_date")
+ {
+ display = date(this.egw().preference('dateformat'), this.date);
+ }
+ // Before today - date and time
+ else
+ {
+ display = date(this.egw().preference('dateformat') + " " +
+ (this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a'), this.date);
+ }
+ break;
+ case "date":
+ display = date(this.egw().preference('dateformat'), this.date);
+ break;
+ case "date-timeonly":
+ display = date(this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', this.date);
+ break;
+ case "date-time":
+ display = date(this.egw().preference('dateformat') + " " +
+ (this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a'), this.date);
+ break;
+ case "date-since":
+ var unit2label = {
+ 'Y': 'years',
+ 'm': 'month',
+ 'd': 'days',
+ 'H': 'hours',
+ 'i': 'minutes',
+ 's': 'seconds'
+ };
+ var unit2s = {
+ 'Y': 31536000,
+ 'm': 2628000,
+ 'd': 86400,
+ 'H': 3600,
+ 'i': 60,
+ 's': 1
+ };
+ var d = new Date();
+ var diff = Math.round(d.valueOf() / 1000) - Math.round(this.date.valueOf()/1000);
+ display = '';
+
+ for(var unit in unit2s)
+ {
+ var unit_s = unit2s[unit];
+ if (diff >= unit_s || unit == 's')
+ {
+ display = Math.round(diff/unit_s)+' '+this.egw().lang(unit2label[unit]);
+ break;
+ }
+ }
+ break;
+ }
+ this.span.attr("datetime", date("Y-m-d H:i:s",this.date)).text(display);
+ }
+
+ set_label(label)
+ {
+ // Remove current label
+ this._labelContainer.contents()
+ .filter(function(){ return this.nodeType == 3; }).remove();
+
+ var parts = et2_csvSplit(label, 2, "%s");
+ this._labelContainer.prepend(parts[0]);
+ this._labelContainer.append(parts[1]);
+ this.label = label;
+
+ // add class if label is empty
+ this._labelContainer.toggleClass('et2_label_empty', !label || !parts[0]);
+ }
+
+ /**
+ * Creates a list of attributes which can be set when working in the
+ * "detached" mode. The result is stored in the _attrs array which is provided
+ * by the calling code.
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ getDetachedAttributes(_attrs)
+ {
+ _attrs.push("label", "value","class");
+ }
+
+ /**
+ * Returns an array of DOM nodes. The (relatively) same DOM-Nodes have to be
+ * passed to the "setDetachedAttributes" function in the same order.
+ *
+ * @return {array}
+ */
+ getDetachedNodes()
+ {
+ return [this._labelContainer[0], this.span[0]];
+ }
+
+ /**
+ * Sets the given associative attribute->value array and applies the
+ * attributes to the given DOM-Node.
+ *
+ * @param _nodes is an array of nodes which have to be in the same order as
+ * the nodes returned by "getDetachedNodes"
+ * @param _values is an associative array which contains a subset of attributes
+ * returned by the "getDetachedAttributes" function and sets them to the
+ * given values.
+ */
+ setDetachedAttributes(_nodes, _values)
+ {
+ this._labelContainer = jQuery(_nodes[0]);
+ this.span = jQuery(_nodes[1]);
+
+ this.set_value(_values["value"]);
+ if(_values["label"])
+ {
+ this.set_label(_values["label"]);
+ }
+ if(_values["class"])
+ {
+ this.span.addClass(_values["class"]);
+ }
+ }
+}
+et2_register_widget(et2_date_ro, ["date_ro", "date-time_ro", "date-since", "date-time_today", "time_or_date", "date-timeonly_ro"]);
+
+
+/**
+ * Widget for selecting a date range
+ */
+class et2_date_range extends et2_inputWidget
+{
+ static readonly _attributes: any = {
+ value: {
+ "type": "any",
+ "description": "An object with keys 'from' and 'to' for absolute ranges, or a relative range string"
+ },
+ relative: {
+ name: 'Relative',
+ type: 'boolean',
+ description: 'Is the date range relative (this week) or absolute (2016-02-15 - 2016-02-21). This will affect the value returned.'
+ }
+ }
+
+ div: JQuery;
+ from: et2_date;
+ to: et2_date;
+ select: et2_selectbox;
+
+ /**
+ * Constructor
+ */
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {}));
+
+ this.div = jQuery(document.createElement('div'))
+ .attr({ class:'et2_date_range'});
+
+ this.from = null;
+ this.to = null;
+ this.select = null;
+
+ // Set domid
+ this.set_id(this.id);
+
+ this.setDOMNode(this.div[0]);
+ this._createWidget();
+
+ this.set_relative(this.options.relative || false);
+ }
+
+ _createWidget()
+ {
+ var widget = this;
+
+ this.from = et2_createWidget('date',{
+ id: this.id+'[from]',
+ blur: egw.lang('From'),
+ onchange() { widget.to.set_min(widget.from.getValue()); }
+ },this);
+ this.to = et2_createWidget('date',{
+ id: this.id+'[to]',
+ blur: egw.lang('To'),
+ onchange() {widget.from.set_max(widget.to.getValue()); }
+ },this);
+ this.select = et2_createWidget('select',{
+ id: this.id+'[relative]',
+ select_options: et2_date_range.relative_dates,
+ empty_label: this.options.blur || 'All'
+ },this);
+ this.select.loadingFinished();
+ }
+
+ /**
+ * Function which allows iterating over the complete widget tree.
+ * Overridden here to avoid problems with children when getting value
+ *
+ * @param _callback is the function which should be called for each widget
+ * @param _context is the context in which the function should be executed
+ * @param _type is an optional parameter which specifies a class/interface
+ * the elements have to be instanceOf.
+ */
+ iterateOver(_callback, _context, _type)
+ {
+ if (typeof _type == "undefined")
+ {
+ _type = et2_widget;
+ }
+
+ if (this.isInTree() && this.instanceOf(_type))
+ {
+ _callback.call(_context, this);
+ }
+ }
+
+ /**
+ * Toggles relative or absolute dates
+ *
+ * @param {boolean} _value
+ */
+ set_relative(_value)
+ {
+ this.options.relative = _value;
+ if(this.options.relative)
+ {
+ jQuery(this.from.getDOMNode()).hide();
+ jQuery(this.to.getDOMNode()).hide();
+ }
+ else
+ {
+ jQuery(this.select.getDOMNode()).hide();
+ }
+ }
+
+ set_value(value)
+ {
+ // @ts-ignore
+ if(!value || typeof value == 'null')
+ {
+ this.select.set_value('');
+ this.from.set_value(null);
+ this.to.set_value(null);
+ }
+
+ // Relative
+ if(value && typeof value === 'string')
+ {
+ this._set_relative_value(value);
+
+ }
+ else if(value && typeof value.from === 'undefined' && value[0])
+ {
+ value = {
+ from: value[0],
+ to: value[1] || new Date().valueOf()/1000
+ };
+ }
+ else if (value && value.from && value.to)
+ {
+ this.from.set_value(value.from);
+ this.to.set_value(value.to);
+ }
+ }
+
+ getValue()
+ {
+ return this.options.relative ?
+ this.select.getValue() :
+ { from: this.from.getValue(), to: this.to.getValue() };
+ }
+
+ _set_relative_value(_value)
+ {
+ if(this.options.relative)
+ {
+ jQuery(this.select.getDOMNode()).show();
+ }
+ // Show description
+ this.select.set_value(_value);
+
+ var tempDate = new Date();
+ var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(),0,-tempDate.getTimezoneOffset(),0);
+
+ // Use strings to avoid references
+ this.from.set_value(today.toJSON());
+ this.to.set_value(today.toJSON());
+
+ var relative = null;
+ for(var index in et2_date_range.relative_dates)
+ {
+ if(et2_date_range.relative_dates[index].value === _value)
+ {
+ relative = et2_date_range.relative_dates[index];
+ break;
+ }
+ }
+ if(relative)
+ {
+ var dates = ["from","to"];
+ var value = today.toJSON();
+ for(var i = 0; i < dates.length; i++)
+ {
+ var date = dates[i];
+ if(typeof relative[date] == "function")
+ {
+ value = relative[date](new Date(value));
+ }
+ else
+ {
+ value = this[date]._relativeDate(relative[date]);
+ }
+ this[date].set_value(value);
+ }
+ }
+ }
+
+ // Class Constants
+ static readonly relative_dates = [
+ // Start and end are relative offsets, see et2_date.set_min()
+ // or Date objects
+ {
+ value: 'Today',
+ label: 'Today',
+ from(date) {return date;},
+ to(date) {return date;}
+ },
+ {
+ label: 'Yesterday',
+ value: 'Yesterday',
+ from(date) {
+ date.setUTCDate(date.getUTCDate() - 1);
+ return date;
+ },
+ to: ''
+ },
+ {
+ label: 'This week',
+ value: 'This week',
+ from(date) {return egw.week_start(date);},
+ to(date) {
+ date.setUTCDate(date.getUTCDate() + 6);
+ return date;
+ }
+ },
+ {
+ label: 'Last week',
+ value: 'Last week',
+ from(date) {
+ var d = egw.week_start(date);
+ d.setUTCDate(d.getUTCDate() - 7);
+ return d;
+ },
+ to(date) {
+ date.setUTCDate(date.getUTCDate() + 6);
+ return date;
+ }
+ },
+ {
+ label: 'This month',
+ value: 'This month',
+ from(date)
+ {
+ date.setUTCDate(1);
+ return date;
+ },
+ to(date)
+ {
+ date.setUTCMonth(date.getUTCMonth()+1);
+ date.setUTCDate(0);
+ return date;
+ }
+ },
+ {
+ label: 'Last month',
+ value: 'Last month',
+ from(date)
+ {
+ date.setUTCMonth(date.getUTCMonth() - 1);
+ date.setUTCDate(1);
+ return date;
+ },
+ to(date)
+ {
+ date.setUTCMonth(date.getUTCMonth()+1);
+ date.setUTCDate(0);
+ return date;
+ }
+ },
+ {
+ label: 'Last 3 months',
+ value: 'Last 3 months',
+ from(date)
+ {
+ date.setUTCMonth(date.getUTCMonth() - 2);
+ date.setUTCDate(1);
+ return date;
+ },
+ to(date)
+ {
+ date.setUTCMonth(date.getUTCMonth()+3);
+ date.setUTCDate(0);
+ return date;
+ }
+ },
+ /*
+ 'This quarter'=> array(0,0,0,0, 0,0,0,0), // Just a marker, needs special handling
+ 'Last quarter'=> array(0,-4,0,0, 0,-4,0,0), // Just a marker
+ */
+ {
+ label: 'This year',
+ value: 'This year',
+ from(d) {
+ d.setUTCMonth(0);
+ d.setUTCDate(1);
+ return d;
+ },
+ to(d) {
+ d.setUTCMonth(11);
+ d.setUTCDate(31);
+ return d;
+ }
+ },
+ {
+ label: 'Last year',
+ value: 'Last year',
+ from(d) {
+ d.setUTCMonth(0);
+ d.setUTCDate(1);
+ d.setUTCYear(d.getUTCYear() - 1);
+ return d;
+ },
+ to(d) {
+ d.setUTCMonth(11);
+ d.setUTCDate(31);
+ d.setUTCYear(d.getUTCYear() - 1);
+ return d;
+ }
+ }
+ /* Still needed?
+ '2 years ago' => array(-2,0,0,0, -1,0,0,0),
+ '3 years ago' => array(-3,0,0,0, -2,0,0,0),
+ */
+ ];
+}
+et2_register_widget(et2_date_range, ["date-range"]);