mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-15 12:34:57 +01:00
f7f4053d1a
This lets us add LitElement's updateComplete Promise into the list of things to wait for, and solves the problem of app init code being run before widgets are complete. https://lit.dev/docs/components/lifecycle/#reactive-update-cycle-completing
393 lines
8.6 KiB
TypeScript
393 lines
8.6 KiB
TypeScript
/**
|
|
* EGroupware eTemplate2 - JS Widget base class
|
|
*
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package etemplate
|
|
* @subpackage api
|
|
* @link https://www.egroupware.org
|
|
* @author Andreas Stöckel
|
|
*/
|
|
|
|
/*egw:uses
|
|
/vendor/bower-asset/jquery/dist/jquery.js;
|
|
et2_core_interfaces;
|
|
et2_core_valueWidget;
|
|
*/
|
|
|
|
import {et2_no_init} from "./et2_core_common";
|
|
import {ClassWithAttributes} from "./et2_core_inheritance";
|
|
import {et2_widget, WidgetConfig} from "./et2_core_widget";
|
|
import {et2_valueWidget} from './et2_core_valueWidget'
|
|
import {et2_IInput, et2_ISubmitListener} from "./et2_core_interfaces";
|
|
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
|
|
// fixing circular dependencies by only importing the type (not in compiled .js)
|
|
import type {et2_tabbox} from "./et2_widget_tabs";
|
|
|
|
export interface et2_input
|
|
{
|
|
getInputNode(): HTMLInputElement | HTMLElement;
|
|
}
|
|
|
|
/**
|
|
* et2_inputWidget derrives from et2_simpleWidget and implements the IInput
|
|
* interface. When derriving from this class, call setDOMNode with an input
|
|
* DOMNode.
|
|
*/
|
|
export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_ISubmitListener, et2_input
|
|
{
|
|
static readonly _attributes: any = {
|
|
"needed": {
|
|
"name": "Required",
|
|
"default": false,
|
|
"type": "boolean",
|
|
"description": "If required, the user must enter a value before the form can be submitted"
|
|
},
|
|
"onchange": {
|
|
"name": "onchange",
|
|
"type": "js",
|
|
"default": et2_no_init,
|
|
"description": "JS code which is executed when the value changes."
|
|
},
|
|
"onfocus": {
|
|
"name": "onfocus",
|
|
"type": "js",
|
|
"default": et2_no_init,
|
|
"description": "JS code which get executed when wiget receives focus."
|
|
},
|
|
"validation_error": {
|
|
"name": "Validation Error",
|
|
"type": "string",
|
|
"default": et2_no_init,
|
|
"description": "Used internally to store the validation error that came from the server."
|
|
},
|
|
"tabindex": {
|
|
"name": "Tab index",
|
|
"type": "integer",
|
|
"default": et2_no_init,
|
|
"description": "Specifies the tab order of a widget when the 'tab' button is used for navigating."
|
|
},
|
|
readonly: {
|
|
name: "readonly",
|
|
type: "boolean",
|
|
"default": false,
|
|
description: "Does NOT allow user to enter data, just displays existing data"
|
|
}
|
|
}
|
|
|
|
protected _oldValue: any;
|
|
onchange: Function;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
constructor(_parent, _attrs?: WidgetConfig, _child?: object)
|
|
{
|
|
// Call the inherited constructor
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_inputWidget._attributes, _child || {}));
|
|
|
|
// mark value as not initialised, so set_value can determine if it is necessary to trigger change event
|
|
this._oldValue = et2_no_init;
|
|
this._labelContainer = null;
|
|
}
|
|
|
|
destroy()
|
|
{
|
|
var node = this.getInputNode();
|
|
if (node)
|
|
{
|
|
jQuery(node).unbind("change.et2_inputWidget");
|
|
jQuery(node).unbind("focus");
|
|
}
|
|
|
|
super.destroy();
|
|
|
|
this._labelContainer = null;
|
|
}
|
|
|
|
/**
|
|
* Make sure dirty flag is properly set
|
|
*/
|
|
doLoadingFinished() : boolean | Promise<unknown>
|
|
{
|
|
let result = super.doLoadingFinished();
|
|
|
|
this.resetDirty();
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Load the validation errors from the server
|
|
*
|
|
* @param {object} _attrs
|
|
*/
|
|
transformAttributes(_attrs)
|
|
{
|
|
super.transformAttributes(_attrs);
|
|
|
|
// Check whether an validation error entry exists
|
|
if (this.id && this.getArrayMgr("validation_errors"))
|
|
{
|
|
var val = this.getArrayMgr("validation_errors").getEntry(this.id);
|
|
if (val)
|
|
{
|
|
_attrs["validation_error"] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
attachToDOM()
|
|
{
|
|
var node = this.getInputNode();
|
|
if (node)
|
|
{
|
|
jQuery(node)
|
|
.off('.et2_inputWidget')
|
|
.bind("change.et2_inputWidget", this, function (e)
|
|
{
|
|
e.data.change.call(e.data, this);
|
|
})
|
|
.bind("focus.et2_inputWidget", this, function (e)
|
|
{
|
|
e.data.focus.call(e.data, this);
|
|
});
|
|
}
|
|
|
|
return super.attachToDOM();
|
|
|
|
// jQuery(this.getInputNode()).attr("novalidate","novalidate"); // Stop browser from getting involved
|
|
// jQuery(this.getInputNode()).validator();
|
|
}
|
|
|
|
detatchFromDOM()
|
|
{
|
|
// if(this.getInputNode()) {
|
|
// jQuery(this.getInputNode()).data("validator").destroy();
|
|
// }
|
|
super.detachFromDOM();
|
|
}
|
|
|
|
change(_node, _widget?, _value?)
|
|
{
|
|
var messages = [];
|
|
var valid = this.isValid(messages);
|
|
|
|
// Passing false will clear any set messages
|
|
this.set_validation_error(valid ? false : messages);
|
|
|
|
if (valid && this.onchange)
|
|
{
|
|
if (typeof this.onchange == 'function')
|
|
{
|
|
// Make sure function gets a reference to the widget
|
|
var args = Array.prototype.slice.call(arguments);
|
|
if (args.indexOf(this) == -1) args.push(this);
|
|
|
|
return this.onchange.apply(this, args);
|
|
}
|
|
else
|
|
{
|
|
return (et2_compileLegacyJS(this.options.onchange, this, _node))();
|
|
}
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
focus(_node)
|
|
{
|
|
if (typeof this.options.onfocus == 'function')
|
|
{
|
|
// Make sure function gets a reference to the widget
|
|
var args = Array.prototype.slice.call(arguments);
|
|
if (args.indexOf(this) == -1) args.push(this);
|
|
|
|
return this.options.onfocus.apply(this, args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set value of widget and trigger for real changes a change event
|
|
*
|
|
* First initialisation (_oldValue === et2_no_init) is NOT considered a change!
|
|
*
|
|
* @param {string} _value value to set
|
|
*/
|
|
set_value(_value: any | null)
|
|
{
|
|
var node = this.getInputNode();
|
|
if (node)
|
|
{
|
|
jQuery(node).val(_value);
|
|
if (this.isAttached() && this._oldValue !== et2_no_init && this._oldValue !== _value)
|
|
{
|
|
jQuery(node).change();
|
|
}
|
|
}
|
|
this._oldValue = _value;
|
|
}
|
|
|
|
set_id(_value)
|
|
{
|
|
this.id = _value;
|
|
this.dom_id = _value && this.getInstanceManager() ? this.getInstanceManager().uniqueId + '_' + this.id : _value;
|
|
|
|
// Set the id of the _input_ node (in contrast to the default
|
|
// implementation, which sets the base node)
|
|
var node = this.getInputNode();
|
|
if (node)
|
|
{
|
|
// Unique ID to prevent DOM collisions across multiple templates
|
|
if (_value != "")
|
|
{
|
|
node.setAttribute("id", this.dom_id);
|
|
node.setAttribute("name", _value);
|
|
}
|
|
else
|
|
{
|
|
node.removeAttribute("id");
|
|
node.removeAttribute("name");
|
|
}
|
|
}
|
|
}
|
|
|
|
set_needed(_value)
|
|
{
|
|
var node = this.getInputNode();
|
|
if (node)
|
|
{
|
|
if (_value && !this.options.readonly)
|
|
{
|
|
jQuery(node).attr("required", "required");
|
|
}
|
|
else
|
|
{
|
|
node.removeAttribute("required");
|
|
}
|
|
}
|
|
}
|
|
|
|
set_validation_error(_value)
|
|
{
|
|
var node = this.getInputNode();
|
|
if (node)
|
|
{
|
|
if (_value === false)
|
|
{
|
|
this.hideMessage();
|
|
jQuery(node).removeClass("invalid");
|
|
}
|
|
else
|
|
{
|
|
this.showMessage(_value, "validation_error");
|
|
jQuery(node).addClass("invalid");
|
|
|
|
// If on a tab, switch to that tab so user can see it
|
|
let widget: et2_widget = this;
|
|
while (widget.getParent() && widget.getType() != 'tabbox')
|
|
{
|
|
widget = widget.getParent();
|
|
}
|
|
if (widget.getType() == 'tabbox') (<et2_tabbox><unknown>widget).activateTab(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set tab index
|
|
*
|
|
* @param {number} index
|
|
*/
|
|
set_tabindex(index)
|
|
{
|
|
jQuery(this.getInputNode()).attr("tabindex", index);
|
|
}
|
|
|
|
getInputNode()
|
|
{
|
|
return this.node;
|
|
}
|
|
|
|
get_value()
|
|
{
|
|
return this.getValue();
|
|
}
|
|
|
|
getValue()
|
|
{
|
|
var node = this.getInputNode();
|
|
if (node)
|
|
{
|
|
var val = jQuery(node).val();
|
|
|
|
return val;
|
|
}
|
|
|
|
return this._oldValue;
|
|
}
|
|
|
|
isDirty()
|
|
{
|
|
let value = this.getValue();
|
|
if (typeof value !== typeof this._oldValue)
|
|
{
|
|
return true;
|
|
}
|
|
if (this._oldValue === value)
|
|
{
|
|
return false;
|
|
}
|
|
switch (typeof this._oldValue)
|
|
{
|
|
case "object":
|
|
if (typeof this._oldValue.length !== "undefined" &&
|
|
this._oldValue.length !== value.length
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
for (let key in this._oldValue)
|
|
{
|
|
if (this._oldValue[key] !== value[key]) return true;
|
|
}
|
|
return false;
|
|
default:
|
|
return this._oldValue != value;
|
|
}
|
|
}
|
|
|
|
resetDirty()
|
|
{
|
|
this._oldValue = this.getValue();
|
|
}
|
|
|
|
isValid(messages)
|
|
{
|
|
var ok = true;
|
|
|
|
// Check for required
|
|
if (this.options && this.options.needed && !this.options.readonly && !this.disabled &&
|
|
(this.getValue() == null || this.getValue().valueOf() == ''))
|
|
{
|
|
messages.push(this.egw().lang('Field must not be empty !!!'));
|
|
ok = false;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Called whenever the template gets submitted. We return false if the widget
|
|
* is not valid, which cancels the submission.
|
|
*
|
|
* @param _values contains the values which will be sent to the server.
|
|
* Listeners may change these values before they get submitted.
|
|
*/
|
|
submit(_values)
|
|
{
|
|
var messages = [];
|
|
var valid = this.isValid(messages);
|
|
|
|
// Passing false will clear any set messages
|
|
this.set_validation_error(valid ? false : messages);
|
|
return valid;
|
|
}
|
|
} |