From 48ddfb173087cbcf68398e5f57474529abe8fa99 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 21 Jan 2020 15:12:29 +0100 Subject: [PATCH] WIP on et2_widget_textbox --- api/js/etemplate/et2_core_DOMWidget.js | 1 + api/js/etemplate/et2_core_DOMWidget.ts | 1 + api/js/etemplate/et2_core_interfaces.js | 7 + api/js/etemplate/et2_core_interfaces.ts | 7 + api/js/etemplate/et2_core_widget.js | 3 + api/js/etemplate/et2_core_widget.ts | 2 +- api/js/etemplate/et2_types.d.ts | 4 +- api/js/etemplate/et2_widget_textbox.js | 1143 +++++++++++------------ api/js/etemplate/et2_widget_textbox.ts | 647 +++++++++++++ 9 files changed, 1217 insertions(+), 598 deletions(-) create mode 100644 api/js/etemplate/et2_widget_textbox.ts diff --git a/api/js/etemplate/et2_core_DOMWidget.js b/api/js/etemplate/et2_core_DOMWidget.js index 40cf9e3f2f..b6589e99f0 100644 --- a/api/js/etemplate/et2_core_DOMWidget.js +++ b/api/js/etemplate/et2_core_DOMWidget.js @@ -32,6 +32,7 @@ require("./et2_core_interfaces"); require("./et2_core_common"); var et2_core_widget_1 = require("./et2_core_widget"); var egw_action_js_1 = require("../egw_action/egw_action.js"); +require("./et2_types"); /** * Abstract widget class which can be inserted into the DOM. All widget classes * deriving from this class have to care about implementing the "getDOMNode" diff --git a/api/js/etemplate/et2_core_DOMWidget.ts b/api/js/etemplate/et2_core_DOMWidget.ts index 1d9dd8d0a8..dce01e8d8e 100644 --- a/api/js/etemplate/et2_core_DOMWidget.ts +++ b/api/js/etemplate/et2_core_DOMWidget.ts @@ -23,6 +23,7 @@ import { egw_getActionManager, egw_getAppObjectManager, egwActionObject, egwAction, EGW_AI_DRAG_OVER, EGW_AI_DRAG_OUT } from '../egw_action/egw_action.js'; +import './et2_types'; /** * Abstract widget class which can be inserted into the DOM. All widget classes diff --git a/api/js/etemplate/et2_core_interfaces.js b/api/js/etemplate/et2_core_interfaces.js index 86bf331979..0ae9f1ec9c 100644 --- a/api/js/etemplate/et2_core_interfaces.js +++ b/api/js/etemplate/et2_core_interfaces.js @@ -21,24 +21,31 @@ function implements_methods(obj, methods) { } return true; } +var et2_IDOMNode = "et2_IDOMNode"; function implements_et2_IDOMNode(obj) { return implements_methods(obj, ["getDOMNode"]); } +var et2_IInput = "et2_IInput"; function implements_et2_IInput(obj) { return implements_methods(obj, ["getValue", "isDirty", "resetDirty", "isValid"]); } +var et2_IResizeable = "et2_IResizeable"; function implements_et2_IResizeable(obj) { return implements_methods(obj, ["resize"]); } +var et2_IAligned = "et2_IAligned"; function implements_et2_IAligned(obj) { return implements_methods(obj, ["get_align"]); } +var et2_ISubmitListener = "et2_ISubmitListener"; function implements_et2_ISubmitListener(obj) { return implements_methods(obj, ["submit"]); } +var et2_IDetachedDOM = "et2_IDetachedDOM"; function implements_et2_IDetachedDOM(obj) { return implements_methods(obj, ["getDetachedAttributes", "getDetachedNodes", "setDetachedAttributes"]); } +var et2_IPrint = "et2_IPrint"; function implements_et2_IPrint(obj) { return implements_methods(obj, ["beforePrint", "afterPrint"]); } diff --git a/api/js/etemplate/et2_core_interfaces.ts b/api/js/etemplate/et2_core_interfaces.ts index ddc1e853bd..bb8a31182f 100644 --- a/api/js/etemplate/et2_core_interfaces.ts +++ b/api/js/etemplate/et2_core_interfaces.ts @@ -49,6 +49,7 @@ interface et2_IDOMNode */ getDOMNode(_sender? : et2_widget) : HTMLElement } +var et2_IDOMNode = "et2_IDOMNode"; function implements_et2_IDOMNode(obj : et2_widget) { return implements_methods(obj, ["getDOMNode"]); @@ -91,6 +92,7 @@ interface et2_IInput */ isValid(messages) : boolean } +var et2_IInput = "et2_IInput"; function implements_et2_IInput(obj : et2_widget) { return implements_methods(obj, ["getValue", "isDirty", "resetDirty", "isValid"]); @@ -106,6 +108,7 @@ interface et2_IResizeable */ resize() : void } +var et2_IResizeable = "et2_IResizeable"; function implements_et2_IResizeable(obj : et2_widget) { return implements_methods(obj, ["resize"]); @@ -118,6 +121,7 @@ interface et2_IAligned { get_align() : string } +var et2_IAligned = "et2_IAligned"; function implements_et2_IAligned(obj : et2_widget) { return implements_methods(obj, ["get_align"]); @@ -138,6 +142,7 @@ interface et2_ISubmitListener */ submit(_values) : void } +var et2_ISubmitListener = "et2_ISubmitListener"; function implements_et2_ISubmitListener(obj : et2_widget) { return implements_methods(obj, ["submit"]); @@ -179,6 +184,7 @@ interface et2_IDetachedDOM setDetachedAttributes(_nodes : HTMLElement[], _values : object) : void } +var et2_IDetachedDOM = "et2_IDetachedDOM"; function implements_et2_IDetachedDOM(obj : et2_widget) { return implements_methods(obj, ["getDetachedAttributes", "getDetachedNodes", "setDetachedAttributes"]); @@ -202,6 +208,7 @@ interface et2_IPrint */ afterPrint() : void } +var et2_IPrint = "et2_IPrint"; function implements_et2_IPrint(obj : et2_widget) { return implements_methods(obj, ["beforePrint", "afterPrint"]); diff --git a/api/js/etemplate/et2_core_widget.js b/api/js/etemplate/et2_core_widget.js index d751d68d65..32d0395de7 100644 --- a/api/js/etemplate/et2_core_widget.js +++ b/api/js/etemplate/et2_core_widget.js @@ -121,6 +121,9 @@ var et2_widget = /** @class */ (function (_super) { */ function et2_widget(_parent, _attrs, _child) { var _this = _super.call(this) || this; + // Set the legacyOptions array to the names of the properties the "options" + // attribute defines. + _this.legacyOptions = []; _this._children = []; _this._mgrs = {}; /** diff --git a/api/js/etemplate/et2_core_widget.ts b/api/js/etemplate/et2_core_widget.ts index d7d3f94475..9c276afa2f 100644 --- a/api/js/etemplate/et2_core_widget.ts +++ b/api/js/etemplate/et2_core_widget.ts @@ -174,7 +174,7 @@ export class et2_widget extends ClassWithAttributes // Set the legacyOptions array to the names of the properties the "options" // attribute defines. - legacyOptions: []; + legacyOptions: string[] = []; private _type: string; id: string; diff --git a/api/js/etemplate/et2_types.d.ts b/api/js/etemplate/et2_types.d.ts index 6383250267..d74a881a53 100644 --- a/api/js/etemplate/et2_types.d.ts +++ b/api/js/etemplate/et2_types.d.ts @@ -23,13 +23,13 @@ declare var et2_validTypes : string[]; declare var et2_typeDefaults : object; //declare const et2_no_init : object; declare var et2_editableWidget : any; -declare var et2_IDOMNode : any; +/*declare var et2_IDOMNode : any; declare var et2_IInput : any; declare var et2_IResizeable : any; declare var et2_IAligned : any; declare var et2_ISubmitListener : any; declare var et2_IDetachedDOM : any; -declare var et2_IPrint : any; +declare var et2_IPrint : any;*/ declare var et2_registry : {}; declare var et2_dataview : any; declare var et2_dataview_controller : any; diff --git a/api/js/etemplate/et2_widget_textbox.js b/api/js/etemplate/et2_widget_textbox.js index ec8da5c351..67d59932b3 100644 --- a/api/js/etemplate/et2_widget_textbox.js +++ b/api/js/etemplate/et2_widget_textbox.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - JS Textbox object * @@ -6,615 +7,567 @@ * @subpackage api * @link http://www.egroupware.org * @author Andreas Stöckel - * @copyright Stylite 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; - et2_core_inputWidget; - et2_core_valueWidget; + /vendor/bower-asset/jquery/dist/jquery.js; + 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_DOMWidget_1 = require("./et2_core_DOMWidget"); +var et2_core_valueWidget_1 = require("./et2_core_valueWidget"); +var et2_core_inputWidget_1 = require("./et2_core_inputWidget"); +require("./et2_types"); /** * Class which implements the "textbox" XET-Tag * * @augments et2_inputWidget */ -var et2_textbox = (function(){ "use strict"; return et2_inputWidget.extend([et2_IResizeable], -{ - attributes: { - "multiline": { - "name": "multiline", - "type": "boolean", - "default": false, - "description": "If true, the textbox is a multiline edit field." - }, - "size": { - "name": "Size", - "type": "integer", - "default": et2_no_init, - "description": "Field width" - }, - "maxlength": { - "name": "Maximum length", - "type": "integer", - "default": et2_no_init, - "description": "Maximum number of characters allowed" - }, - "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." - }, - // These for multi-line - "rows": { - "name": "Rows", - "type": "integer", - "default": -1, - "description": "Multiline field height - better to use CSS" - }, - "cols": { - "name": "Size", - "type": "integer", - "default": -1, - "description": "Multiline field width - better to use CSS" - }, - "validator": { - "name": "Validator", - "type": "string", - "default": et2_no_init, - "description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'" - }, - "autocomplete": { - "name": "Autocomplete", - "type": "string", - "default": "", - "description": "Weither or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value for type password is set to off." - }, - onkeypress: { - name: "onKeypress", - type: "js", - default: et2_no_init, - description: "JS code or app.$app.$method called when key is pressed, return false cancels it." - } - }, - - legacyOptions: ["size", "maxlength", "validator"], - - /** - * Constructor - * - * @memberOf et2_textbox - */ - init: function() { - this._super.apply(this, arguments); - - this.input = null; - - this.createInputWidget(); - }, - - createInputWidget: function() { - if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) - { - this.input = jQuery(document.createElement("textarea")); - - if (this.options.rows > 0) - { - this.input.attr("rows", this.options.rows); - } - - if (this.options.cols > 0) - { - this.input.attr("cols", this.options.cols); - } - } - else - { - this.input = jQuery(document.createElement("input")); - switch(this.options.type) - { - case "passwd": - this.input.attr("type", "password"); - // Make autocomplete default value off for password field - // seems browsers not respecting 'off' anymore and started to - // impelement a new key called "new-password" considered as switching - // autocomplete off. - // https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion - if (this.options.autocomplete === "" || this.options.autocomplete == "off") this.options.autocomplete = "new-password"; - break; - case "hidden": - this.input.attr("type", "hidden"); - break; - } - if (this.options.autocomplete) this.input.attr("autocomplete", this.options.autocomplete); - } - - if(this.options.size) { - this.set_size(this.options.size); - } - if(this.options.blur) { - this.set_blur(this.options.blur); - } - if(this.options.readonly) { - this.set_readonly(true); - } - this.input.addClass("et2_textbox"); - this.setDOMNode(this.input[0]); - if(this.options.value) - { - this.set_value(this.options.value); - } - if (this.options.onkeypress && typeof this.options.onkeypress == 'function') - { - var self = this; - this.input.keypress(function(_ev) - { - return self.options.onkeypress.call(this, _ev, self); - }); - } - }, - - /** - * Override the parent set_id method to manuipulate the input DOM node - * - * @param {type} _value - * @returns {undefined} - */ - set_id: function(_value) - { - this._super.apply(this,arguments); - // Remove the name attribute inorder to affect autocomplete="off" - // for no password save. ATM seems all browsers ignore autocomplete for - // input field inside the form - if (this.options.type === "passwd" - && this.options.autocomplete === "off") this.input.removeAttr('name'); - }, - - destroy: function() { - var node = this.getInputNode(); - if (node) jQuery(node).unbind("keypress"); - - this._super.apply(this, arguments); - }, - - getValue: function() - { - if(this.options && this.options.blur && this.input.val() == this.options.blur) return ""; - return this._super.apply(this, arguments); - }, - - /** - * Clientside validation using regular expression in "validator" attribute - * - * @param {array} _messages - */ - isValid: function(_messages) - { - var ok = true; - // Check input is valid - if(this.options && this.options.validator && !this.options.readonly && !this.disabled) - { - if (typeof this.options.validator == 'string') - { - var parts = this.options.validator.split('/'); - var flags = parts.pop(); - if (parts.length < 2 || parts[0] !== '') - { - _messages.push(this.egw().lang("'%1' has an invalid format !!!", this.options.validator)); - return false; // show invalid expression - } - parts.shift(); - this.options.validator = new RegExp(parts.join('/'), flags); - } - var value = this.getValue(); - if (!(ok = this.options.validator.test(value))) - { - _messages.push(this.egw().lang("'%1' has an invalid format !!!", value)); - } - } - return this._super.apply(this, arguments) && ok; - }, - - /** - * Set input widget size - * @param _size Rather arbitrary size units, approximately characters - */ - set_size: function(_size) { - if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) - { - this.input.css('width', _size + "em"); - } - else if (typeof _size != 'undefined' && _size != this.input.attr("size")) - { - this.size = _size; - this.input.attr("size", this.size); - } - }, - - /** - * Set maximum characters allowed - * @param _size Max characters allowed - */ - set_maxlength: function(_size) { - if (typeof _size != 'undefined' && _size != this.input.attr("maxlength")) - { - this.maxLength = _size; - this.input.attr("maxLength", this.maxLength); - } - }, - - /** - * Set HTML readonly attribute. - * Do not confuse this with etemplate readonly, which would use et_textbox_ro instead - * @param _readonly Boolean - */ - set_readonly: function(_readonly) { - this.input.attr("readonly", _readonly); - this.input.toggleClass('et2_textbox_ro', _readonly); - }, - - set_blur: function(_value) { - if(_value) { - this.input.attr("placeholder", this.egw().lang(_value) + ""); // HTML5 - if(!this.input[0].placeholder) { - // Not HTML5 - if(this.input.val() == "") this.input.val(this.egw().lang(this.options.blur)); - this.input.focus(this,function(e) { - if(e.data.input.val() == e.data.egw().lang(e.data.options.blur)) e.data.input.val(""); - }).blur(this, function(e) { - if(e.data.input.val() == "") e.data.input.val(e.data.egw().lang(e.data.options.blur)); - }); - } - } else { - if (!this.getValue()) this.input.val(''); - this.input.removeAttr("placeholder"); - } - this.options.blur = _value; - }, - - set_autocomplete: function(_value) { - this.options.autocomplete = _value; - this.input.attr('autocomplete', _value); - }, - - resize: function (_height) - { - if (_height && this.options.multiline) - { - // apply the ratio - _height = (this.options.resize_ratio != '')? _height * this.options.resize_ratio: _height; - if (_height != 0) - { - this.input.height(this.input.height() + _height); - // resize parent too, so mailvelope injected into parent inherits its height - this.input.parent().height(this.input.parent().height()+_height); - } - } - } -});}).call(this); -et2_register_widget(et2_textbox, ["textbox", "passwd", "hidden"]); - +var et2_textbox = /** @class */ (function (_super_1) { + __extends(et2_textbox, _super_1); + /** + * Constructor + */ + function et2_textbox(_parent, _attrs, _child) { + var _this = + // Call the inherited constructor + _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_core_DOMWidget_1.et2_DOMWidget._attributes, _child || {})) || this; + _this.legacyOptions = ["size", "maxlength", "validator"]; + _this.input = null; + _this.input = null; + _this.createInputWidget(); + return _this; + } + et2_textbox.prototype.createInputWidget = function () { + if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) { + this.input = jQuery(document.createElement("textarea")); + if (this.options.rows > 0) { + this.input.attr("rows", this.options.rows); + } + if (this.options.cols > 0) { + this.input.attr("cols", this.options.cols); + } + } + else { + this.input = jQuery(document.createElement("input")); + switch (this.options.type) { + case "passwd": + this.input.attr("type", "password"); + // Make autocomplete default value off for password field + // seems browsers not respecting 'off' anymore and started to + // impelement a new key called "new-password" considered as switching + // autocomplete off. + // https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion + if (this.options.autocomplete === "" || this.options.autocomplete == "off") + this.options.autocomplete = "new-password"; + break; + case "hidden": + this.input.attr("type", "hidden"); + break; + } + if (this.options.autocomplete) + this.input.attr("autocomplete", this.options.autocomplete); + } + if (this.options.size) { + this.set_size(this.options.size); + } + if (this.options.blur) { + this.set_blur(this.options.blur); + } + if (this.options.readonly) { + this.set_readonly(true); + } + this.input.addClass("et2_textbox"); + this.setDOMNode(this.input[0]); + if (this.options.value) { + this.set_value(this.options.value); + } + if (this.options.onkeypress && typeof this.options.onkeypress == 'function') { + var self = this; + this.input.keypress(function (_ev) { + return self.options.onkeypress.call(this, _ev, self); + }); + } + }; + /** + * Override the parent set_id method to manuipulate the input DOM node + * + * @param {type} _value + * @returns {undefined} + */ + et2_textbox.prototype.set_id = function (_value) { + _super_1.prototype.set_id.call(this, _value); + // Remove the name attribute inorder to affect autocomplete="off" + // for no password save. ATM seems all browsers ignore autocomplete for + // input field inside the form + if (this.options.type === "passwd" + && this.options.autocomplete === "off") + this.input.removeAttr('name'); + }; + et2_textbox.prototype.destroy = function () { + var node = this.getInputNode(); + if (node) + jQuery(node).unbind("keypress"); + _super_1.prototype.destroy.call(this); + }; + et2_textbox.prototype.getValue = function () { + if (this.options && this.options.blur && this.input.val() == this.options.blur) + return ""; + return _super_1.prototype.getValue.call(this); + }; + /** + * Clientside validation using regular expression in "validator" attribute + * + * @param {array} _messages + */ + et2_textbox.prototype.isValid = function (_messages) { + var ok = true; + // Check input is valid + if (this.options && this.options.validator && !this.options.readonly && !this.disabled) { + if (typeof this.options.validator == 'string') { + var parts = this.options.validator.split('/'); + var flags = parts.pop(); + if (parts.length < 2 || parts[0] !== '') { + _messages.push(this.egw().lang("'%1' has an invalid format !!!", this.options.validator)); + return false; // show invalid expression + } + parts.shift(); + this.options.validator = new RegExp(parts.join('/'), flags); + } + var value = this.getValue(); + if (!(ok = this.options.validator.test(value))) { + _messages.push(this.egw().lang("'%1' has an invalid format !!!", value)); + } + } + return _super_1.prototype.isValid.call(this, _messages) && ok; + }; + /** + * Set input widget size + * @param _size Rather arbitrary size units, approximately characters + */ + et2_textbox.prototype.set_size = function (_size) { + if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) { + this.input.css('width', _size + "em"); + } + else if (typeof _size != 'undefined' && _size != this.input.attr("size")) { + this.size = _size; + this.input.attr("size", this.size); + } + }; + /** + * Set maximum characters allowed + * @param _size Max characters allowed + */ + et2_textbox.prototype.set_maxlength = function (_size) { + if (typeof _size != 'undefined' && _size != this.input.attr("maxlength")) { + this.maxLength = _size; + this.input.attr("maxLength", this.maxLength); + } + }; + /** + * Set HTML readonly attribute. + * Do not confuse this with etemplate readonly, which would use et_textbox_ro instead + * @param _readonly Boolean + */ + et2_textbox.prototype.set_readonly = function (_readonly) { + this.input.attr("readonly", _readonly); + this.input.toggleClass('et2_textbox_ro', _readonly); + }; + et2_textbox.prototype.set_blur = function (_value) { + if (_value) { + this.input.attr("placeholder", this.egw().lang(_value) + ""); // HTML5 + if (!this.input[0].placeholder) { + // Not HTML5 + if (this.input.val() == "") + this.input.val(this.egw().lang(this.options.blur)); + this.input.focus(this, function (e) { + if (e.data.input.val() == e.data.egw().lang(e.data.options.blur)) + e.data.input.val(""); + }).blur(this, function (e) { + if (e.data.input.val() == "") + e.data.input.val(e.data.egw().lang(e.data.options.blur)); + }); + } + } + else { + if (!this.getValue()) + this.input.val(''); + this.input.removeAttr("placeholder"); + } + this.options.blur = _value; + }; + et2_textbox.prototype.set_autocomplete = function (_value) { + this.options.autocomplete = _value; + this.input.attr('autocomplete', _value); + }; + et2_textbox.prototype.resize = function (_height) { + if (_height && this.options.multiline) { + // apply the ratio + _height = (this.options.resize_ratio != '') ? _height * this.options.resize_ratio : _height; + if (_height != 0) { + this.input.height(this.input.height() + _height); + // resize parent too, so mailvelope injected into parent inherits its height + this.input.parent().height(this.input.parent().height() + _height); + } + } + }; + et2_textbox._attributes = { + "multiline": { + "name": "multiline", + "type": "boolean", + "default": false, + "description": "If true, the textbox is a multiline edit field." + }, + "size": { + "name": "Size", + "type": "integer", + "default": et2_no_init, + "description": "Field width" + }, + "maxlength": { + "name": "Maximum length", + "type": "integer", + "default": et2_no_init, + "description": "Maximum number of characters allowed" + }, + "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." + }, + // These for multi-line + "rows": { + "name": "Rows", + "type": "integer", + "default": -1, + "description": "Multiline field height - better to use CSS" + }, + "cols": { + "name": "Size", + "type": "integer", + "default": -1, + "description": "Multiline field width - better to use CSS" + }, + "validator": { + "name": "Validator", + "type": "string", + "default": et2_no_init, + "description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'" + }, + "autocomplete": { + "name": "Autocomplete", + "type": "string", + "default": "", + "description": "Weither or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value for type password is set to off." + }, + onkeypress: { + name: "onKeypress", + type: "js", + default: et2_no_init, + description: "JS code or app.$app.$method called when key is pressed, return false cancels it." + } + }; + return et2_textbox; +}(et2_core_inputWidget_1.et2_inputWidget)); +et2_core_widget_1.et2_register_widget(et2_textbox, ["textbox", "passwd", "hidden"]); /** * et2_textbox_ro is the dummy readonly implementation of the textbox. * * @augments et2_valueWidget */ -var et2_textbox_ro = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDetachedDOM], -{ - /** - * Ignore all more advanced attributes. - */ - attributes: { - "multiline": { - "ignore": true - }, - "maxlength": { - "ignore": true - }, - "onchange": { - "ignore": true - }, - "rows": { - "ignore": true - }, - "cols": { - "ignore": true - }, - "size": { - "ignore": true - }, - "needed": { - "ignore": true - } - }, - - /** - * Constructor - * - * @memberOf et2_textbox_ro - */ - init: function() { - this._super.apply(this, arguments); - - this.value = ""; - this.span = jQuery(document.createElement("label")) - .addClass("et2_label"); - this.value_span = jQuery(document.createElement("span")) - .addClass("et2_textbox_ro") - .appendTo(this.span); - - this.setDOMNode(this.span[0]); - }, - - set_label: function(label) - { - // Remove current label - this.span.contents() - .filter(function(){ return this.nodeType == 3; }).remove(); - - var parts = et2_csvSplit(label, 2, "%s"); - this.span.prepend(parts[0]); - this.span.append(parts[1]); - this.label = label; - - // add class if label is empty - this.span.toggleClass('et2_label_empty', !label || !parts[0]); - }, - set_value: function(_value) - { - this.value = _value; - - if(!_value) - { - _value = ""; - } - if (this.label !="") - { - this.span.removeClass('et2_label_empty'); - } - else - { - this.span.addClass('et2_label_empty'); - } - this.value_span.text(_value); - }, - /** - * Code for implementing et2_IDetachedDOM - * - * @param {array} _attrs array to add further attributes to - */ - getDetachedAttributes: function(_attrs) - { - _attrs.push("value", "label"); - }, - - getDetachedNodes: function() - { - return [this.span[0], this.value_span[0]]; - }, - - setDetachedAttributes: function(_nodes, _values) - { - this.span = jQuery(_nodes[0]); - this.value_span = jQuery(_nodes[1]); - if(typeof _values["label"] != 'undefined') - { - this.set_label(_values["label"]); - } - if(typeof _values["value"] != 'undefined') - { - this.set_value(_values["value"]); - } - } -});}).call(this); -et2_register_widget(et2_textbox_ro, ["textbox_ro"]); - +var et2_textbox_ro = /** @class */ (function (_super_1) { + __extends(et2_textbox_ro, _super_1); + /** + * Constructor + */ + function et2_textbox_ro(_parent, _attrs, _child) { + var _this = + // Call the inherited constructor + _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_core_DOMWidget_1.et2_DOMWidget._attributes, _child || {})) || this; + _this.value = ""; + _this.span = jQuery(document.createElement("label")) + .addClass("et2_label"); + _this.value_span = jQuery(document.createElement("span")) + .addClass("et2_textbox_ro") + .appendTo(_this.span); + _this.setDOMNode(_this.span[0]); + return _this; + } + et2_textbox_ro.prototype.set_label = function (label) { + // Remove current label + this.span.contents() + .filter(function () { return this.nodeType == 3; }).remove(); + var parts = et2_csvSplit(label, 2, "%s"); + this.span.prepend(parts[0]); + this.span.append(parts[1]); + this.label = label; + // add class if label is empty + this.span.toggleClass('et2_label_empty', !label || !parts[0]); + }; + et2_textbox_ro.prototype.set_value = function (_value) { + this.value = _value; + if (!_value) { + _value = ""; + } + if (this.label != "") { + this.span.removeClass('et2_label_empty'); + } + else { + this.span.addClass('et2_label_empty'); + } + this.value_span.text(_value); + }; + /** + * Code for implementing et2_IDetachedDOM + * + * @param {array} _attrs array to add further attributes to + */ + et2_textbox_ro.prototype.getDetachedAttributes = function (_attrs) { + _attrs.push("value", "label"); + }; + et2_textbox_ro.prototype.getDetachedNodes = function () { + return [this.span[0], this.value_span[0]]; + }; + et2_textbox_ro.prototype.setDetachedAttributes = function (_nodes, _values) { + this.span = jQuery(_nodes[0]); + this.value_span = jQuery(_nodes[1]); + if (typeof _values["label"] != 'undefined') { + this.set_label(_values["label"]); + } + if (typeof _values["value"] != 'undefined') { + this.set_value(_values["value"]); + } + }; + /** + * Ignore all more advanced attributes. + */ + et2_textbox_ro._attributes = { + "multiline": { + "ignore": true + }, + "maxlength": { + "ignore": true + }, + "onchange": { + "ignore": true + }, + "rows": { + "ignore": true + }, + "cols": { + "ignore": true + }, + "size": { + "ignore": true + }, + "needed": { + "ignore": true + } + }; + return et2_textbox_ro; +}(et2_core_valueWidget_1.et2_valueWidget)); +et2_core_widget_1.et2_register_widget(et2_textbox_ro, ["textbox_ro"]); /** * et2_searchbox is a widget which provides a collapsable input search * with on searching indicator and clear handler regardless of any browser limitation. - * - * @type type */ -var et2_searchbox = (function(){ "use strict"; return et2_textbox.extend( -{ - /** - * Advanced attributes - */ - attributes: { - overlay:{ - name:"Overlay searchbox", - type:"boolean", - default:false, - description:"Define wheter the searchbox overlays while it's open (true) or stay as solid box infront of the search button (false). Default is false." - }, - fix: { - name:"Fix searchbox", - type:"boolean", - default:true, - description:"Define wheter the searchbox should be a fix input field or flexible search button. Default is true (fix)." - } - }, - - /** - * Constructor - * - * @memberOf et2_searchbox - */ - init: function() { - this.value = ""; - this.div = jQuery(document.createElement('div')) - .addClass('et2_searchbox'); - this.flex = jQuery(document.createElement('div')) - .addClass('flex') - .appendTo(this.div); - this._super.apply(this, arguments); - this.setDOMNode(this.div[0]); - this._createWidget(); - }, - - _createWidget:function() - { - var self = this; - if (this.options.overlay) this.flex.addClass('overlay'); - // search button indicator - // no need to create search button if it's a fix search field - if (!this.options.fix) - { - this.button = et2_createWidget('button',{image:"search","background_image":"1"},this); - this.button.onclick= function(){ - self._show_hide(jQuery(self.flex).hasClass('hide')); - self.search.input.focus(); - }; - this.div.prepend(this.button.getDOMNode()); - } - // input field - this.search = et2_createWidget('textbox',{"blur":egw.lang("search"), - onkeypress:function(event) { - if(event.which == 13) - { - event.preventDefault(); - self.getInstanceManager().autocomplete_fixer(); - // Use a timeout to make sure we get the autocomplete value, - // if one was chosen, instead of what was actually typed. - // Chrome doesn't need this, but FF does. - window.setTimeout(function() { - self.set_value(self.search.input.val()); - self.change(); - },0); - } - }},this); - // Autocomplete needs name - this.search.input.attr('name', this.id||'searchbox'); - this.search.input.on({ - keyup:function(event) - { - self.clear.toggle(self.get_value() !='' || !self.options.fix); - if(event.which == 27) // Escape - { - // Excape clears search - self.set_value(''); - } - }, - - blur: function(event){ - if (egwIsMobile()) return; - if (!event.relatedTarget || !jQuery(event.relatedTarget.parentNode).hasClass('et2_searchbox')) - { - self._show_hide((!self.options.overlay && self.get_value())); - } - if (typeof self.oldValue !='undefined' && self._oldValue != self.get_value()) { - self.change(); - } - }, - mousedown:function(event){ - if (event.target.type == 'span') event.stopImmidatePropagation(); - } - }); - this.flex.append(this.search.getDOMNode()); - - // clear button implementation - this.clear = jQuery(document.createElement('span')) - .addClass('ui-icon clear') - .toggle(!this.options.fix || (this._oldValue != '' && !jQuery.isEmptyObject(this._oldValue))) - .on('mousedown',function(event){ - event.preventDefault(); - }) - .on('click',function(event) { - if (self.get_value()){ - self.search.input.val(''); - self.search.input.focus(); - self._show_hide(true); - if (self._oldValue) self.change(); - } - else - { - self._show_hide(false); - } - if (self.options.fix) self.clear.hide(); - }) - .appendTo(this.flex); - }, - - /** - * Show/hide search field - * @param {boolean} _stat true means show and false means hide - */ - _show_hide: function(_stat) - { - // Not applied for fix option - if (this.options.fix) return; - - jQuery(this.flex).toggleClass('hide',!_stat); - jQuery(this.getDOMNode()).toggleClass('expanded', _stat); - }, - - /** - * toggle search button status based on value - */ - _searchToggleState:function() - { - if (this.options.fix || egwIsMobile()) return; - - if (!this.get_value()) - { - jQuery(this.button.getDOMNode()).removeClass('toolbar_toggled'); - this.button.set_statustext(''); - } - else - { - jQuery(this.button.getDOMNode()).addClass('toolbar_toggled'); - this.button.set_statustext(egw.lang("search for '%1'", this.get_value())); - } - }, - - /** - * override change function in order to preset the toggle state - */ - change:function() - { - this._searchToggleState(); - - this._super.apply(this,arguments); - }, - - - get_value:function(){ - return this.search.input.val(); - }, - - set_value: function (_value){ - this._super.apply(this,arguments); - if (this.search) this.search.input.val(_value); - }, - - /** - * override doLoadingFinished in order to set initial state - */ - doLoadingFinished: function() - { - this._super.apply(this,arguments); - if (!this.get_value()) { - this._show_hide(false); - } - else{ - this._show_hide(!this.options.overlay); - this._searchToggleState(); - } - }, - - /** - * Overrride attachToDOM in order to unbind change handler - */ - attachToDOM: function() { - this._super.apply(this,arguments); - var node = this.getInputNode(); - if (node) - { - jQuery(node).off('.et2_inputWidget'); - } - }, -});}).call(this); -et2_register_widget(et2_searchbox, ["searchbox"]); +var et2_searchbox = /** @class */ (function (_super_1) { + __extends(et2_searchbox, _super_1); + /** + * Constructor + */ + function et2_searchbox(_parent, _attrs, _child) { + var _this = + // Call the inherited constructor + _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_core_DOMWidget_1.et2_DOMWidget._attributes, _child || {})) || this; + _this.value = ""; + _this.div = jQuery(document.createElement('div')) + .addClass('et2_searchbox'); + _this.flex = jQuery(document.createElement('div')) + .addClass('flex') + .appendTo(_this.div); + //this._super.apply(this, arguments); + _this.setDOMNode(_this.div[0]); + _this._createWidget(); + return _this; + } + et2_searchbox.prototype._createWidget = function () { + var self = this; + if (this.options.overlay) + this.flex.addClass('overlay'); + // search button indicator + // no need to create search button if it's a fix search field + if (!this.options.fix) { + this.button = et2_core_widget_1.et2_createWidget('button', { image: "search", "background_image": "1" }, this); + this.button.onclick = function () { + self._show_hide(jQuery(self.flex).hasClass('hide')); + self.search.input.focus(); + }; + this.div.prepend(this.button.getDOMNode()); + } + // input field + this.search = et2_core_widget_1.et2_createWidget('textbox', { "blur": egw.lang("search"), + onkeypress: function (event) { + if (event.which == 13) { + event.preventDefault(); + self.getInstanceManager().autocomplete_fixer(); + // Use a timeout to make sure we get the autocomplete value, + // if one was chosen, instead of what was actually typed. + // Chrome doesn't need this, but FF does. + window.setTimeout(function () { + self.set_value(self.search.input.val()); + self.change(); + }, 0); + } + } }, this); + // Autocomplete needs name + this.search.input.attr('name', this.id || 'searchbox'); + this.search.input.on({ + keyup: function (event) { + self.clear.toggle(self.get_value() != '' || !self.options.fix); + if (event.which == 27) // Escape + { + // Excape clears search + self.set_value(''); + } + }, + blur: function (event) { + if (egwIsMobile()) + return; + if (!event.relatedTarget || !jQuery(event.relatedTarget.parentNode).hasClass('et2_searchbox')) { + self._show_hide((!self.options.overlay && self.get_value())); + } + if (typeof self.oldValue != 'undefined' && self._oldValue != self.get_value()) { + self.change(); + } + }, + mousedown: function (event) { + if (event.target.type == 'span') + event.stopImmidatePropagation(); + } + }); + this.flex.append(this.search.getDOMNode()); + // clear button implementation + this.clear = jQuery(document.createElement('span')) + .addClass('ui-icon clear') + .toggle(!this.options.fix || (this._oldValue != '' && !jQuery.isEmptyObject(this._oldValue))) + .on('mousedown', function (event) { + event.preventDefault(); + }) + .on('click', function (event) { + if (self.get_value()) { + self.search.input.val(''); + self.search.input.focus(); + self._show_hide(true); + if (self._oldValue) + self.change(); + } + else { + self._show_hide(false); + } + if (self.options.fix) + self.clear.hide(); + }) + .appendTo(this.flex); + }; + /** + * Show/hide search field + * @param {boolean} _stat true means show and false means hide + */ + et2_searchbox.prototype._show_hide = function (_stat) { + // Not applied for fix option + if (this.options.fix) + return; + jQuery(this.flex).toggleClass('hide', !_stat); + jQuery(this.getDOMNode()).toggleClass('expanded', _stat); + }; + /** + * toggle search button status based on value + */ + et2_searchbox.prototype._searchToggleState = function () { + if (this.options.fix || egwIsMobile()) + return; + if (!this.get_value()) { + jQuery(this.button.getDOMNode()).removeClass('toolbar_toggled'); + this.button.set_statustext(''); + } + else { + jQuery(this.button.getDOMNode()).addClass('toolbar_toggled'); + this.button.set_statustext(egw.lang("search for '%1'", this.get_value())); + } + }; + /** + * override change function in order to preset the toggle state + */ + et2_searchbox.prototype.change = function () { + this._searchToggleState(); + this._super.apply(this, arguments); + }; + et2_searchbox.prototype.get_value = function () { + return this.search.input.val(); + }; + et2_searchbox.prototype.set_value = function (_value) { + _super_1.prototype.set_value.call(this, _value); + if (this.search) + this.search.input.val(_value); + }; + /** + * override doLoadingFinished in order to set initial state + */ + et2_searchbox.prototype.doLoadingFinished = function () { + _super_1.prototype.doLoadingFinished.call(this); + if (!this.get_value()) { + this._show_hide(false); + } + else { + this._show_hide(!this.options.overlay); + this._searchToggleState(); + } + }; + /** + * Overrride attachToDOM in order to unbind change handler + */ + et2_searchbox.prototype.attachToDOM = function () { + _super_1.prototype.attachToDOM.call(this); + var node = this.getInputNode(); + if (node) { + jQuery(node).off('.et2_inputWidget'); + } + }; + /** + * Advanced attributes + */ + et2_searchbox._attributes = { + overlay: { + name: "Overlay searchbox", + type: "boolean", + default: false, + description: "Define wheter the searchbox overlays while it's open (true) or stay as solid box infront of the search button (false). Default is false." + }, + fix: { + name: "Fix searchbox", + type: "boolean", + default: true, + description: "Define wheter the searchbox should be a fix input field or flexible search button. Default is true (fix)." + } + }; + return et2_searchbox; +}(et2_textbox)); +et2_core_widget_1.et2_register_widget(et2_searchbox, ["searchbox"]); diff --git a/api/js/etemplate/et2_widget_textbox.ts b/api/js/etemplate/et2_widget_textbox.ts new file mode 100644 index 0000000000..f8e96b8a8d --- /dev/null +++ b/api/js/etemplate/et2_widget_textbox.ts @@ -0,0 +1,647 @@ +/** + * EGroupware eTemplate2 - JS Textbox object + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @author Andreas Stöckel + */ + +/*egw:uses + /vendor/bower-asset/jquery/dist/jquery.js; + 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_DOMWidget } from './et2_core_DOMWidget' +import { et2_valueWidget } from './et2_core_valueWidget' +import { et2_inputWidget } from './et2_core_inputWidget' +import './et2_types'; + +/** + * Class which implements the "textbox" XET-Tag + * + * @augments et2_inputWidget + */ +class et2_textbox extends et2_inputWidget implements et2_IResizeable +{ + static readonly _attributes : any = { + "multiline": { + "name": "multiline", + "type": "boolean", + "default": false, + "description": "If true, the textbox is a multiline edit field." + }, + "size": { + "name": "Size", + "type": "integer", + "default": et2_no_init, + "description": "Field width" + }, + "maxlength": { + "name": "Maximum length", + "type": "integer", + "default": et2_no_init, + "description": "Maximum number of characters allowed" + }, + "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." + }, + // These for multi-line + "rows": { + "name": "Rows", + "type": "integer", + "default": -1, + "description": "Multiline field height - better to use CSS" + }, + "cols": { + "name": "Size", + "type": "integer", + "default": -1, + "description": "Multiline field width - better to use CSS" + }, + "validator": { + "name": "Validator", + "type": "string", + "default": et2_no_init, + "description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'" + }, + "autocomplete": { + "name": "Autocomplete", + "type": "string", + "default": "", + "description": "Weither or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value for type password is set to off." + }, + onkeypress: { + name: "onKeypress", + type: "js", + default: et2_no_init, + description: "JS code or app.$app.$method called when key is pressed, return false cancels it." + } + }; + + legacyOptions: string[] = ["size", "maxlength", "validator"]; + input: JQuery = null; + size: number|string; + maxLength: number|string; + + /** + * Constructor + */ + constructor(_parent, _attrs? : WidgetConfig, _child? : object) + { + // Call the inherited constructor + super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {})); + + + this.input = null; + + this.createInputWidget(); + } + + createInputWidget() + { + if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) + { + this.input = jQuery(document.createElement("textarea")); + + if (this.options.rows > 0) + { + this.input.attr("rows", this.options.rows); + } + + if (this.options.cols > 0) + { + this.input.attr("cols", this.options.cols); + } + } + else + { + this.input = jQuery(document.createElement("input")); + switch(this.options.type) + { + case "passwd": + this.input.attr("type", "password"); + // Make autocomplete default value off for password field + // seems browsers not respecting 'off' anymore and started to + // impelement a new key called "new-password" considered as switching + // autocomplete off. + // https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion + if (this.options.autocomplete === "" || this.options.autocomplete == "off") this.options.autocomplete = "new-password"; + break; + case "hidden": + this.input.attr("type", "hidden"); + break; + } + if (this.options.autocomplete) this.input.attr("autocomplete", this.options.autocomplete); + } + + if(this.options.size) { + this.set_size(this.options.size); + } + if(this.options.blur) { + this.set_blur(this.options.blur); + } + if(this.options.readonly) { + this.set_readonly(true); + } + this.input.addClass("et2_textbox"); + this.setDOMNode(this.input[0]); + if(this.options.value) + { + this.set_value(this.options.value); + } + if (this.options.onkeypress && typeof this.options.onkeypress == 'function') + { + var self = this; + this.input.keypress(function(_ev) + { + return self.options.onkeypress.call(this, _ev, self); + }); + } + } + + /** + * Override the parent set_id method to manuipulate the input DOM node + * + * @param {type} _value + * @returns {undefined} + */ + set_id(_value) + { + super.set_id(_value); + + // Remove the name attribute inorder to affect autocomplete="off" + // for no password save. ATM seems all browsers ignore autocomplete for + // input field inside the form + if (this.options.type === "passwd" + && this.options.autocomplete === "off") this.input.removeAttr('name'); + } + + destroy() + { + var node = this.getInputNode(); + if (node) jQuery(node).unbind("keypress"); + + super.destroy(); + } + + getValue() + { + if(this.options && this.options.blur && this.input.val() == this.options.blur) return ""; + + return super.getValue(); + } + + /** + * Clientside validation using regular expression in "validator" attribute + * + * @param {array} _messages + */ + isValid(_messages) + { + var ok = true; + // Check input is valid + if(this.options && this.options.validator && !this.options.readonly && !this.disabled) + { + if (typeof this.options.validator == 'string') + { + var parts = this.options.validator.split('/'); + var flags = parts.pop(); + if (parts.length < 2 || parts[0] !== '') + { + _messages.push(this.egw().lang("'%1' has an invalid format !!!", this.options.validator)); + return false; // show invalid expression + } + parts.shift(); + this.options.validator = new RegExp(parts.join('/'), flags); + } + var value = this.getValue(); + if (!(ok = this.options.validator.test(value))) + { + _messages.push(this.egw().lang("'%1' has an invalid format !!!", value)); + } + } + return super.isValid(_messages) && ok; + } + + /** + * Set input widget size + * @param _size Rather arbitrary size units, approximately characters + */ + set_size(_size : number|string) + { + if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) + { + this.input.css('width', _size + "em"); + } + else if (typeof _size != 'undefined' && _size != this.input.attr("size")) + { + this.size = _size; + this.input.attr("size", this.size); + } + } + + /** + * Set maximum characters allowed + * @param _size Max characters allowed + */ + set_maxlength(_size : number|string) + { + if (typeof _size != 'undefined' && _size != this.input.attr("maxlength")) + { + this.maxLength = _size; + this.input.attr("maxLength", this.maxLength); + } + } + + /** + * Set HTML readonly attribute. + * Do not confuse this with etemplate readonly, which would use et_textbox_ro instead + * @param _readonly Boolean + */ + set_readonly(_readonly) + { + this.input.attr("readonly", _readonly); + this.input.toggleClass('et2_textbox_ro', _readonly); + } + + set_blur(_value) + { + if(_value) { + this.input.attr("placeholder", this.egw().lang(_value) + ""); // HTML5 + if(!this.input[0].placeholder) { + // Not HTML5 + if(this.input.val() == "") this.input.val(this.egw().lang(this.options.blur)); + this.input.focus(this,function(e) { + if(e.data.input.val() == e.data.egw().lang(e.data.options.blur)) e.data.input.val(""); + }).blur(this, function(e) { + if(e.data.input.val() == "") e.data.input.val(e.data.egw().lang(e.data.options.blur)); + }); + } + } else { + if (!this.getValue()) this.input.val(''); + this.input.removeAttr("placeholder"); + } + this.options.blur = _value; + } + + set_autocomplete(_value) + { + this.options.autocomplete = _value; + this.input.attr('autocomplete', _value); + } + + resize(_height) + { + if (_height && this.options.multiline) + { + // apply the ratio + _height = (this.options.resize_ratio != '')? _height * this.options.resize_ratio: _height; + if (_height != 0) + { + this.input.height(this.input.height() + _height); + // resize parent too, so mailvelope injected into parent inherits its height + this.input.parent().height(this.input.parent().height()+_height); + } + } + } +} +et2_register_widget(et2_textbox, ["textbox", "passwd", "hidden"]); + +/** + * et2_textbox_ro is the dummy readonly implementation of the textbox. + * + * @augments et2_valueWidget + */ +class et2_textbox_ro extends et2_valueWidget implements et2_IDetachedDOM +{ + /** + * Ignore all more advanced attributes. + */ + static readonly _attributes : any = { + "multiline": { + "ignore": true + }, + "maxlength": { + "ignore": true + }, + "onchange": { + "ignore": true + }, + "rows": { + "ignore": true + }, + "cols": { + "ignore": true + }, + "size": { + "ignore": true + }, + "needed": { + "ignore": true + } + }; + + /** + * Constructor + */ + constructor(_parent, _attrs? : WidgetConfig, _child? : object) + { + // Call the inherited constructor + super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {})); + + this.value = ""; + this.span = jQuery(document.createElement("label")) + .addClass("et2_label"); + this.value_span = jQuery(document.createElement("span")) + .addClass("et2_textbox_ro") + .appendTo(this.span); + + this.setDOMNode(this.span[0]); + } + + set_label(label) + { + // Remove current label + this.span.contents() + .filter(function(){ return this.nodeType == 3; }).remove(); + + var parts = et2_csvSplit(label, 2, "%s"); + this.span.prepend(parts[0]); + this.span.append(parts[1]); + this.label = label; + + // add class if label is empty + this.span.toggleClass('et2_label_empty', !label || !parts[0]); + } + + set_value(_value) + { + this.value = _value; + + if(!_value) + { + _value = ""; + } + if (this.label !="") + { + this.span.removeClass('et2_label_empty'); + } + else + { + this.span.addClass('et2_label_empty'); + } + this.value_span.text(_value); + } + + /** + * Code for implementing et2_IDetachedDOM + * + * @param {array} _attrs array to add further attributes to + */ + getDetachedAttributes(_attrs) + { + _attrs.push("value", "label"); + } + + getDetachedNodes() + { + return [this.span[0], this.value_span[0]]; + } + + setDetachedAttributes(_nodes, _values) + { + this.span = jQuery(_nodes[0]); + this.value_span = jQuery(_nodes[1]); + if(typeof _values["label"] != 'undefined') + { + this.set_label(_values["label"]); + } + if(typeof _values["value"] != 'undefined') + { + this.set_value(_values["value"]); + } + } +} +et2_register_widget(et2_textbox_ro, ["textbox_ro"]); + +/** + * et2_searchbox is a widget which provides a collapsable input search + * with on searching indicator and clear handler regardless of any browser limitation. + */ +class et2_searchbox extends et2_textbox +{ + /** + * Advanced attributes + */ + static readonly _attributes : any = { + overlay:{ + name:"Overlay searchbox", + type:"boolean", + default:false, + description:"Define wheter the searchbox overlays while it's open (true) or stay as solid box infront of the search button (false). Default is false." + }, + fix: { + name:"Fix searchbox", + type:"boolean", + default:true, + description:"Define wheter the searchbox should be a fix input field or flexible search button. Default is true (fix)." + } + } + + /** + * Constructor + */ + constructor(_parent, _attrs? : WidgetConfig, _child? : object) + { + // Call the inherited constructor + super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {})); + + this.value = ""; + this.div = jQuery(document.createElement('div')) + .addClass('et2_searchbox'); + this.flex = jQuery(document.createElement('div')) + .addClass('flex') + .appendTo(this.div); + + //this._super.apply(this, arguments); + + this.setDOMNode(this.div[0]); + this._createWidget(); + } + + _createWidget() + { + var self = this; + if (this.options.overlay) this.flex.addClass('overlay'); + // search button indicator + // no need to create search button if it's a fix search field + if (!this.options.fix) + { + this.button = et2_createWidget('button',{image:"search","background_image":"1"},this); + this.button.onclick= function(){ + self._show_hide(jQuery(self.flex).hasClass('hide')); + self.search.input.focus(); + }; + this.div.prepend(this.button.getDOMNode()); + } + // input field + this.search = et2_createWidget('textbox',{"blur":egw.lang("search"), + onkeypress:function(event) { + if(event.which == 13) + { + event.preventDefault(); + self.getInstanceManager().autocomplete_fixer(); + // Use a timeout to make sure we get the autocomplete value, + // if one was chosen, instead of what was actually typed. + // Chrome doesn't need this, but FF does. + window.setTimeout(function() { + self.set_value(self.search.input.val()); + self.change(); + },0); + } + }},this); + // Autocomplete needs name + this.search.input.attr('name', this.id||'searchbox'); + this.search.input.on({ + keyup:function(event) + { + self.clear.toggle(self.get_value() !='' || !self.options.fix); + if(event.which == 27) // Escape + { + // Excape clears search + self.set_value(''); + } + }, + + blur(event){ + if (egwIsMobile()) return; + if (!event.relatedTarget || !jQuery(event.relatedTarget.parentNode).hasClass('et2_searchbox')) + { + self._show_hide((!self.options.overlay && self.get_value())); + } + if (typeof self.oldValue !='undefined' && self._oldValue != self.get_value()) { + self.change(); + } + }, + mousedown:function(event){ + if (event.target.type == 'span') event.stopImmidatePropagation(); + } + }); + this.flex.append(this.search.getDOMNode()); + + // clear button implementation + this.clear = jQuery(document.createElement('span')) + .addClass('ui-icon clear') + .toggle(!this.options.fix || (this._oldValue != '' && !jQuery.isEmptyObject(this._oldValue))) + .on('mousedown',function(event){ + event.preventDefault(); + }) + .on('click',function(event) { + if (self.get_value()){ + self.search.input.val(''); + self.search.input.focus(); + self._show_hide(true); + if (self._oldValue) self.change(); + } + else + { + self._show_hide(false); + } + if (self.options.fix) self.clear.hide(); + }) + .appendTo(this.flex); + } + + /** + * Show/hide search field + * @param {boolean} _stat true means show and false means hide + */ + _show_hide(_stat) + { + // Not applied for fix option + if (this.options.fix) return; + + jQuery(this.flex).toggleClass('hide',!_stat); + jQuery(this.getDOMNode()).toggleClass('expanded', _stat); + } + + /** + * toggle search button status based on value + */ + _searchToggleState() + { + if (this.options.fix || egwIsMobile()) return; + + if (!this.get_value()) + { + jQuery(this.button.getDOMNode()).removeClass('toolbar_toggled'); + this.button.set_statustext(''); + } + else + { + jQuery(this.button.getDOMNode()).addClass('toolbar_toggled'); + this.button.set_statustext(egw.lang("search for '%1'", this.get_value())); + } + } + + /** + * override change function in order to preset the toggle state + */ + change() + { + this._searchToggleState(); + + this._super.apply(this,arguments); + } + + + get_value() + { + return this.search.input.val(); + } + + set_value(_value) + { + super.set_value(_value); + if (this.search) this.search.input.val(_value); + } + + /** + * override doLoadingFinished in order to set initial state + */ + doLoadingFinished() + { + super.doLoadingFinished(); + if (!this.get_value()) { + this._show_hide(false); + } + else{ + this._show_hide(!this.options.overlay); + this._searchToggleState(); + } + } + + /** + * Overrride attachToDOM in order to unbind change handler + */ + attachToDOM() + { + super.attachToDOM(); + + var node = this.getInputNode(); + if (node) + { + jQuery(node).off('.et2_inputWidget'); + } + } +} +et2_register_widget(et2_searchbox, ["searchbox"]);