(function() {
/**
* @namespace control2
* @memberof Help4
*/
Help4.control2 = {};
/**
* @namespace input
* @memberof Help4.control2
*/
Help4.control2.input = {};
/**
* @namespace recording
* @memberof Help4.control2
*/
Help4.control2.recording = {};
/**
* @typedef {Object} Help4.control2.DeriveClassParams
* @property {HTMLElement} dom - parent DOM element
* @property {HTMLDocument} document - parent Document
* @property {boolean} rtl - RTL mode enabled
* @property {boolean} mobile - mobile mode enabled
* @property {string} language - language of system; for translation
* @property {string} contentLanguage - language of content; for translation
*/
/**
* resolves a classname, such as "Help4.control2.Control"
* and delivers to corresponding object
* @param {string} name - the to be resolved name
* @returns {?Function}
*/
Help4.control2.getClass = function(name) {
/**
* @param {string[]} parts
* @param {Object} namespace
* @returns {?Object|?Function}
*/
const search = (parts, namespace) => {
const l = parts.length;
let i = -1;
while (namespace && (++i < l)) {
namespace = namespace[parts[i]];
}
return namespace;
}
// search
// 1. within Help4.control2 namespace
// 2. within window
const parts = name.split('.');
return search(parts, Help4.control2) || search(parts, window);
}
/**
* @param {Help4.control2.Control|Help4.control2.DeriveClassParams} control
* @param {Object} params
* @return {Object}
*/
Help4.control2.deriveClassParams = function(control, params) {
const {rtl, mobile, language, contentLanguage, dom, document} = control;
params.dom ||= dom || control.getDom();
params.document ||= document || control.getDocument();
params.rtl ??= rtl;
params.mobile ??= mobile;
params.language ||= language;
params.contentLanguage ||= contentLanguage;
return params;
}
/**
* @param {Function} object
* @param {Help4.control2.Control|Help4.control2.DeriveClassParams} control
* @param {Help4.control2.Control.Params} params
* @returns {Help4.control2.Control}
*/
Help4.control2.createControl = function(object, control, params) {
this.deriveClassParams(control, params);
return new object(params);
}
const DATA_TEXT_ATTRIBUTE = 'data-help4-text-id';
/**
* @typedef {Object} Help4.control2.PositionXY
* @property {number} x - left
* @property {number} y - top
*/
/**
* @typedef {Object} Help4.control2.PositionLeftTop
* @property {number} left - left
* @property {number} top - top
*/
/**
* @typedef {Object} Help4.control2.SizeWH
* @property {number} w - width
* @property {number} h - height
*/
/**
* @typedef {Object} Help4.control2.SizeWidthHeight
* @property {number} width - width
* @property {number} height - height
*/
/**
* @typedef {Object} Help4.control2.AreaXYWH
* @property {number} x - left
* @property {number} y - top
* @property {number} w - width
* @property {number} h - height
*/
/**
* @typedef {Object} Help4.control2.ConnectionPoints
* @property {Help4.control2.PositionXY} [l] - left point
* @property {Help4.control2.PositionXY} [r] - right point
* @property {Help4.control2.PositionXY} [t] - top point
* @property {Help4.control2.PositionXY} [b] - bottom point
* @property {Help4.control2.PositionXY} [m] - middle point
* @property {Help4.control2.PositionXY} [c] - center point
*/
/**
* @typedef {Object} Help4.control2.DragDropParams
* @property {HTMLElement} object - the to be moved element
* @property {HTMLElement} [area] - the area that controls the drag & drop event
* @property {boolean} [remoteMode]
* @property {Help4.control2.PositionXY} [min] - min coordinates for drag
* @property {Help4.control2.PositionXY} [max] - max coordinates for drag
*/
/**
* @typedef {Object} Help4.control2.Control.Params
* @property {string} [id] - the control id
* @property {?string} [css = null] - CSS classes to be added to the control
* @property {string} [tag = 'div'] - the control's TAG NAME in the DOM
* @property {boolean} [rtl = false] - whether RTL is enabled
* @property {?string} [language = null] - current language
* @property {?string} [contentLanguage = null] - content language
* @property {boolean} [mobile = false] - whether mobile mode is enabled
* @property {Help4.control2.container.Container} [container = null] - a possible container that hosts this control
* @property {?string} [title = null] - title of the control within DOM
* @property {?string} [role = null] - role attribute
* @property {?number} [tabIndex = null] - tabIndex attribute
* @property {?number} [zIndex = null] - z-index CSS property for this control
* @property {?string} [ariaLabel = null] - arial-label attribute
* @property {?string} [ariaRoleDescription = null] - aria-roledescription attribute
* @property {?string} [ariaLive = null] - aria-live attribute
* @property {?string} [ariaAtomic = null] - aria-atomic attribute
* @property {?string} [ariaRelevant = null] - aria-relevant attribute
* @property {boolean} [visible = true] - whether the control is visible
* @property {boolean} [active = false] - whether the control is active
* @property {boolean} [disabled = false] - whether the control is disabled
* @property {boolean} [animated = false] - whether the control is in an animated state
* @property {boolean} [autoFocus = false] - whether the control shall be automatically focussed after creation
* @property {boolean} [autoEvent = false] - whether the control shall automatically listen to events
* @property {boolean} [enableDragDrop = false] - whether drag & drop shall be enabled for this control
* @property {?Help4.control2.PositionLeftTop} [dragPosition = null] - initial position of drag & drop
* @property {Object} [_metadata = {}] - provide external data as key-value pairs
* @property {?string} [_order = null] - for {@link Help4.control2.container.SortedContainer}
* @property {HTMLElement} [dom = window.document.body] - the hosting DOM element for this control
* @property {HTMLDocument} [document = window.document] - the hosting document for this control
*/
/**
* The base class of the control2 library.
* @augments Help4.jscore.ControlBase
* @property {string} id - the control id; readonly
* @property {?string} css - classes of the control; readonly
* @property {string} tag - tag name; readonly
* @property {boolean} rtl - enable RTL; readonly
* @property {?string} language - language id; readonly
* @property {boolean} mobile - enable mobile mode; readonly
* @property {?Help4.control2.container.Container} container - reference to a possible container
* @property {?string} title - DOM title of the control
* @property {?string} role - DOM role
* @property {?number} tabIndex - DOM tabIndex
* @property {?number} zIndex - DOM z-index
* @property {?string} contentLanguage - content language id; readonly
* @property {?string} ariaLabel - DOM aria-label
* @property {?string} ariaRoleDescription - DOM aria-roledescription
* @property {?string} ariaLive - aria-live attribute
* @property {?string} ariaAtomic - aria-atomic attribute
* @property {?string} ariaRelevant - aria-relevant attribute
* @property {boolean} visible - control is visible
* @property {boolean} active - control is active
* @property {boolean} animated - whether the control is in an animated state
* @property {boolean} disabled - control is disabled
* @property {boolean} autoFocus - enable automatic focus; readonly
* @property {boolean} autoEvent - enable automatic event listening; readonly
* @property {boolean} enableDragDrop - enable drag & drop; readonly
* @property {?Help4.control2.PositionLeftTop} dragPosition - initial position of drag & drop; readonly
* @property {HTMLElement} __dom - DOM of this control
* @property {HTMLDocument} __document - document of this control
* @property {Object} ___metadata - metadata information
* @property {?string} ___order - for {@link Help4.control2.container.SortedContainer}
* @property {boolean} _isMouseOver
* @property {?Help4.jscore.DragDrop} _dragDrop
* @property {?HTMLElement} _dom
* @property {?number} _state
*/
Help4.control2.Control = class extends Help4.jscore.ControlBase {
/**
* @override
* @param {Help4.control2.Control.Params} [params] - control configuration
* @param {Help4.jscore.ControlBase.Params} [derived] - configurations from derived classes
*/
constructor(params, derived) {
const {ControlBase} = Help4.jscore;
const T = ControlBase.TYPES;
const TT = ControlBase.TEXT_TYPES;
const id = Help4.createId(); // do not move in to simplify debugging!
super(params, {
params: {
id: {type: T.string, init: id, readonly: true},
css: {type: T.string_null, readonly: true},
tag: {type: T.string, init: 'div', readonly: true},
rtl: {type: T.boolean, readonly: true},
language: {type: T.string_null, readonly: true},
mobile: {type: T.boolean, readonly: true},
container: {type: T.instance},
title: {type: T.string_null},
role: {type: T.string_null},
tabIndex: {type: T.number_null},
zIndex: {type: T.number_null},
contentLanguage: {type: T.string_null, readonly: true},
ariaLabel: {type: T.string_null},
ariaRoleDescription: {type: T.string_null},
ariaLive: {type: T.string_null},
ariaAtomic: {type: T.string_null},
ariaRelevant: {type: T.string_null},
visible: {type: T.boolean, init: true},
active: {type: T.boolean},
disabled: {type: T.boolean},
animated: {type: T.boolean},
autoFocus: {type: T.boolean, readonly: true},
autoEvent: {type: T.boolean, readonly: true},
enableDragDrop: {type: T.boolean},
dragPosition: {type: T.leftTop_null, readonly: true},
dom: {type: T.element, init: window.document.body, readonly: true, private: true},
document: {type: T.element, init: window.document, readonly: true, private: true},
_metadata: {type: T.json, private: true},
_order: {type: T.string_null, readonly: true, private: true}
},
config: {
css: 'control',
onPropertyChange: event => void this._applyPropertyToDom(event)
},
texts: {
title: TT.title
},
statics: {
_isMouseOver: {init: false, destroy: false},
_dragDrop: {},
_dom: {destroy: false},
_state: {destroy: false}
},
derived
});
Help4.control2.Store.add(this);
const {STATE} = this.constructor;
this._state = STATE.init;
this._onAfterInit(params || {});
this._state = STATE.createDom;
_createDom.call(this);
this._state = STATE.ready;
this._onReady();
const {TYPES} = Help4.EventBus;
const eventBus = Help4.getController().getService('eventBus');
eventBus && eventBus.fire({type: TYPES.controlCreate, control: this});
}
static STATE = {
init: 0,
createDom: 1,
ready: 2
}
/**
* deliver the DOM elements that represents this control or some element inside
* @param {string} [id] - a possible sub-dom-id
* @returns {HTMLElement}
*/
getDom(id) {
return typeof id === 'string'
? this._dom.querySelector('#' + this.id + id)
: this._dom;
}
/**
* deliver the document that hosts this control
* @returns {Document}
*/
getDocument() {
return this.__document;
}
/**
* deliver the DOM that contains this control
* @returns {HTMLElement}
*/
getOwnerDom() {
return this.__dom;
}
/**
* destroys the control
* @override
*/
destroy() {
// due to async nature of event flow, metadata information is deleted in destroy process
// before event reaches the listener; hence passing this in-accessible information
const {___metadata: metadata} = this;
const eventBus = Help4.getController()?.getService('eventBus');
eventBus?.fire({type: Help4.EventBus.TYPES.controlDestroy, control: this, metadata});
this._fireEvent({type: 'destroy', control: this, metadata});
this._onBeforeDestroy();
Help4.control2.Store.remove(this);
const {_dom} = this;
if (_dom) {
Help4.Event.stopObserving(_dom);
_dom.parentNode?.removeChild(_dom);
}
const {id, container} = this;
const [control, index] = container?.find(id) || []; // find me within a possible container
super.destroy();
if (control === this) container.remove(index); // remove me from container, if contained
}
/**
* called after init
* @protected
* @param {Help4.control2.Control.Params} params - same params as provided to the constructor
*/
_onAfterInit(params) {}
/**
* called before destroy
* @protected
*/
_onBeforeDestroy() {}
/**
* called once the DOM node is available but not yet attached
* @protected
* @param {HTMLElement} dom - control DOM
*/
_onDomAvailable(dom) {}
/**
* called once the DOM node is attached
* @protected
* @param {HTMLElement} dom - control DOM
*/
_onDomAttached(dom) {}
/**
* called once the DOM's creation is complete
* @protected
* @param {HTMLElement} dom - control DOM
*/
_onDomCreated(dom) {}
/**
* called once the DOM's event listeners are attached and the DOM's automatic focus is applied
* @protected
* @param {HTMLElement} dom - control DOM
*/
_onDomFocussed(dom) {}
/**
* called once control is completely initialized
* @protected
*/
_onReady() {}
/**
* called once the control starts drag & drop
* @protected
*/
_onDragStart() {
_handleDragEvent.call(this, 'start');
}
/**
* called on every move of the control during drag & drop
* @protected
*/
_onDragMove() {
_handleDragEvent.call(this, 'move');
}
/**
* called once drag & drop ends
* @protected
*/
_onDragStop() {
_handleDragEvent.call(this, 'stop');
}
/**
* called to configure drag & drop properly
* @protected
* @returns {Help4.control2.DragDropParams} - allows to define specific DOM (object) and dragable area (area)
*/
_onGetDragDropParams() {
return {object: this._dom};
}
/**
* allows to store any external key-value information within this control
* @param {string|Object} key - the key
* @param {*} value - any value to be stored
* @returns {Help4.control2.Control}
*/
setMetadata(key, value) {
const {___metadata} = this;
typeof key === 'string'
? ___metadata[key] = value
: Help4.extendObject(___metadata, key);
this.___metadata = ___metadata; // data tracking in ControlBase does only work for complete assignments!
return this;
}
/**
* get information from the metadata store
* @param {string} keys - keys to retrieve the stored meta information
* @returns {*}
*/
getMetadata(...keys) {
const {___metadata} = this;
if (keys.length === 1) {
return ___metadata[keys[0]];
} else {
const r = {};
keys.forEach(key => r[key] = ___metadata[key]);
return r;
}
}
/**
* get all currently stored metadata keys
* @returns {string[]} - all currently stored keys
*/
getMetadataKeys() {
return Object.keys(this.___metadata);
}
/**
* get possible connection points for this control
* @param {Object} [params] - optional parameters to be used by derived classes
* @returns {Help4.control2.ConnectionPoints} - points top,left,bottom,right and middle of this control
*/
getConnectionPoints(params) {
const {left, right, top, bottom, width, height} = this.getDom().getBoundingClientRect();
const mx = left + (width >> 1); // mid x
const my = top + (height >> 1); // mid y
return {
l: {x: left, y: my},
r: {x: right, y: my},
t: {x: mx, y: top},
b: {x: mx, y: bottom},
m: {x: mx, y: my}
};
}
/**
* get size of this control
* @returns {Help4.control2.SizeWH} - size of this control's bounding client rect
*/
getSize() {
const {width: w, height: h} = this.getDom().getBoundingClientRect();
return {w, h};
}
/**
* get position of this control
* @returns {Help4.control2.PositionXY} - position of this control within DOM
*/
getPosition() {
const {left: x, top: y} = this.getDom().getBoundingClientRect();
return {x, y};
}
/**
* get position and size of this control
* @returns {Help4.control2.AreaXYWH} - area of this control within DOM
*/
getArea() {
const {left: x, top: y, width: w, height: h} = this.getDom().getBoundingClientRect();
return {
x: Math.floor(x),
y: Math.floor(y),
w: Math.floor(w),
h: Math.floor(h)
};
}
/**
* adds an attribute to the DOM
* @param {string} name
* @param {*} value
* @returns {Help4.control2.Control}
*/
setAttribute(name, value) {
Help4.Element.setAttribute(this.getDom(), {[name]: value});
return this;
}
/**
* add CSS classnames to this control
* @param {string} classNames - class names to be added to this control's DOM
* @returns {Help4.control2.Control}
*/
addCss(...classNames) {
Help4.Element.addClass(this.getDom(), ...classNames);
return this;
}
/**
* remove CSS classnames from this control
* @param {string} classNames - class names to be removed from this control's DOM
* @returns {Help4.control2.Control}
*/
removeCss(...classNames) {
Help4.Element.removeClass(this.getDom(), ...classNames);
return this;
}
/**
* tests some classnames on this control
* @param {string} classNames - class names to be tested on this control's DOM
* @returns {boolean}
*/
hasCss(...classNames) {
return Help4.Element.hasClass(this.getDom(), ...classNames);
}
/**
* add styles to this control
* @param {Object} styles - styles to be added to this control's DOM
* @returns {Help4.control2.Control}
*/
setStyle(styles) {
Help4.extendObject(this.getDom().style, styles);
return this;
}
/**
* focus the control's DOM
* @returns {Help4.control2.Control}
*/
focus() {
!this.mobile && this.getDom().focus();
return this;
}
/**
* suspend drag & drop operation
* @returns {Help4.control2.Control}
*/
suspendDragDrop() {
if (this._dragDrop) {
_updateDragPosition.call(this);
const {style} = this.getDom();
style.removeProperty('left');
style.removeProperty('top');
this._destroyControl('_dragDrop');
}
return this;
}
/**
* resume drag & drop operation
* @returns {Help4.control2.Control}
*/
resumeDragDrop() {
if (!this._dragDrop) {
const {dragPosition} = this;
if (dragPosition) {
const {left, top} = dragPosition;
this.setStyle({
left: `${left}px`,
top: `${top}px`
});
}
if (this.enableDragDrop) _enableDragDrop.call(this);
}
return this;
}
/**
* handle incoming events
* @param {Object} event - the received event
*/
onEvent(event) {
let {type} = event;
switch (type) {
case 'mouseover':
this._isMouseOver = true;
break;
case 'mouseout':
this._isMouseOver = false;
break;
case 'mouseup':
if (event.keyCode === 2) type = 'middleclick';
break;
case 'click':
if (this.enableDragDrop) return; // suppress the 'click' event triggered by mouse up during drag stop
if (event.ctrlKey) type = 'middleclick';
break;
case 'keyup':
const key = Help4.service.HotkeyService.getKey(event);
if (key === 'enter' || key === 'space') type = key;
break;
}
this._fireEvent({type, data: event});
}
/**
* calculate whether the mouse is on top of my DOM
* @returns {boolean} - whether mouse is over my DOM
*/
isMouseOver() {
const dom = this.getDom();
if (!dom) return false;
return dom.matches
? dom.matches(':hover')
: dom.msMatchesSelector(':hover');
}
/**
* get all texts within my DOM; they can then be used for e.g. automatic machine learning translation (MLT)
* @returns {Object|null}
* @throws {Error}
*/
getControlTexts() {
const texts = this.dataFunctions.getTexts();
const dom = this.getDom();
const result = {};
const {Store} = Help4.control2;
const {contentLanguage: language} = this;
const shell = Help4.getShell();
const {uacp = null} = language && shell.getLanguage(language) || {};
// @var {string} textId
// @var {{type: string, set: function, get: function}} config
for (const [textId, config] of Object.entries(texts)) {
if (!config.get) throw new Error(`Bad configuration for text id "${textId}"`);
const list = _getTextNodeList(dom, textId);
let value, attrs;
for (const node of list) {
attrs = _getTextAttributeList(node);
if (attrs.indexOf(textId) >= 0 &&
(value = config.get(node)) &&
Store.find(node) === this)
{
result[textId] = {
data: value,
contentType: config.contentType,
language: uacp
};
break;
}
}
}
return Object.keys(result).length ? result : null;
}
/**
* exchange all texts of my control; e.g. after successful MLT
* @param {Object} textMap
* @returns {Help4.control2.Control}
* @throws {Error}
*/
setControlTexts(textMap) {
const {Store} = Help4.control2;
const texts = this.dataFunctions.getTexts();
const dom = this.getDom();
let hasChanges = false;
for (const [textId, value] of Object.entries(textMap)) {
if (!value) continue;
const config = texts[textId];
if (!config.set) throw new Error(`Bad configuration for text id "${textId}"`);
const list = _getTextNodeList(dom, textId);
for (const node of list) {
const attrs = _getTextAttributeList(node);
if (attrs.indexOf(textId) >= 0 &&
Store.find(node) === this)
{
hasChanges = config.set(node, value) || hasChanges;
break;
}
}
}
hasChanges && this._fireEvent({type: 'afterSetControlTexts', control: this});
return this;
}
/**
* get the texts within my control and within all controls that are within my control
* @param {boolean} [onlyVisible = false]
* @returns {Object|null}
*/
getTexts(onlyVisible = false) {
if (onlyVisible && !this.visible) return null;
const controlTexts = {};
const controls = [...this.getHostedControls(onlyVisible), this];
for (const control of controls) {
const texts = control.getControlTexts();
if (texts) controlTexts[control.id] = texts;
}
const textMap = {};
for (const [controlId, texts] of Object.entries(controlTexts)) {
for (const [textKey, text] of Object.entries(texts)) {
textMap[controlId + '.' + textKey] = text;
}
}
return Object.keys(textMap).length ? textMap : null;
}
/**
* set the texts within my control and within all controls that are within my control
* @param {Object} textMap
* @returns {Help4.control2.Control}
*/
setTexts(textMap) {
const controlTexts = {};
for (const [combinedId, text] of Object.entries(textMap)) {
const [controlId, textId] = combinedId.split('.');
if (!controlTexts[controlId]) controlTexts[controlId] = {};
controlTexts[controlId][textId] = text;
}
const {Store} = Help4.control2;
const controls = [...this.getHostedControls(), this];
for (const [controlId, texts] of Object.entries(controlTexts)) {
const control = Store.get(controlId);
if (control && controls.indexOf(control) >= 0) { // only do for controls hosted by me and me
control.setControlTexts(texts);
}
}
return this;
}
/**
* @param {boolean} [onlyVisible] - deliver only currently visible controls
* @returns {Help4.control2.Control[]}
*/
getHostedControls(onlyVisible = false) {
const html = this.getDom().innerHTML;
const matches = [...html.matchAll(/ id=(['"])(.*?)\1/g)];
const controls = [];
const {Store} = Help4.control2;
let control;
for (const [match, quote, id] of matches) {
if (control = Store.get(id)) {
if (onlyVisible && !control.visible) continue;
controls.push(control);
}
}
return controls;
}
/** @returns {?string} */
getOrder() {
return this.___order;
}
/**
* used to support text extraction and replacement
* @param {HTMLElement} node
* @param {string} textId
* @protected
*/
_setTextAttribute(node, ...textId) {
let attrs = _getTextAttributeList(node);
attrs.push(...textId);
attrs = [...new Set(attrs)].join(',');
node.setAttribute(DATA_TEXT_ATTRIBUTE, attrs);
}
/**
* used to support text extraction and replacement
* @param {HTMLElement} node
* @param {string} textId
* @protected
*/
_removeTextAttribute(node, ...textId) {
const attrs = _getTextAttributeList(node)
.filter(item => textId.indexOf(item) < 0)
.join(',');
attrs
? node.setAttribute(DATA_TEXT_ATTRIBUTE, attrs)
: node.removeAttribute(DATA_TEXT_ATTRIBUTE);
}
/**
* used to support text extraction and replacement
* @param {HTMLElement} node
* @param {string} textId
* @param {boolean} value
* @protected
*/
_applyTextAttribute(node, textId, value = true) {
value
? this._setTextAttribute(node, textId)
: this._removeTextAttribute(node, textId);
}
/**
* creates a new DOM element within my control's DOM
* @param {string} tagName - tag name of new DOM element
* @param {Object} params - additional parameters
* @returns {HTMLElement}
* @protected
*/
_createElement(tagName, params = {}) {
if (typeof params.id === 'string' && params.id !== '') {
params.id = this.id + params.id;
}
params.dom ||= this.getDom();
return Help4.Element.create(tagName, params);
}
/**
* @protected
* @param {Function} object
* @param {Help4.control2.Control.Params} params
* @returns {Help4.control2.Control}
*/
_createControl(object, params) {
return Help4.control2.createControl(object, this, params);
}
/**
* is called on data changes of my control
* should be used to apply this changes to my DOM
* @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
* @protected
*/
_applyPropertyToDom({name, value}) {
const {
Element,
control2: {Control: {STATE}}
} = Help4;
switch (name) {
case 'visible':
this.getDom().style.visibility = this._state <= STATE.createDom && value === false ? 'hidden' : '';
value ? this.removeCss('hidden') : this.addCss('hidden');
break;
case 'active':
case 'disabled':
case 'animated':
value ? this.addCss(name) : this.removeCss(name);
break;
case 'zIndex':
this.getDom().style.zIndex = value;
break;
case 'title':
const d = this.getDom();
Element.setAttribute(d, {title: value});
this._applyTextAttribute(d, 'title', !!value);
break;
case 'role':
case 'tabIndex':
case 'ariaLabel':
case 'ariaRoleDescription':
case 'ariaLive':
case 'ariaAtomic':
case 'ariaRelevant':
const o = {};
o[name] = value;
Element.setAttribute(this.getDom(), o);
break;
case 'enableDragDrop':
value ? _enableDragDrop.call(this) : _disableDragDrop.call(this);
break;
}
}
/** @param {HTMLElement} dom */
setDom(dom) {
const {__dom} = this;
if (__dom !== dom) {
this.dataFunctions.set({dom}, {allowReadonlyOverride: true}); // allow readonly override
this.dataFunctions.set({document: dom.ownerDocument}, {allowReadonlyOverride: true}); // allow readonly override
dom.appendChild(this._dom);
}
}
}
/**
* creates the DOM of this control
* @memberof Help4.control2.Control#
* @private
*/
function _createDom() {
const {id, tag, css, visible, __dom} = this;
const dom = this._dom = Help4.Element.create(tag, {id: id, css: css});
// XRAY-4669: prevent flickering of controls that should be invisible
if (!visible) this._applyPropertyToDom({name: 'visible', value: visible, oldValue: visible});
this._onDomAvailable(dom);
if (!__dom) return;
__dom.appendChild(dom);
this._onDomAttached(dom);
const {autoEvent, autoFocus} = this;
autoEvent && Help4.Event.observe(dom, {
type: 'control',
controlId: id
});
this._onDomCreated(dom);
autoFocus && this.focus();
this._onDomFocussed(dom);
this.resumeDragDrop();
// apply state to DOM
this.dataFunctions.forEach(name => {
const value = this[name]; // do not invoke getter twice
this._applyPropertyToDom({name, value, oldValue: value});
});
}
/**
* enables drag & drop for this control
* @memberof Help4.control2.Control#
* @private
*/
function _enableDragDrop() {
if (this._dragDrop) return;
const params = this._onGetDragDropParams();
const dd = this._dragDrop = new Help4.jscore.DragDrop(params);
dd.onStart.addListener(() => this._onDragStart());
dd.onMove.addListener(() => this._onDragMove());
dd.onEnd.addListener(() => this._onDragStop());
}
/**
* disables drag & drop for this control
* @memberOf Help4.control2.Control#
* @private
*/
function _disableDragDrop() {
this._destroyControl('_dragDrop');
this.dataFunctions.set({dragPosition: {left: 0, top: 0}}, {allowReadonlyOverride: true}); // this.dragPosition is readonly; allow override
}
/**
* gets all text attributes for a DOM node
* @memberof Help4.control2.Control#
* @param {HTMLElement} node - the DOM node
* @returns {string[]}
* @private
*/
function _getTextAttributeList(node) {
return (node.getAttribute(DATA_TEXT_ATTRIBUTE) || '')
.split(',')
.map(value => value.trim())
.filter(value => value !== '');
}
/**
* finds all nodes with a certain text attribute
* @memberof Help4.control2.Control#
* @param {HTMLElement} dom - the start node
* @param {string} textId - the text attribute
* @returns {HTMLElement[]} unique list of elements
* @private
*/
function _getTextNodeList(dom, textId) {
const nodeList = dom.querySelectorAll(`[${DATA_TEXT_ATTRIBUTE}*=${textId}]`);
const domList = [].slice.call(nodeList);
domList.unshift(dom);
return [...new Set(domList)];
}
/**
* updates the current drag position
* @memberof Help4.control2.Control#
* @private
*/
function _updateDragPosition() {
let {left, top} = this.getDom().style;
left = parseInt(left);
top = parseInt(top);
this.dataFunctions.set({dragPosition: {left, top}}, {allowReadonlyOverride: true}); // this.dragPosition is readonly; allow override
}
/**
* fires the dragdrop event, common for start, move and stop
* @memberof Help4.control2.Control#
* @private
* @param {'start'|'move'|'stop'} action - type of drag action
*/
function _handleDragEvent(action) {
_updateDragPosition.call(this);
this._fireEvent({type: 'dragdrop', position: this.dragPosition, action});
}
})();