From 528134cfac4d5258f6c4db0b9b26f4927a26ffc2 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 14 Dec 2021 16:55:02 -0700 Subject: [PATCH] Adapt nextmatch to work with webcomponents + readonly datetime widget --- api/js/etemplate/Et2Date/DateStyles.ts | 12 + api/js/etemplate/Et2Date/Et2Date.ts | 6 +- .../etemplate/Et2Date/Et2DateTimeReadonly.ts | 74 +++ api/js/etemplate/Et2Widget/Et2Widget.ts | 54 ++- .../et2_extension_nextmatch_rowProvider.ts | 296 +++++++----- api/js/etemplate/etemplate2.ts | 446 ++++++++++-------- 6 files changed, 557 insertions(+), 331 deletions(-) create mode 100644 api/js/etemplate/Et2Date/DateStyles.ts create mode 100644 api/js/etemplate/Et2Date/Et2DateTimeReadonly.ts diff --git a/api/js/etemplate/Et2Date/DateStyles.ts b/api/js/etemplate/Et2Date/DateStyles.ts new file mode 100644 index 0000000000..2e656c4d25 --- /dev/null +++ b/api/js/etemplate/Et2Date/DateStyles.ts @@ -0,0 +1,12 @@ +import {css} from "@lion/core"; + +export const dateStyles = css` + :host { + display: inline-block; + white-space: nowrap; + min-width: 20ex; + } + .overdue { + color: red; // var(--whatever the theme color) + } +`; \ No newline at end of file diff --git a/api/js/etemplate/Et2Date/Et2Date.ts b/api/js/etemplate/Et2Date/Et2Date.ts index 1c7382869a..1496ba7d55 100644 --- a/api/js/etemplate/Et2Date/Et2Date.ts +++ b/api/js/etemplate/Et2Date/Et2Date.ts @@ -13,6 +13,7 @@ import {css, html} from "@lion/core"; import {LionInputDatepicker} from "@lion/input-datepicker"; import {Unparseable} from "@lion/form-core"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; +import {dateStyles} from "./DateStyles"; /** @@ -155,7 +156,7 @@ export function parseDateTime(dateTimeString) { if(!isNaN(dateTimeString) && parseInt(dateTimeString) == dateTimeString) { - this.egw().debug("warn", "Invalid date/time string: " + dateTimeString); + console.warn("Invalid date/time string: " + dateTimeString); dateTimeString *= 1000; } try @@ -275,6 +276,7 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker) { return [ ...super.styles, + dateStyles, css` :host([focused]) ::slotted(button), :host(:hover) ::slotted(button) { display: inline-block; @@ -337,7 +339,7 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker) { return null; } - + // Empty field, return '' if(!this.modelValue) { diff --git a/api/js/etemplate/Et2Date/Et2DateTimeReadonly.ts b/api/js/etemplate/Et2Date/Et2DateTimeReadonly.ts new file mode 100644 index 0000000000..f1d0a46507 --- /dev/null +++ b/api/js/etemplate/Et2Date/Et2DateTimeReadonly.ts @@ -0,0 +1,74 @@ +import {css, html, LitElement} from "@lion/core"; +import {formatDateTime, parseDateTime} from "./Et2Date"; +import {et2_IDetachedDOM} from "../et2_core_interfaces"; +import {Et2Widget} from "../Et2Widget/Et2Widget"; +import {dateStyles} from "./DateStyles"; + +/** + * This is a stripped-down read-only widget used in nextmatch + */ +export class Et2DateTimeReadonly extends Et2Widget(LitElement) implements et2_IDetachedDOM +{ + private value : any; + + static get styles() + { + return [ + ...super.styles, + dateStyles + ]; + } + + static get properties() + { + return { + ...super.properties, + value: String, + } + } + + set_value(value) + { + this.value = value; + } + + render() + { + let parsed : Date | Boolean = this.value ? parseDateTime(this.value) : false + + return html` + + `; + } + + getDetachedAttributes(attrs) + { + attrs.push("id", "value", "class"); + } + + getDetachedNodes() : HTMLElement[] + { + return [this]; + } + + setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void + { + // Do nothing, since we can't actually stop being a DOM node... + } + + loadFromXML() + { + // nope + } + + loadingFinished() + { + // already done, I'm a wc with no children + } +} + +// @ts-ignore TypeScript is not recognizing that Et2Date is a LitElement +customElements.define("et2-datetime_ro", Et2DateTimeReadonly); \ No newline at end of file diff --git a/api/js/etemplate/Et2Widget/Et2Widget.ts b/api/js/etemplate/Et2Widget/Et2Widget.ts index 36c810ac1a..2a9145c85f 100644 --- a/api/js/etemplate/Et2Widget/Et2Widget.ts +++ b/api/js/etemplate/Et2Widget/Et2Widget.ts @@ -93,6 +93,12 @@ const Et2WidgetMixin = (superClass) => return { ...super.properties, + /** + * Widget ID. Optional, and not always the same as the DOM ID if the widget is inside something + * else that also has an ID. + */ + id: {type: String, reflect: false}, + /** * CSS Class. This class is applied to the _outside_, on the web component itself. * Due to how WebComponents work, this might not change anything inside the component. @@ -606,6 +612,11 @@ const Et2WidgetMixin = (superClass) => } } + transformAttributes(attrs) + { + transformAttributes(this, this.getArrayMgr("content"), attrs); + } + iterateOver(_callback : Function, _context, _type) { if(typeof _type == "undefined" || et2_implements_registry[_type] && et2_implements_registry[_type](this)) @@ -817,8 +828,12 @@ const Et2WidgetMixin = (superClass) => // Create the copy var copy = this.cloneNode(); - let widget_class = window.customElements.get(this.nodeName); - let properties = widget_class ? widget_class.properties() : {}; + let widget_class = window.customElements.get(this.localName); + let properties = widget_class ? widget_class.properties : []; + for(let key in properties) + { + copy[key] = this[key]; + } if(_parent) { @@ -1078,10 +1093,28 @@ export function loadWebComponent(_nodeName : string, _template_node, parent : Et _template_node.getAttribute("id"), _template_node.getAttribute("readonly"), typeof parent.readonly !== "undefined" ? parent.readonly : false) : false; - // Apply any set attributes - widget will do its own coercion + let attrs = {}; _template_node.getAttributeNames().forEach(attribute => { - let attrValue = _template_node.getAttribute(attribute); + attrs[attribute] = _template_node.getAttribute(attribute); + }); + widget.transformAttributes(attrs); + + // Children need to be loaded + widget.loadFromXML(_template_node); + + return widget; +} + +function transformAttributes(widget, mgr : et2_arrayMgr, attributes) +{ + + const widget_class = window.customElements.get(widget.localName); + + // Apply any set attributes - widget will do its own coercion + for(let attribute in attributes) + { + let attrValue = attributes[attribute]; // If there is not attribute set, ignore it. Widget sets its own default. if(typeof attrValue === "undefined") @@ -1095,7 +1128,7 @@ export function loadWebComponent(_nodeName : string, _template_node, parent : Et case Boolean: // If the attribute is marked as boolean, parse the // expression as bool expression. - attrValue = mgr.parseBoolExpression(attrValue); + attrValue = mgr ? mgr.parseBoolExpression(attrValue) : attrValue; break; case Function: // We parse it into a function here so we can pass in the widget as context. @@ -1106,8 +1139,8 @@ export function loadWebComponent(_nodeName : string, _template_node, parent : Et } break; default: - attrValue = mgr.expandName(attrValue); - if(!_template_node.getAttribute("no_lang") && widget_class.translate[attribute]) + attrValue = mgr ? mgr.expandName(attrValue) : attrValue; + if(!attributes.no_lang && widget_class.translate[attribute]) { // allow attribute to contain multiple translated sub-strings eg: {Firstname}.{Lastname} if(attrValue.indexOf('{') !== -1) @@ -1136,7 +1169,7 @@ export function loadWebComponent(_nodeName : string, _template_node, parent : Et // Set as property, not attribute widget[attribute] = attrValue; } - }); + } if(widget_class.getPropertyOptions("value") && widget.set_value) { @@ -1149,9 +1182,4 @@ export function loadWebComponent(_nodeName : string, _template_node, parent : Et } } } - - // Children need to be loaded - widget.loadFromXML(_template_node); - - return widget; } \ No newline at end of file diff --git a/api/js/etemplate/et2_extension_nextmatch_rowProvider.ts b/api/js/etemplate/et2_extension_nextmatch_rowProvider.ts index f553c1c54d..c4ac789d79 100644 --- a/api/js/etemplate/et2_extension_nextmatch_rowProvider.ts +++ b/api/js/etemplate/et2_extension_nextmatch_rowProvider.ts @@ -23,6 +23,7 @@ import {et2_arrayMgrs_expand} from "./et2_core_arrayMgr"; import {et2_dataview_grid} from "./et2_dataview_view_grid"; import {egw} from "../jsapi/egw_global"; import {et2_IDetachedDOM, et2_IDOMNode} from "./et2_core_interfaces"; +import {Et2DateTimeReadonly} from "./Et2Date/Et2DateTimeReadonly"; /** * The row provider contains prototypes (full clonable dom-trees) @@ -31,12 +32,13 @@ import {et2_IDetachedDOM, et2_IDOMNode} from "./et2_core_interfaces"; */ export class et2_nextmatch_rowProvider { - private _rowProvider: any; - private _subgridCallback: any; - private _context: any; - private _rootWidget: any; - private _template: any; - private _dataRow: any; + private _rowProvider : any; + private _subgridCallback : any; + private _context : any; + private _rootWidget : any; + private _template : any; + private _dataRow : any; + /** * Creates the nextmatch row provider. * @@ -45,9 +47,9 @@ export class et2_nextmatch_rowProvider * @param {object} _context * @memberOf et2_nextmatch_rowProvider */ - constructor( _rowProvider, _subgridCallback, _context) + constructor(_rowProvider, _subgridCallback, _context) { - + // Copy the arguments this._rowProvider = _rowProvider; @@ -73,7 +75,7 @@ export class et2_nextmatch_rowProvider * @param _rootWidget is the parent widget of the data rows (i.e. * the nextmatch) */ - setDataRowTemplate( _widgets, _rowData, _rootWidget) + setDataRowTemplate(_widgets, _rowData, _rootWidget) { // Copy the root widget this._rootWidget = _rootWidget; @@ -120,7 +122,7 @@ export class et2_nextmatch_rowProvider this._template = rowTemplate; } - getDataRow( _data : any, _row, _idx, _controller) + getDataRow(_data : any, _row, _idx, _controller) { // Clone the row template @@ -133,17 +135,24 @@ export class et2_nextmatch_rowProvider // Insert the widgets into the row which do not provide the functions // to set the _data directly var rowWidget : et2_nextmatch_rowTemplateWidget = null; - if (this._template.seperated.remaining.length > 0) + if(this._template.seperated.remaining.length > 0) { // Transform the variable attributes - for (var i = 0; i < this._template.seperated.remaining.length; i++) + for(var i = 0; i < this._template.seperated.remaining.length; i++) { var entry = this._template.seperated.remaining[i]; - for (var j = 0; j < entry.data.length; j++) + for(var j = 0; j < entry.data.length; j++) { var set = entry.data[j]; - entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression); + if(typeof entry.widget.options != "undefined") + { + entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression); + } + else if(entry.widget.getAttributeNames().indexOf(set.attribute) >= 0) + { + entry.widget.setAttribute(set.attribute, mgrs["content"].expandName(set.expression)); + } } } @@ -156,47 +165,56 @@ export class et2_nextmatch_rowProvider } // Update the content of all other widgets - for (var i = 0; i < this._template.seperated.detachable.length; i++) + for(var i = 0; i < this._template.seperated.detachable.length; i++) { var entry = this._template.seperated.detachable[i]; + let widget = entry.widget; // Parse the attribute expressions var data : any = {}; - for (var j = 0; j < entry.data.length; j++) + for(var j = 0; j < entry.data.length; j++) { var set = entry.data[j]; data[set.attribute] = mgrs["content"].expandName(set.expression); } - - // Retrieve all DOM-Nodes - var nodes = new Array(entry.nodeFuncs.length); - for (var j = 0; j < nodes.length; j++) + // WebComponent IS the node, and we've already cloned it + if(typeof window.customElements.get(widget.localName) != "undefined") { - // Use the previously compiled node function to get the node - // from the entry - try + // Use the clone, not the original + widget = entry.nodeFuncs[0](row) + } + else + { + // Retrieve all DOM-Nodes (legacy widgets) + var nodes = new Array(entry.nodeFuncs.length); + for(var j = 0; j < nodes.length; j++) { - nodes[j] = entry.nodeFuncs[j](row); - } - catch (e) - { - debugger; - continue; + // Use the previously compiled node function to get the node + // from the entry + try + { + nodes[j] = entry.nodeFuncs[j](row); + } + catch(e) + { + debugger; + continue; + } } } // Set the array managers first - entry.widget._mgrs = mgrs; - if (typeof data.id != "undefined") + widget.setArrayMgrs(mgrs); + if(typeof data.id != "undefined") { - entry.widget.id = data.id; + widget.id = data.id; } // Adjust data for that row - entry.widget.transformAttributes?.call(entry.widget, data); + widget.transformAttributes?.call(widget, data); // Call the setDetachedAttributes function - entry.widget.setDetachedAttributes(nodes, data, _data); + widget.setDetachedAttributes(nodes, data, _data); } // Insert the row into the tr @@ -204,12 +222,13 @@ export class et2_nextmatch_rowProvider tr.appendChild(row); // Make the row expandable - if (typeof _data.content["is_parent"] !== "undefined" - && _data.content["is_parent"]) + if(typeof _data.content["is_parent"] !== "undefined" + && _data.content["is_parent"]) { - _row.makeExpandable(true, function () { + _row.makeExpandable(true, function() + { return this._subgridCallback.call(this._context, - _row, _data, _controller); + _row, _data, _controller); }, this); // Check for kept expansion, and set the row up to be re-expanded @@ -222,13 +241,14 @@ export class et2_nextmatch_rowProvider var expansion_index = top_controller.kept_expansion.indexOf( top_controller.dataStorePrefix + '::' + _data.content[this._context.settings.row_id] ); - if(top_controller.kept_expansion && expansion_index >=0) + if(top_controller.kept_expansion && expansion_index >= 0) { - top_controller.kept_expansion.splice(expansion_index,1); + top_controller.kept_expansion.splice(expansion_index, 1); // Use a timeout since the DOM nodes might not be finished yet - window.setTimeout(function() { + window.setTimeout(function() + { _row.expansionButton.trigger('click'); - },et2_dataview_grid.ET2_GRID_INVALIDATE_TIMEOUT); + }, et2_dataview_grid.ET2_GRID_INVALIDATE_TIMEOUT); } } @@ -246,14 +266,14 @@ export class et2_nextmatch_rowProvider * This allows the user to still have a drop target, or use actions that * do not require a row ID, such as 'Add new'. */ - _createEmptyPrototype( ) + _createEmptyPrototype() { var label = this._context && this._context.options && this._context.options.settings.placeholder; var placeholder = jQuery(document.createElement("td")) - .attr("colspan",this._rowProvider.getColumnCount()) - .css("height","19px") - .text(typeof label != "undefined" && label ? label : egw().lang("No matches found")); + .attr("colspan", this._rowProvider.getColumnCount()) + .css("height", "19px") + .text(typeof label != "undefined" && label ? label : egw().lang("No matches found")); this._rowProvider._prototypes["empty"] = jQuery(document.createElement("tr")) .addClass("egwGridView_empty") .append(placeholder); @@ -266,11 +286,11 @@ export class et2_nextmatch_rowProvider * * @param {et2_widget} _widget */ - _getVariableAttributeSet( _widget) + _getVariableAttributeSet(_widget) { let variableAttributes = []; - const process = function (_widget) + const process = function(_widget) { // Create the attribtues var hasAttr = false; @@ -280,28 +300,15 @@ export class et2_nextmatch_rowProvider }; // Get all attribute values - for (const key in _widget.attributes) + let attrs = []; + if(_widget.getDetachedAttributes) + { + _widget.getDetachedAttributes(attrs); + } + for(let key of attrs) { - if(typeof _widget.attributes[key] !== "object") - { - continue; - } - let attr_name = key; - let val; - if(!_widget.attributes[key].ignore && - typeof _widget.options != "undefined" && - typeof _widget.options[key] != "undefined") - { - val = _widget.options[key]; - } - // Handle web components - else if(_widget.attributes[key].value) - { - val = _widget.attributes[key].value; - attr_name = _widget.attributes[key].name; - } - // TODO: Improve detection + let val = _widget[key]; if(typeof val == "string" && val.indexOf("$") >= 0) { hasAttr = true; @@ -312,6 +319,36 @@ export class et2_nextmatch_rowProvider } } + // Legacy + if(_widget.instanceOf(et2_widget)) + { + for(const key in _widget.attributes) + { + if(typeof _widget.attributes[key] !== "object") + { + continue; + } + + let attr_name = key; + let val; + if(!_widget.attributes[key].ignore && + typeof _widget.options != "undefined" && + typeof _widget.options[key] != "undefined") + { + val = _widget.options[key]; + } + // TODO: Improve detection + if(typeof val == "string" && val.indexOf("$") >= 0) + { + hasAttr = true; + widgetData.data.push({ + "attribute": attr_name, + "expression": val + }); + } + } + } + // Add the entry if there is any data in it if(hasAttr) { @@ -321,7 +358,7 @@ export class et2_nextmatch_rowProvider // Check each column const columns = _widget._widgets; - for (var i = 0; i < columns.length; i++) + for(var i = 0; i < columns.length; i++) { // If column is hidden, don't process it if(typeof columns[i] === 'undefined' || this._context && this._context.columns && this._context.columns[i] && !this._context.columns[i].visible) @@ -334,7 +371,7 @@ export class et2_nextmatch_rowProvider return variableAttributes; } - _seperateWidgets( _varAttrs) + _seperateWidgets(_varAttrs) { // The detachable array contains all widgets which implement the // et2_IDetachedDOM interface for all needed attributes @@ -345,19 +382,20 @@ export class et2_nextmatch_rowProvider var remaining = []; // Iterate over the widgets - for (var i = 0; i < _varAttrs.length; i++) + for(var i = 0; i < _varAttrs.length; i++) { var widget = _varAttrs[i].widget; // Check whether the widget parents are not allready in the "remaining" // slot - if this is the case do not include the widget at all. var insertWidget = true; - var checkWidget = function (_widget) { - if (_widget.parent != null) + var checkWidget = function(_widget) + { + if(_widget.parent != null) { - for (var i = 0; i < remaining.length; i++) + for(var i = 0; i < remaining.length; i++) { - if (remaining[i].widget == _widget.parent) + if(remaining[i].widget == _widget.parent) { insertWidget = false; return; @@ -370,29 +408,32 @@ export class et2_nextmatch_rowProvider checkWidget(widget); // Handle the next widget if this one should not be included. - if (!insertWidget) + if(!insertWidget) { continue; } // Check whether the widget implements the et2_IDetachedDOM interface var isDetachable = false; - if (widget.implements(et2_IDetachedDOM)) + if(widget.implements && widget.implements(et2_IDetachedDOM)) { // Get all attributes the widgets supports to be set in the // "detached" mode var supportedAttrs = []; - widget.getDetachedAttributes(supportedAttrs); + if(widget.getDetachedAttributes) + { + widget.getDetachedAttributes(supportedAttrs); + } supportedAttrs.push("id"); isDetachable = true; - for (var j = 0; j < _varAttrs[i].data.length/* && isDetachable*/; j++) + for(var j = 0; j < _varAttrs[i].data.length/* && isDetachable*/; j++) { var data = _varAttrs[i].data[j]; var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1; - if (!supportsAttr) + if(!supportsAttr) { egw.debug("warn", "et2_IDetachedDOM widget " + widget._type + " does not support " + data.attribute); @@ -403,7 +444,7 @@ export class et2_nextmatch_rowProvider } // Insert the widget into the correct slot - if (isDetachable) + if(isDetachable) { detachable.push(_varAttrs[i]); } @@ -424,28 +465,28 @@ export class et2_nextmatch_rowProvider * * @param {object} _rowTemplate */ - _stripTemplateRow( _rowTemplate) + _stripTemplateRow(_rowTemplate) { _rowTemplate.placeholders = []; - for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++) + for(var i = 0; i < _rowTemplate.seperated.remaining.length; i++) { var entry = _rowTemplate.seperated.remaining[i]; // Issue a warning - widgets which do not implement et2_IDOMNode // are very slow - egw.debug("warn", "Non-clonable widget '"+ entry.widget._type + "' in dataview row - this " + + egw.debug("warn", "Non-clonable widget '" + entry.widget._type + "' in dataview row - this " + "might be slow", entry); // Set the placeholder for the entry to null entry.placeholder = null; // Get the outer DOM-Node of the widget - if (entry.widget.implements(et2_IDOMNode)) + if(entry.widget.implements(et2_IDOMNode)) { var node = entry.widget.getDOMNode(entry.widget); - if (node && node.parentNode) + if(node && node.parentNode) { // Get the parent node and replace the node with a placeholder entry.placeholder = document.createElement("span"); @@ -460,15 +501,15 @@ export class et2_nextmatch_rowProvider } } - _nodeIndex( _node) + _nodeIndex(_node) { if(_node.parentNode == null) { return 0; } - for (var i = 0; i < _node.parentNode.childNodes.length; i++) + for(var i = 0; i < _node.parentNode.childNodes.length; i++) { - if (_node.parentNode.childNodes[i] == _node) + if(_node.parentNode.childNodes[i] == _node) { return i; } @@ -483,20 +524,20 @@ export class et2_nextmatch_rowProvider * @param {DOMElement} _root * @param {DOMElement} _target */ - _compileDOMAccessFunc( _root, _target) + _compileDOMAccessFunc(_root, _target) { function recordPath(_root, _target, _path) { - if (typeof _path == "undefined") + if(typeof _path == "undefined") { _path = []; } - if (_root != _target && _target) + if(_root != _target && _target) { // Get the index of _target in its parent node var idx = this._nodeIndex(_target); - if (idx >= 0) + if(idx >= 0) { // Add the access selector _path.unshift("childNodes[" + idx + "]"); @@ -522,18 +563,18 @@ export class et2_nextmatch_rowProvider * * @param {object} _rowTemplate */ - _buildNodeAccessFuncs( _rowTemplate) + _buildNodeAccessFuncs(_rowTemplate) { - for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++) + for(var i = 0; i < _rowTemplate.seperated.detachable.length; i++) { var entry = _rowTemplate.seperated.detachable[i]; // Get all needed nodes from the widget - var nodes = entry.widget.getDetachedNodes(); + var nodes = window.customElements.get(entry.widget.localName) ? [entry.widget] : entry.widget.getDetachedNodes(); var nodeFuncs = entry.nodeFuncs = new Array(nodes.length); // Record the path to each DOM-Node - for (var j = 0; j < nodes.length; j++) + for(var j = 0; j < nodes.length; j++) { nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row, nodes[j]); @@ -548,11 +589,11 @@ export class et2_nextmatch_rowProvider * * We can NOT use something like /(^| |,|cat_)([0-9]+)( |,|$)/g as it wont find all cats in "123,456,789 "! */ - cat_regexp: RegExp = /(^| |,|cat_)([0-9]+)/g; + cat_regexp : RegExp = /(^| |,|cat_)([0-9]+)/g; /** * Regular expression used to filter out non-nummerical chars from above matches */ - cat_cleanup: RegExp = /[^0-9]/g; + cat_cleanup : RegExp = /[^0-9]/g; /** * Applies additional row data (like the class) to the tr @@ -561,10 +602,10 @@ export class et2_nextmatch_rowProvider * @param {DOMElement} _tr * @param {object} _mgrs */ - _setRowData( _data, _tr, _mgrs) + _setRowData(_data, _tr, _mgrs) { // TODO: Implement other fields than "class" - if (_data["class"]) + if(_data["class"]) { var classes = _mgrs["content"].expandName(_data["class"]); @@ -575,7 +616,10 @@ export class et2_nextmatch_rowProvider { // Accept either cat, cat_id or category as ID, and look there for category settings var category_location = _data["class"].match(/(cat(_id|egory)?)/); - if(category_location) category_location = category_location[0]; + if(category_location) + { + category_location = category_location[0]; + } cats = classes.match(this.cat_regexp) || []; classes = classes.replace(this.cat_regexp, ''); @@ -585,9 +629,9 @@ export class et2_nextmatch_rowProvider { // Need cat_, classes can't start with a number var cat_id = cats[i].replace(this.cat_cleanup, ''); - var cat_class = 'cat_'+cat_id; + var cat_class = 'cat_' + cat_id; - classes += ' '+cat_class; + classes += ' ' + cat_class; } classes += " row_category"; } @@ -607,8 +651,9 @@ export class et2_nextmatch_rowProvider */ export class et2_nextmatch_rowWidget extends et2_widget implements et2_IDOMNode { - private _widgets: any[]; - private _row: any; + private _widgets : any[]; + private _row : any; + /** * Constructor * @@ -616,7 +661,7 @@ export class et2_nextmatch_rowWidget extends et2_widget implements et2_IDOMNode * @param _row * @memberOf et2_nextmatch_rowWidget */ - constructor( _mgrs, _row) + constructor(_mgrs, _row) { // Call the parent constructor with some dummy attributes super(null, {"id": "", "type": "rowWidget"}); @@ -635,15 +680,18 @@ export class et2_nextmatch_rowWidget extends et2_widget implements et2_IDOMNode * * @param {array} _widgets */ - createWidgets( _widgets) + createWidgets(_widgets) { // Clone the given the widgets with this element as parent this._widgets = []; let row_id = 0; - for (var i = 0; i < _widgets.length; i++) + for(var i = 0; i < _widgets.length; i++) { // Disabled columns might be missing widget - skip it - if(!_widgets[i]) continue; + if(!_widgets[i]) + { + continue; + } this._widgets[i] = _widgets[i].clone(this); this._widgets[i].loadingFinished(); @@ -662,13 +710,16 @@ export class et2_nextmatch_rowWidget extends et2_widget implements et2_IDOMNode * @param {et2_widget} _sender * @return {DOMElement} */ - getDOMNode( _sender) + getDOMNode(_sender) { var row_id = 0; - for (var i = 0; i < this._widgets.length; i++) + for(var i = 0; i < this._widgets.length; i++) { // Disabled columns might be missing widget - skip it - if(!this._widgets[i]) continue; + if(!this._widgets[i]) + { + continue; + } if(this._widgets[i] == _sender && this._row.childNodes[row_id]) { return this._row.childNodes[row_id].childNodes[0]; // Return the i-th td tag @@ -686,9 +737,10 @@ export class et2_nextmatch_rowWidget extends et2_widget implements et2_IDOMNode */ export class et2_nextmatch_rowTemplateWidget extends et2_widget implements et2_IDOMNode { - private _root: any; - private _row: any; - private _widgets: any[]; + private _root : any; + private _row : any; + private _widgets : any[]; + /** * Constructor * @@ -696,7 +748,7 @@ export class et2_nextmatch_rowTemplateWidget extends et2_widget implements et2_I * @param _row * @memberOf et2_nextmatch_rowTemplateWidget */ - constructor( _root, _row) + constructor(_root, _row) { // Call the parent constructor with some dummy attributes super(null, {"id": "", "type": "rowTemplateWidget"}); @@ -712,14 +764,14 @@ export class et2_nextmatch_rowTemplateWidget extends et2_widget implements et2_I this._widgets = []; } - createWidgets( _mgrs, _widgets : {widget : et2_widget,func(_row: any): any; }[]) + createWidgets(_mgrs, _widgets : { widget : et2_widget, func(_row : any) : any; }[]) { // Set the array managers - don't use setArrayMgrs here as this creates // an unnecessary copy of the object this._mgrs = _mgrs; this._widgets = new Array(_widgets.length); - for (var i = 0; i < _widgets.length; i++) + for(var i = 0; i < _widgets.length; i++) { this._row.childNodes[0].childNodes[0]; @@ -737,12 +789,12 @@ export class et2_nextmatch_rowTemplateWidget extends et2_widget implements et2_I * @param {et2_widget} _sender * @return {DOMElement} */ - getDOMNode( _sender: et2_widget): HTMLElement + getDOMNode(_sender : et2_widget) : HTMLElement { - for (var i = 0; i < this._widgets.length; i++) + for(var i = 0; i < this._widgets.length; i++) { - if (this._widgets[i].widget == _sender) + if(this._widgets[i].widget == _sender) { return this._widgets[i].node; } diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts index c9ebbfddfa..e6353f3abb 100644 --- a/api/js/etemplate/etemplate2.ts +++ b/api/js/etemplate/etemplate2.ts @@ -26,6 +26,7 @@ import {egwIsMobile} from "../egw_action/egw_action_common.js"; import './Et2Box/Et2Box'; import './Et2Button/Et2Button'; import './Et2Date/Et2DateTime'; +import './Et2Date/Et2DateTimeReadonly'; import './Et2Textarea/Et2Textarea'; import './Et2Textbox/Et2Textbox'; import './Et2Colorpicker/Et2Colorpicker'; @@ -102,25 +103,25 @@ export class etemplate2 private static _byTemplate = {}; - private _etemplate_exec_id: string; - private readonly menuaction: string; - name: string; - private uniqueId: void | string; - private template_base_url: string; + private _etemplate_exec_id : string; + private readonly menuaction : string; + name : string; + private uniqueId : void | string; + private template_base_url : string; - private _widgetContainer: et2_container; - private _DOMContainer: HTMLElement; + private _widgetContainer : et2_container; + private _DOMContainer : HTMLElement; - private resize_timeout: number | boolean; - private destroy_session: any; - private close_prompt: any; - private _skip_close_prompt: boolean; - private app_obj: EgwApp; - app: string; + private resize_timeout : number | boolean; + private destroy_session : any; + private close_prompt : any; + private _skip_close_prompt : boolean; + private app_obj : EgwApp; + app : string; - constructor(_container: HTMLElement, _menuaction?: string, _uniqueId?: string) + constructor(_container : HTMLElement, _menuaction? : string, _uniqueId? : string) { - if (typeof _menuaction == "undefined") + if(typeof _menuaction == "undefined") { _menuaction = "EGroupware\\Api\\Etemplate::ajax_process_content"; } @@ -143,23 +144,23 @@ export class etemplate2 // We share list of templates with iframes and popups try { - if (opener && opener.etemplate2) + if(opener && opener.etemplate2) { etemplate2.templates = opener.etemplate2.templates; } // @ts-ignore - else if (top.etemplate2) + else if(top.etemplate2) { // @ts-ignore etemplate2.templates = top.etemplate2.templates; } } - catch (e) + catch(e) { // catch security exception if opener is from a different domain console.log('Security exception accessing etemplate2.prototype of opener or top!'); } - if (typeof etemplate2.templates == "undefined") + if(typeof etemplate2.templates == "undefined") { etemplate2.templates = {}; } @@ -174,42 +175,48 @@ export class etemplate2 { const event = e; const self = this; - let excess_height: number | boolean = false; + let excess_height : number | boolean = false; // Check if the framework has an specific excess height calculation - if (typeof window.framework != 'undefined' && typeof window.framework.get_wExcessHeight != 'undefined') + if(typeof window.framework != 'undefined' && typeof window.framework.get_wExcessHeight != 'undefined') { excess_height = window.framework.get_wExcessHeight(window); } //@TODO implement getaccess height for other framework and remove - if (typeof event != 'undefined' && event.type == 'resize') + if(typeof event != 'undefined' && event.type == 'resize') { - if (this.resize_timeout) + if(this.resize_timeout) { clearTimeout(this.resize_timeout); } - this.resize_timeout = setTimeout(function () + this.resize_timeout = setTimeout(function() { self.resize_timeout = false; - if (self._widgetContainer) + if(self._widgetContainer) { const appHeader = jQuery('#divAppboxHeader'); //Calculate the excess height excess_height = egw(window).is_popup() ? jQuery(window).height() - jQuery(self._DOMContainer).height() - appHeader.outerHeight() + 11 : 0; // Recalculate excess height if the appheader is shown - if (appHeader.length > 0 && appHeader.is(':visible')) excess_height -= appHeader.outerHeight() - 9; + if(appHeader.length > 0 && appHeader.is(':visible')) + { + excess_height -= appHeader.outerHeight() - 9; + } // Do not resize if the template height is bigger than screen available height // For templates which have sub templates and they are bigger than screenHeight - if (screen.availHeight < jQuery(self._DOMContainer).height()) excess_height = 0; + if(screen.availHeight < jQuery(self._DOMContainer).height()) + { + excess_height = 0; + } // If we're visible, call the "resize" event of all functions which implement the // "IResizeable" interface - if (jQuery(self.DOMContainer).is(":visible")) + if(jQuery(self.DOMContainer).is(":visible")) { - self._widgetContainer.iterateOver(function (_widget) + self._widgetContainer.iterateOver(function(_widget) { _widget.resize(excess_height); }, self, et2_IResizeable); @@ -218,11 +225,11 @@ export class etemplate2 }, 100); } // Initial resize needs to be resized immediately (for instance for nextmatch resize) - else if (this._widgetContainer) + else if(this._widgetContainer) { // Call the "resize" event of all functions which implement the // "IResizeable" interface - this._widgetContainer.iterateOver(function (_widget) + this._widgetContainer.iterateOver(function(_widget) { _widget.resize(excess_height); }, this, et2_IResizeable); @@ -234,23 +241,26 @@ export class etemplate2 * @param _keep_app_object keep app object * @param _keep_session keep server-side et2 session eg. for vfs-select */ - public clear(_keep_app_object?: boolean, _keep_session?: boolean) + public clear(_keep_app_object? : boolean, _keep_session? : boolean) { jQuery(this._DOMContainer).trigger('clear'); // Remove any handlers on window (resize) - if (this.uniqueId) + if(this.uniqueId) { jQuery(window).off("." + this.uniqueId); } // call our destroy_session handler, if it is not already unbind, and unbind it after - if (this.destroy_session) + if(this.destroy_session) { - if (!_keep_session) this.destroy_session(); + if(!_keep_session) + { + this.destroy_session(); + } this.unbind_unload(); } - if (this._widgetContainer != null) + if(this._widgetContainer != null) { // Un-register handler this._widgetContainer.egw().unregisterJSONPlugin(this.handle_assign, this, 'assign'); @@ -261,12 +271,15 @@ export class etemplate2 jQuery(this._DOMContainer).empty(); // Remove self from the index - for (const name in etemplate2.templates) + for(const name in etemplate2.templates) { - if (typeof etemplate2._byTemplate[name] == "undefined") continue; - for (let i = 0; i < etemplate2._byTemplate[name].length; i++) + if(typeof etemplate2._byTemplate[name] == "undefined") { - if (etemplate2._byTemplate[name][i] === this) + continue; + } + for(let i = 0; i < etemplate2._byTemplate[name].length; i++) + { + if(etemplate2._byTemplate[name][i] === this) { etemplate2._byTemplate[name].splice(i, 1); } @@ -274,11 +287,11 @@ export class etemplate2 } // If using a private app object, remove all of them - if (!_keep_app_object && this.app_obj !== window.app) + if(!_keep_app_object && this.app_obj !== window.app) { - for (const app_name in this.app_obj) + for(const app_name in this.app_obj) { - if (this.app_obj[app_name] instanceof EgwApp) + if(this.app_obj[app_name] instanceof EgwApp) { this.app_obj[app_name].destroy(); } @@ -286,17 +299,17 @@ export class etemplate2 } } - get widgetContainer(): et2_container + get widgetContainer() : et2_container { return this._widgetContainer; } - get DOMContainer(): HTMLElement + get DOMContainer() : HTMLElement { return this._DOMContainer; } - get etemplate_exec_id(): string + get etemplate_exec_id() : string { return this._etemplate_exec_id; } @@ -310,7 +323,7 @@ export class etemplate2 */ private _createArrayManagers(_data) { - if (typeof _data == "undefined") + if(typeof _data == "undefined") { _data = {}; } @@ -318,9 +331,9 @@ export class etemplate2 // Create all neccessary _data entries const neededEntries = ["content", "sel_options", "readonlys", "modifications", "validation_errors"]; - for (let i = 0; i < neededEntries.length; i++) + for(let i = 0; i < neededEntries.length; i++) { - if (typeof _data[neededEntries[i]] == "undefined" || !_data[neededEntries[i]]) + if(typeof _data[neededEntries[i]] == "undefined" || !_data[neededEntries[i]]) { egw.debug("log", "Created not passed entry '" + neededEntries[i] + "' in data array."); @@ -331,9 +344,9 @@ export class etemplate2 const result = {}; // Create an array manager object for each part of the _data array. - for (const key in _data) + for(const key in _data) { - switch (key) + switch(key) { case "etemplate_exec_id": // already processed case "app_header": @@ -360,14 +373,14 @@ export class etemplate2 bind_unload() { // Prompt user to save for dirty popups - if (window !== egw_topWindow() && !this.close_prompt) + if(window !== egw_topWindow() && !this.close_prompt) { this.close_prompt = this._close_changed_prompt.bind(this); window.addEventListener("beforeunload", this.close_prompt); } - if (this._etemplate_exec_id) + if(this._etemplate_exec_id) { - this.destroy_session = jQuery.proxy(function (ev) + this.destroy_session = jQuery.proxy(function(ev) { // need to use async === "keepalive" to run via beforeunload egw.json("EGroupware\\Api\\Etemplate::ajax_destroy_session", @@ -378,9 +391,9 @@ export class etemplate2 } } - private _close_changed_prompt(e: BeforeUnloadEvent) + private _close_changed_prompt(e : BeforeUnloadEvent) { - if (this._skip_close_prompt || !this.isDirty()) + if(this._skip_close_prompt || !this.isDirty()) { return; } @@ -404,7 +417,7 @@ export class etemplate2 { window.removeEventListener("beforeunload", this.destroy_session); window.removeEventListener("beforeunload", this.close_prompt); - if (window.onbeforeunload === this.destroy_session) + if(window.onbeforeunload === this.destroy_session) { window.onbeforeunload = null; } @@ -413,7 +426,7 @@ export class etemplate2 const onbeforeunload = window.onbeforeunload; window.onbeforeunload = null; // bind unload handler again (can NOT do it direct, as this would be quick enough to be still triggered!) - window.setTimeout(function () + window.setTimeout(function() { window.onbeforeunload = onbeforeunload; }, 100); @@ -457,7 +470,7 @@ export class etemplate2 this.name = _name; // store top-level template name to have it available in widgets // store template base url, in case initial template is loaded via webdav, to use that for further loads too // need to split off domain first, as it could contain app-name part of template eg. stylite.report.xet and https://my.stylite.de/egw/... - if (_url && _url[0] != '/') + if(_url && _url[0] != '/') { this.template_base_url = _url.match(/https?:\/\/[^/]+/).shift(); _url = _url.split(this.template_base_url)[1]; @@ -473,7 +486,7 @@ export class etemplate2 const appname = _name.split('.')[0]; // if no app object provided and template app is not currentapp (eg. infolog CRM view) // create private app object / closure with just classes / prototypes - if (!_app && appname && appname != currentapp || _open_target) + if(!_app && appname && appname != currentapp || _open_target) { app = {classes: window.app.classes}; } @@ -482,7 +495,7 @@ export class etemplate2 // extract $content['msg'] and call egw.message() with it const msg = _data.content.msg; - if (typeof msg != 'undefined') + if(typeof msg != 'undefined') { egw(window).message(msg); delete _data.content.msg; @@ -491,21 +504,21 @@ export class etemplate2 // Register a handler for AJAX responses egw(currentapp, window).registerJSONPlugin(this.handle_assign, this, 'assign'); - if (egw.debug_level() >= 3) + if(egw.debug_level() >= 3) { - if (console.groupCollapsed) + if(console.groupCollapsed) { egw.window.console.groupCollapsed("Loading %s into ", _name, '#' + this._DOMContainer.id); } } // Timing & profiling on debug level 'log' (4) - if (egw.debug_level() >= 4) + if(egw.debug_level() >= 4) { - if (console.time) + if(console.time) { console.time(_name); } - if (console.profile) + if(console.profile) { console.profile(_name); } @@ -514,7 +527,7 @@ export class etemplate2 // require necessary translations from server AND the app.js file, if not already loaded let promisses = [window.egw_ready]; // to wait for legacy-loaded JS - if (Array.isArray(_data.langRequire)) + if(Array.isArray(_data.langRequire)) { promisses.push(egw(currentapp, window).langRequire(window, _data.langRequire)); } @@ -529,23 +542,23 @@ export class etemplate2 let app_callback = null; // Only initialize once // new app class with constructor function in app.classes[appname] - if (typeof app[appname] !== 'object' && typeof app.classes[appname] == 'function') + if(typeof app[appname] !== 'object' && typeof app.classes[appname] == 'function') { app[appname] = new app.classes[appname](); } - else if (appname && typeof app[appname] !== "object") + else if(appname && typeof app[appname] !== "object") { egw.debug("warn", "Did not load '%s' JS object", appname); } // If etemplate current app does not match app owning the template, // initialize the current app too - if (typeof app[this.app] !== 'object' && typeof app.classes[this.app] == 'function') + if(typeof app[this.app] !== 'object' && typeof app.classes[this.app] == 'function') { app[this.app] = new app.classes[this.app](); } - if (typeof app[appname] == "object") + if(typeof app[appname] == "object") { - app_callback = function (_et2, _name) + app_callback = function(_et2, _name) { app[appname].et2_ready(_et2, _name); }; @@ -558,11 +571,11 @@ export class etemplate2 this._widgetContainer.setParentDOMNode(this._DOMContainer); // store the id to submit it back to server - if (_data) + if(_data) { this._etemplate_exec_id = _data.etemplate_exec_id; // set app_header - if (typeof _data.app_header == 'string') + if(typeof _data.app_header == 'string') { // @ts-ignore window.egw_app_header(_data.app_header); @@ -571,17 +584,17 @@ export class etemplate2 this.bind_unload(); } - const _load = function () + const _load = function() { egw.debug("log", "Loading template..."); - if (egw.debug_level() >= 4 && console.timeStamp) + if(egw.debug_level() >= 4 && console.timeStamp) { console.timeStamp("Begin rendering template"); } // Add into indexed list - do this before, so anything looking can find it, // even if it's not loaded - if (typeof etemplate2._byTemplate[_name] == "undefined") + if(typeof etemplate2._byTemplate[_name] == "undefined") { etemplate2._byTemplate[_name] = []; } @@ -597,34 +610,37 @@ export class etemplate2 this._widgetContainer.loadingFinished(deferred); // Connect to the window resize event - jQuery(window).on("resize." + this.uniqueId, this, function (e) + jQuery(window).on("resize." + this.uniqueId, this, function(e) { e.data.resize(e); }); - if (egw.debug_level() >= 3 && console.groupEnd) + if(egw.debug_level() >= 3 && console.groupEnd) { egw.window.console.groupEnd(); } - if (deferred.length > 0) + if(deferred.length > 0) { let still_deferred = 0; - jQuery(deferred).each(function () + jQuery(deferred).each(function() { - if (this.state() == "pending") still_deferred++; + if(this.state() == "pending") + { + still_deferred++; + } }); - if (still_deferred > 0) + if(still_deferred > 0) { egw.debug("log", "Template loaded, waiting for %d/%d deferred to finish...", still_deferred, deferred.length); } } // Wait for everything to be loaded, then finish it up - jQuery.when.apply(jQuery, deferred).done(jQuery.proxy(function () + jQuery.when.apply(jQuery, deferred).done(jQuery.proxy(function() { egw.debug("log", "Finished loading %s, triggering load event", _name); - if (typeof window.framework != 'undefined' && typeof window.framework.et2_loadingFinished != 'undefined') + if(typeof window.framework != 'undefined' && typeof window.framework.et2_loadingFinished != 'undefined') { //Call loading finished method of the framework with local window window.framework.et2_loadingFinished(egw(window).window); @@ -633,12 +649,12 @@ export class etemplate2 this.resize(); // Automatically set focus to first visible input for popups - if (this._widgetContainer._egw.is_popup() && jQuery('[autofocus]', this._DOMContainer).focus().length == 0) + if(this._widgetContainer._egw.is_popup() && jQuery('[autofocus]', this._DOMContainer).focus().length == 0) { const $input = jQuery('input:visible', this._DOMContainer) // Date fields open the calendar popup on focus .not('.et2_date') - .filter(function () + .filter(function() { // Skip inputs that are out of tab ordering const $this = jQuery(this); @@ -647,19 +663,22 @@ export class etemplate2 // mobile device, focus only if the field is empty (usually means new entry) // should focus always for non-mobile one - if (egwIsMobile() && $input.val() == "" || !egwIsMobile()) $input.focus(); + if(egwIsMobile() && $input.val() == "" || !egwIsMobile()) + { + $input.focus(); + } } // Tell others about it - if (typeof _callback == "function") + if(typeof _callback == "function") { _callback.call(window, this, _name); } - if (app_callback && _callback != app_callback && !_no_et2_ready) + if(app_callback && _callback != app_callback && !_no_et2_ready) { app_callback.call(window, this, _name); } - if (appname && appname != this.app && typeof app[this.app] == "object" && !_no_et2_ready) + if(appname && appname != this.app && typeof app[this.app] == "object" && !_no_et2_ready) { // Loaded a template from a different application? // Let the application that loaded it know too @@ -668,10 +687,10 @@ export class etemplate2 jQuery(this._DOMContainer).trigger('load', this); - if (etemplate2.templates[this.name].attributes.onload) + if(etemplate2.templates[this.name].attributes.onload) { let onload = et2_checkType(etemplate2.templates[this.name].attributes.onload.value, 'js', 'onload', {}); - if (typeof onload === 'string') + if(typeof onload === 'string') { onload = et2_compileLegacyJS(onload, this, this._widgetContainer); } @@ -679,19 +698,22 @@ export class etemplate2 } // Profiling - if (egw.debug_level() >= 4) + if(egw.debug_level() >= 4) { - if (console.timeEnd) + if(console.timeEnd) { console.timeEnd(_name); } - if (console.profileEnd) + if(console.profileEnd) { console.profileEnd(_name); } const end_time = (new Date).getTime(); let gen_time_div = jQuery('#divGenTime_' + appname); - if (!gen_time_div.length) gen_time_div = jQuery('.pageGenTime'); + if(!gen_time_div.length) + { + gen_time_div = jQuery('.pageGenTime'); + } gen_time_div.find('.et2RenderTime').remove(); gen_time_div.append('' + egw.lang('eT2 rendering took %1s', '' + ((end_time - start_time) / 1000)) + ''); } @@ -702,7 +724,7 @@ export class etemplate2 // Load & process try { - if (etemplate2.templates[_name]) + if(etemplate2.templates[_name]) { // Set array managers first, or errors will happen this._widgetContainer.setArrayMgrs(this._createArrayManagers(_data)); @@ -712,10 +734,10 @@ export class etemplate2 return; } } - catch (e) + catch(e) { // weird security exception in IE denying access to template cache in opener - if (e.message == 'Permission denied') + if(e.message == 'Permission denied') { etemplate2.templates = {}; } @@ -730,16 +752,22 @@ export class etemplate2 this._widgetContainer.setArrayMgrs(this._createArrayManagers(_data)); // Asynchronously load the XET file - return et2_loadXMLFromURL(_url, function (_xmldoc) + return et2_loadXMLFromURL(_url, function(_xmldoc) { // Scan for templates and store them - for (let i = 0; i < _xmldoc.childNodes.length; i++) + for(let i = 0; i < _xmldoc.childNodes.length; i++) { const template = _xmldoc.childNodes[i]; - if (template.nodeName.toLowerCase() != "template") continue; + if(template.nodeName.toLowerCase() != "template") + { + continue; + } etemplate2.templates[template.getAttribute("id")] = template; - if (!_name) this.name = template.getAttribute("id"); + if(!_name) + { + this.name = template.getAttribute("id"); + } } _load.apply(this, []); }, this); @@ -754,9 +782,9 @@ export class etemplate2 public isDirty() { let dirty = false; - this._widgetContainer.iterateOver(function (_widget) + this._widgetContainer.iterateOver(function(_widget) { - if (_widget.isDirty && _widget.isDirty()) + if(_widget.isDirty && _widget.isDirty()) { console.info(_widget.id + " is dirty", _widget); dirty = true; @@ -777,16 +805,16 @@ export class etemplate2 // Safari always do the autofill for password field regardless of autocomplete = off // and since there's no other way to switch the autocomplete of, we should switch the // form autocomplete off (e.g. compose dialog, attachment password field) - if (navigator.userAgent.match(/safari/i) && !navigator.userAgent.match(/chrome/i) + if(navigator.userAgent.match(/safari/i) && !navigator.userAgent.match(/chrome/i) && jQuery('input[type="password"]').length > 0) { return; } - if (form) + if(form) { // Stop submit propagation in order to not fire other possible submit events - form.onsubmit = function (e) + form.onsubmit = function(e) { e.stopPropagation(); }; @@ -794,7 +822,7 @@ export class etemplate2 // Firefox give a security warning when transmitting to "about:blank" from a https site // we work around that by giving existing etemplate/empty.html url // Safari shows same warning, thought Chrome userAgent also includes Safari - if (navigator.userAgent.match(/(firefox|safari|iceweasel)/i) && !navigator.userAgent.match(/chrome/i)) + if(navigator.userAgent.match(/(firefox|safari|iceweasel)/i) && !navigator.userAgent.match(/chrome/i)) { jQuery(form).attr({action: egw.webserverUrl + '/api/templates/default/empty.html', method: 'post'}); } @@ -806,46 +834,52 @@ export class etemplate2 private _set_button(button, values) { - if (typeof button == 'string') + if(typeof button == 'string') { button = this._widgetContainer.getWidgetById(button); } // Button parameter used for submit buttons in datagrid // TODO: This should probably go in nextmatch's getValues(), along with selected rows somehow. // I'm just not sure how. - if (button && !values.button) + if(button && !values.button) { let i; values.button = {}; const path = button.getPath(); let target = values; - for (i = 0; i < path.length; i++) + for(i = 0; i < path.length; i++) { - if (!values[path[i]]) values[path[i]] = {}; + if(!values[path[i]]) + { + values[path[i]] = {}; + } target = values[path[i]]; } - if (target != values || button.id.indexOf('[') != -1 && path.length == 0) + if(target != values || button.id.indexOf('[') != -1 && path.length == 0) { let indexes = button.id.split('['); - if (indexes.length > 1) + if(indexes.length > 1) { indexes = [indexes.shift(), indexes.join('[')]; indexes[1] = indexes[1].substring(0, indexes[1].length - 1); const children = indexes[1].split(']['); - if (children.length) + if(children.length) { indexes = jQuery.merge([indexes[0]], children); } } let idx = ''; - for (i = 0; i < indexes.length; i++) + for(i = 0; i < indexes.length; i++) { idx = indexes[i]; - if (!target[idx] || target[idx]['$row_cont']) target[idx] = i < indexes.length - 1 ? {} : true; + if(!target[idx] || target[idx]['$row_cont']) + { + target[idx] = i < indexes.length - 1 ? {} : true; + } target = target[idx]; } } - else if (typeof values.button == 'undefined' || jQuery.isEmptyObject(values.button)) + else if(typeof values.button == 'undefined' || jQuery.isEmptyObject(values.button)) { delete values.button; values[button.id] = true; @@ -860,20 +894,20 @@ export class etemplate2 * @param values * @return et2_widget|null first invalid widget or null, if all are valid */ - isInvalid(container : et2_container|undefined, values : object|undefined) : et2_widget|null + isInvalid(container : et2_container | undefined, values : object | undefined) : et2_widget | null { - if (typeof container === 'undefined') + if(typeof container === 'undefined') { container = this._widgetContainer; } - if (typeof values === 'undefined') + if(typeof values === 'undefined') { values = this.getValues(container); } let invalid = null; - container.iterateOver(function (_widget) + container.iterateOver(function(_widget) { - if (_widget.submit(values) === false) + if(_widget.submit(values) === false) { if(!invalid && !_widget.isValid([])) { @@ -899,7 +933,7 @@ export class etemplate2 { const api = this._widgetContainer.egw(); - if (typeof no_validation == 'undefined') + if(typeof no_validation == 'undefined') { no_validation = false; } @@ -911,22 +945,25 @@ export class etemplate2 // Trigger the submit event let canSubmit = true; let invalid = null; - if (!no_validation) + if(!no_validation) { canSubmit = !(invalid = this.isInvalid(container, values)); } - if (canSubmit) + if(canSubmit) { - if (typeof async == 'undefined' || typeof async == 'string') + if(typeof async == 'undefined' || typeof async == 'string') { api.loading_prompt('et2_submit_spinner', true, api.lang(typeof async == 'string' ? async : 'Please wait...')); async = true; } - if (button) this._set_button(button, values); + if(button) + { + this._set_button(button, values); + } // Create the request object - if (this.menuaction) + if(this.menuaction) { //Autocomplete @@ -936,7 +973,7 @@ export class etemplate2 this.unbind_unload(); - const request = api.json(this.menuaction, [this._etemplate_exec_id, values, no_validation], function () + const request = api.json(this.menuaction, [this._etemplate_exec_id, values, no_validation], function() { api.loading_prompt('et2_submit_spinner', false); }, this, async); @@ -947,7 +984,7 @@ export class etemplate2 this._widgetContainer.egw().debug("warn", "Missing menuaction for submit. Values: ", values); } } - else if (invalid !== null) + else if(invalid !== null) { // Show the first invalid widget, not the last let messages = []; @@ -972,17 +1009,20 @@ export class etemplate2 // Trigger the submit event let canSubmit = true; - this._widgetContainer.iterateOver(function (_widget) + this._widgetContainer.iterateOver(function(_widget) { - if (_widget.submit(values) === false) + if(_widget.submit(values) === false) { canSubmit = false; } }, this, et2_ISubmitListener); - if (canSubmit) + if(canSubmit) { - if (button) this._set_button(button, values); + if(button) + { + this._set_button(button, values); + } // unbind our session-destroy handler, as we are submitting this.unbind_unload(); @@ -1015,15 +1055,15 @@ export class etemplate2 * * @param {et2_widget} _root widget to start iterating */ - getValues(_root: et2_widget) + getValues(_root : et2_widget) { const result = {}; // Iterate over the widget tree - _root.iterateOver(function (_widget) + _root.iterateOver(function(_widget) { // The widget must have an id to be included in the values array - if (_widget.id === undefined || _widget.id === "") + if(_widget.id === undefined || _widget.id === "") { return; } @@ -1034,12 +1074,12 @@ export class etemplate2 // check if id contains a hierachical name, eg. "button[save]" let id = _widget.id; let indexes = id.split('['); - if (indexes.length > 1) + if(indexes.length > 1) { indexes = [indexes.shift(), indexes.join('[')]; indexes[1] = indexes[1].substring(0, indexes[1].length - 1); const children = indexes[1].split(']['); - if (children.length) + if(children.length) { indexes = jQuery.merge([indexes[0]], children); } @@ -1050,16 +1090,16 @@ export class etemplate2 // Set the _target variable to that node let _target = result; - for (var i = 0; i < path.length; i++) + for(var i = 0; i < path.length; i++) { // Create a new object for not-existing path nodes - if (typeof _target[path[i]] === 'undefined') + if(typeof _target[path[i]] === 'undefined') { _target[path[i]] = {}; } // Check whether the path node is really an object - if (typeof _target[path[i]] === 'object') + if(typeof _target[path[i]] === 'object') { _target = _target[path[i]]; } @@ -1071,7 +1111,7 @@ export class etemplate2 } // Handle arrays, eg radio[] - if (id === "") + if(id === "") { id = typeof _target == "undefined" ? 0 : Object.keys(_target).length; } @@ -1079,10 +1119,10 @@ export class etemplate2 const value = _widget.getValue(); // Check whether the entry is really undefined - if (typeof _target[id] != "undefined" && (typeof _target[id] != 'object' || typeof value != 'object')) + if(typeof _target[id] != "undefined" && (typeof _target[id] != 'object' || typeof value != 'object')) { // Don't warn about children of nextmatch header - they're part of nm value - if (!_widget.getParent().instanceOf(et2_nextmatch_header_bar)) + if(!_widget.getParent().instanceOf(et2_nextmatch_header_bar)) { egw.debug("warn", _widget, "Overwriting value of '" + _widget.id + "', id exists twice!"); @@ -1090,10 +1130,10 @@ export class etemplate2 } // Store the value of the widget and reset its dirty flag - if (value !== null) + if(value !== null) { // Merge, if possible (link widget) - if (typeof _target[id] == 'object' && typeof value == 'object') + if(typeof _target[id] == 'object' && typeof value == 'object') { _target[id] = jQuery.extend({}, _target[id], value); } @@ -1102,11 +1142,11 @@ export class etemplate2 _target[id] = value; } } - else if (jQuery.isEmptyObject(_target)) + else if(jQuery.isEmptyObject(_target)) { // Avoid sending back empty sub-arrays _target = result; - for (var i = 0; i < path.length - 1; i++) + for(var i = 0; i < path.length - 1; i++) { _target = _target[path[i]]; } @@ -1141,7 +1181,7 @@ export class etemplate2 let refresh_done = false; // Refresh nextmatches - this._widgetContainer.iterateOver(function (_widget) + this._widgetContainer.iterateOver(function(_widget) { // Trigger refresh _widget.refresh(id, type); @@ -1167,11 +1207,11 @@ export class etemplate2 let refresh_done = false; let app = _app.split('-'); const et2 = etemplate2.getByApplication(app[0]); - for (let i = 0; i < et2.length; i++) + for(let i = 0; i < et2.length; i++) { - if (app[1]) + if(app[1]) { - if (et2[i]['uniqueId'].match(_app)) + if(et2[i]['uniqueId'].match(_app)) { refresh_done = et2[i].refresh(_msg, app[0], _id, _type) || refresh_done; break; @@ -1200,22 +1240,28 @@ export class etemplate2 const deferred = []; // Skip hidden etemplates - if (jQuery(this._DOMContainer).filter(':visible').length === 0) + if(jQuery(this._DOMContainer).filter(':visible').length === 0) { return []; } // Allow any widget to change for printing - this._widgetContainer.iterateOver(function (_widget) + this._widgetContainer.iterateOver(function(_widget) { // Skip widgets from a different etemplate (home) - if (_widget.getInstanceManager() != this) return; + if(_widget.getInstanceManager() != this) + { + return; + } // Skip hidden widgets - if (jQuery(_widget.getDOMNode()).filter(':visible').length === 0) return; + if(jQuery(_widget.getDOMNode()).filter(':visible').length === 0) + { + return; + } const result = _widget.beforePrint(); - if (typeof result == "object" && result.done) + if(typeof result == "object" && result.done) { deferred.push(result); } @@ -1238,7 +1284,7 @@ export class etemplate2 public static getByTemplate(template) { - if (typeof etemplate2._byTemplate[template] != "undefined") + if(typeof etemplate2._byTemplate[template] != "undefined") { return etemplate2._byTemplate[template]; } @@ -1260,9 +1306,9 @@ export class etemplate2 public static getByApplication(app) { let list = []; - for (let name in etemplate2._byTemplate) + for(let name in etemplate2._byTemplate) { - if (name.indexOf(app + ".") == 0) + if(name.indexOf(app + ".") == 0) { list = list.concat(etemplate2._byTemplate[name]); } @@ -1278,13 +1324,13 @@ export class etemplate2 */ public static getById(id) { - for (let name in etemplate2._byTemplate) + for(let name in etemplate2._byTemplate) { - for (let i = 0; i < etemplate2._byTemplate[name].length; i++) + for(let i = 0; i < etemplate2._byTemplate[name].length; i++) { const et = etemplate2._byTemplate[name][i]; - if (et._DOMContainer.getAttribute("id") == id) + if(et._DOMContainer.getAttribute("id") == id) { return et; } @@ -1306,9 +1352,9 @@ export class etemplate2 const data = _response.data; // handle Api\Framework::refresh_opener() - if (Array.isArray(data['refresh-opener'])) + if(Array.isArray(data['refresh-opener'])) { - if (window.opener)// && typeof window.opener.egw_refresh == 'function') + if(window.opener)// && typeof window.opener.egw_refresh == 'function') { var egw = window.egw(opener); egw.refresh.apply(egw, data['refresh-opener']); @@ -1317,22 +1363,22 @@ export class etemplate2 var egw = window.egw(window); // need to set app_header before message, as message temp. replaces app_header - if (typeof data.data == 'object' && typeof data.data.app_header == 'string') + if(typeof data.data == 'object' && typeof data.data.app_header == 'string') { egw.app_header(data.data.app_header, data.data.currentapp || null); delete data.data.app_header; } // handle Api\Framework::message() - if (jQuery.isArray(data.message)) + if(jQuery.isArray(data.message)) { egw.message.apply(egw, data.message); } // handle Api\Framework::window_close(), this will terminate execution - if (data['window-close']) + if(data['window-close']) { - if (typeof data['window-close'] == 'string' && data['window-close'] !== 'true') + if(typeof data['window-close'] == 'string' && data['window-close'] !== 'true') { alert(data['window-close']); } @@ -1341,24 +1387,27 @@ export class etemplate2 } // handle Api\Framework::window_focus() - if (data['window-focus']) + if(data['window-focus']) { window.focus(); } // handle framework.setSidebox calls - if (window.framework && jQuery.isArray(data.setSidebox)) + if(window.framework && jQuery.isArray(data.setSidebox)) { - if (data['fw-target']) data.setSidebox[0] = data['fw-target']; + if(data['fw-target']) + { + data.setSidebox[0] = data['fw-target']; + } window.framework.setSidebox.apply(window.framework, data.setSidebox); } // regular et2 re-load - if (typeof data.url == "string" && typeof data.data === 'object') + if(typeof data.url == "string" && typeof data.data === 'object') { // @ts-ignore - if (this && typeof this.load == 'function') + if(this && typeof this.load == 'function') { // Called from etemplate // set id in case serverside returned a different template @@ -1371,16 +1420,19 @@ export class etemplate2 // Not etemplate const node = document.getElementById(data.DOMNodeID); let uniqueId = data.DOMNodeID; - if (node) + if(node) { - if (node.children.length) + if(node.children.length) { // Node has children already? Check for loading over an // existing etemplate const old = etemplate2.getById(node.id); - if (old) old.clear(); + if(old) + { + old.clear(); + } } - if (data['open_target'] && !uniqueId.match(data['open_target'])) + if(data['open_target'] && !uniqueId.match(data['open_target'])) { uniqueId = data.DOMNodeID.replace('.', '-') + '-' + data['open_target']; } @@ -1406,22 +1458,25 @@ export class etemplate2 public static handle_validation_error(_type, _response) { // Display validation errors - for (let id in _response.data) + for(let id in _response.data) { // @ts-ignore const widget = this._widgetContainer.getWidgetById(id); - if (widget && widget.instanceOf(et2_baseWidget)) + if(widget && widget.instanceOf(et2_baseWidget)) { (widget).showMessage(_response.data[id], 'validation_error'); // Handle validation_error (messages coming back from server as a response) if widget is children of a tabbox let tmpWidget = widget; - while (tmpWidget.getParent() && tmpWidget.getType() != 'tabbox') + while(tmpWidget.getParent() && tmpWidget.getType() != 'tabbox') { tmpWidget = tmpWidget.getParent(); } //Acvtivate the tab where the widget with validation error is located - if (tmpWidget.getType() == 'tabbox') (tmpWidget).activateTab(widget); + if(tmpWidget.getType() == 'tabbox') + { + (tmpWidget).activateTab(widget); + } } } @@ -1446,32 +1501,32 @@ export class etemplate2 //type, req; // unused, but required by plugin signature //Check whether all needed parameters have been passed and call the alertHandler function - if ((typeof res.data.id != 'undefined') && + if((typeof res.data.id != 'undefined') && (typeof res.data.key != 'undefined') && (typeof res.data.value != 'undefined') ) { - if (typeof res.data.etemplate_exec_id == 'undefined' || + if(typeof res.data.etemplate_exec_id == 'undefined' || res.data.etemplate_exec_id != this._etemplate_exec_id) { // Not for this etemplate, but not an error return false; } - if (res.data.key == 'etemplate_exec_id') + if(res.data.key == 'etemplate_exec_id') { this._etemplate_exec_id = res.data.value; return true; } - if (this._widgetContainer == null) + if(this._widgetContainer == null) { // Right etemplate, but it's already been cleared. egw.debug('warn', "Tried to call assign on an un-loaded etemplate", res.data); return false; } const widget = this._widgetContainer.getWidgetById(res.data.id); - if (widget) + if(widget) { - if (typeof widget['set_' + res.data.key] != 'function') + if(typeof widget['set_' + res.data.key] != 'function') { egw.debug('warn', "Cannot set %s attribute %s via JSON assign, no set_%s()", res.data.id, res.data.key, res.data.key); return false; @@ -1481,7 +1536,7 @@ export class etemplate2 widget['set_' + res.data.key].call(widget, res.data.value); return true; } - catch (e) + catch(e) { egw.debug("error", "When assigning %s on %s via AJAX, \n" + (e.message || e + ""), res.data.key, res.data.id, widget); } @@ -1493,7 +1548,10 @@ export class etemplate2 } // make etemplate2 global, as we need it to check an app uses it and then call methods on it -if (typeof window.etemplate2 === 'undefined') window['etemplate2'] = etemplate2; +if(typeof window.etemplate2 === 'undefined') +{ + window['etemplate2'] = etemplate2; +} // Calls etemplate2_handle_response in the context of the object which // requested the response from the server