12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556/// FOURJS_START_COPYRIGHT(D,2015)
/// Property of Four Js*
/// (c) Copyright Four Js 2015, 2021. All Rights Reserved.
/// * Trademark of Four Js Development Tools Europe Ltd
/// in the United States and elsewhere
///
/// This file can be modified by licensees according to the
/// product manual.
/// FOURJS_END_COPYRIGHT
"use strict";
modulum('WidgetBase', ['EventListener'],
function(context, cls) {
var SPACES_RE = /\s+/;
/**
* Base class for widgets.
* @class WidgetBase
* @memberOf classes
* @tutorial widgets
* @extends classes.EventListener
* @publicdoc Widgets
*/
cls.WidgetBase = context.oo.Class({
base: cls.EventListener
}, function($super) {
var __charMeasurer = document.createElement('char-measurer');
__charMeasurer.className = "g_layout_charMeasurer";
var __charMeasurer1 = document.createElement('char-measurer-item');
__charMeasurer1.className = "g_layout_charMeasurer1";
__charMeasurer1.textContent = "MMMMMMMMMM\nM\nM\nM\nM\nM\nM\nM\nM\nM";
var __charMeasurer2 = document.createElement('char-measurer-item');
__charMeasurer2.className = "g_layout_charMeasurer2";
__charMeasurer2.textContent = "0000000000";
__charMeasurer.appendChild(__charMeasurer1);
__charMeasurer.appendChild(__charMeasurer2);
__charMeasurer.setAttribute("aria-hidden", "true");
return /** @lends classes.WidgetBase.prototype */ {
$static: /** @lends classes.WidgetBase */ {
/** Generic click events handler */
// TODO is it still necessary to have these methods static ?
/** Generic focus events handler */
_onFocus: function(event) {
this.emit(context.constants.widgetEvents.focus, event);
},
/**
* Need to listen mouseup event on body to be able to focus an input field if selection ends outside of the field.
* If selection ends inside the field, click event will be raised
* @protected
*/
_onSelect: function() {
document.body.on('mouseup.DetectTextSelection', function(event) {
document.body.off('mouseup.DetectTextSelection');
this._element.off('mouseleave.DetectTextSelection');
}.bind(this));
this._element.on('mouseleave.DetectTextSelection', function(event) {
document.body.off('mouseup.DetectTextSelection');
this._element.off('mouseleave.DetectTextSelection');
this._onRequestFocus(event); // request focus
}.bind(this));
},
selfDataContent: {}
},
__name: "WidgetBase",
__templateName: null,
__charMeasurer: null,
__dataContentPlaceholderSelector: null,
/**
* Current widget's unique ID (GBC system wide)
* @type {?string}
*/
_uuid: null,
/**
* Incremental ID for widgets that are linked to the AUI, 0 otherwise
* @type {number}
*/
_nUuid: 0,
/**
* Widget root class name (based on widget's unique ID)
* @type {?string}
*/
_rootClassName: null,
_auiTag: null,
_auiName: null,
/**
* the parent widget
* @type {HTMLElement}
* @protected
*/
_element: null,
/**
* the parent widget
* @type {classes.WidgetGroupBase}
* @protected
*/
_parentWidget: null,
/**
* Current instance stylesheet
* @type {Object}
*/
_stylesheet: null,
/**
* stylesheet context ('global', 'window')
* @type {string}
*/
_stylingContext: "global",
/**
* the layout engine
* @type {classes.LayoutEngineBase}
* @protected
*/
_layoutEngine: null,
/**
* the layout information
* @type {classes.LayoutInformation}
* @protected
*/
_layoutInformation: null,
/**
* the user interface widget
* @type {classes.UserInterfaceWidget}
* @protected
*/
_uiWidget: null,
/**
* Application widget
* @type {classes.ApplicationWidget}
* @protected
*/
_appWidget: null,
_appHash: null,
_windowWidget: null,
_formWidget: null,
_tableWidgetBase: null,
_i18NList: null,
_i18nTranslateListener: null,
/**
* Dialog type of the widget (Input, Input Array, Display, Display Array, Construct)
* @type {string}
* @protected
*/
_dialogType: false,
_enabled: true,
_noBorder: false,
_hidden: false,
_focusable: false,
/**
* If alwaysSend, any action will send an event to VM without without checking if the value has changed.
* @public
* @type {boolean}
* @default false
*/
//_alwaysSend: false,
_startKey: null,
_endKey: null,
_inMatrix: false,
_inTable: false,
_inFirstTableRow: false,
_ignoreLayout: false,
// arabic mode
_isReversed: false,
/**
* @type {?string}
*/
_rawStyles: null,
/**
* @type {Array<string>}
*/
_applicationStyles: null,
/**
* An interruptible widget is active when the VM is processing
* @type {boolean}
*/
_interruptable: false,
_hasWebcomp: false,
/**
* @inheritDoc
* @constructs
* @param {Object} opts instantiation options
* @param {number} opts.appHash internal app hash
* @param {classes.ApplicationWidget} opts.appWidget early ApplicationWidget link
* @param {number} opts.auiTag internal aui tag id
* @param {boolean} opts.inTable internal is in table
* @param {boolean} opts.inMatrix internal is in matrix
* @param {boolean} opts.inFirstTableRow internal
* @param {boolean} opts.inScrollGrid internal is in a scroll grid
* @param {boolean} opts.ignoreLayout ignore layout char measurer
*/
constructor: function(opts) {
opts = opts || {};
this._appHash = opts.appHash;
this._appWidget = opts.appWidget;
this._auiTag = opts.auiTag;
this._inTable = opts.inTable === true;
this._inFirstTableRow = opts.inFirstTableRow === true;
this._inMatrix = opts.inMatrix === true;
this._inScrollGrid = opts.inScrollGrid === true;
this._ignoreLayout = this._inTable && !this._inFirstTableRow || opts.ignoreLayout;
this._uuid = context.InitService.uniqueIdAsString();
this._nUuid = this._auiTag ? context.InitService.uniqueId() : 0;
$super.constructor.call(this, opts);
this._rootClassName = "w_" + this._uuid;
this._initElement();
this._afterInitElement();
this._initLayout();
this._initTranslation();
if (this._auiTag) {
this._element.addClass("aui__" + this._auiTag);
this._element.setAttribute("data-aui-id", this._auiTag);
}
context.WidgetService._emit(context.constants.widgetEvents.created, this);
context.WidgetService.registerWidget(this);
},
/**
* Define the widget layout on traditional mode
* @param {!number} letterSpacing - letter spacing in pixel
* @param {!number} fieldHeight - field height in pixel
* @param {!number} heightPadding - height padding between 2 lines
*/
traditionalDisplay: function(letterSpacing, fieldHeight, heightPadding) {
var layoutInfo = this.getLayoutInformation();
if (layoutInfo) {
var left = layoutInfo.getGridX();
var top = (layoutInfo.getGridY()) * (fieldHeight + 2 * heightPadding) + heightPadding;
var width = layoutInfo.getGridWidth();
var height = layoutInfo.getGridHeight() * fieldHeight;
layoutInfo.getHostElement().toggleClass(layoutInfo.className, true);
var style = this._element.parentElement.style;
style.left = 'calc(' + left + 'ch + ' + left + ' * ' + letterSpacing + ')';
style.top = top + 'px';
style.width = 'calc(' + width + 'ch + ' + width + ' * ' + letterSpacing + ')';
style.height = height + 'px';
}
},
/**
* Returns build parameters
* @returns {{appHash: (null|*), auiTag: (null|*), inTable: (boolean|*), inFirstTableRow: (boolean|*), inMatrix: (boolean|*), inScrollGrid: *, ignoreLayout: (boolean|*)}} build parameters
* @publicdoc
*/
getBuildParameters: function() {
var opts = {
appHash: this._appHash,
appWidget: this._appWidget,
auiTag: this._auiTag,
inTable: this._inTable,
inFirstTableRow: this._inFirstTableRow,
inMatrix: this._inMatrix,
inScrollGrid: this._inScrollGrid,
ignoreLayout: this._ignoreLayout
};
return opts;
},
/**
* Destroy style sheet related to widget
* @private
*/
_destroyStyle: function() {
context.styler.removeStyleSheet(this.getUniqueIdentifier());
if (this._stylingContext === "window") {
var win = this.getWindowWidget();
var sheetId = win && win.getUniqueIdentifier() || this._appHash || "_";
context.styler.appendStyleSheet({}, this.getRootClassName(), true, sheetId);
} else {
context.styler.appendStyleSheet({}, this.getRootClassName(), true, this._appHash || "_");
}
},
/**
* @inheritDoc
*/
destroy: function() {
this._destroyStyle();
if (this._i18nTranslateChangeListener) {
this._i18nTranslateChangeListener();
}
this.emit(context.constants.widgetEvents.destroyed, this);
context.WidgetService._emit(context.constants.widgetEvents.destroyed, this);
if (this._layoutEngine) {
this._layoutEngine.destroy();
this._layoutEngine = null;
}
if (this._parentWidget && this._parentWidget.removeChildWidget) {
this._parentWidget.removeChildWidget(this);
}
if (this._layoutInformation) {
this._layoutInformation.destroy();
this._layoutInformation = null;
}
document.body.off('mouseup.DetectTextSelection');
if (this._element) {
this._element.remove();
}
this.__charMeasurer1 = null;
this.__charMeasurer2 = null;
this.__charMeasurer = null;
this._stylesheet = null;
this._uiWidget = null;
this._appWidget = null;
this._windowWidget = null;
this._formWidget = null;
this._tableWidgetBase = null;
this._element = null;
if (this._i18nTranslateListener) {
this._i18nTranslateListener();
this._i18nTranslateListener = null;
}
this._i18NList = null;
$super.destroy.call(this);
// make sure widget isn't considered as focusedNode anymore
var currentApp = context.SessionService.getCurrent() && context.SessionService.getCurrent().getCurrentApplication();
if (currentApp && currentApp.focus) {
var focusedNode = currentApp.focus.getFocusedNode();
if (focusedNode) {
var controller = focusedNode.getController();
if (controller && controller.getWidget() === this) {
currentApp.focus.setFocusedNode(null);
}
}
}
context.WidgetService.unregisterWidget(this);
},
/**
* Method called after the element is initialized
* Override in inherited widgets if necessary
* @private
*/
_afterInitElement: function() {},
/**
* Create all instances for layout management
* @protected
*/
_initLayout: function() {
this._layoutInformation = new cls.LayoutInformation(this);
this._layoutEngine = new cls.NoLayoutEngine(this);
},
/**
* function to be called by widget's layout engine when resetting the layout if needed
*/
resetLayout: function() {},
/**
* Get the widget's layout information
* @returns {classes.LayoutInformation} the widget's layout information
* @publicdoc
*/
getLayoutInformation: function() {
return this._layoutInformation;
},
/**
* Get the widget's layout engine
* @returns {classes.LayoutEngineBase} the widget's layout engine
* @publicdoc
*/
getLayoutEngine: function() {
return this._layoutEngine;
},
/**
* Get the styling context of widget style sheet (global, window or widget);
* @returns {string} widget styling context used in its style sheet
*/
getStylingContext: function() {
return this._stylingContext;
},
/**
* Setups the DOM element
* @protected
*/
_initElement: function() {
this._element = context.TemplateService.renderDOM(this.__templateName || this.__name, this.__ascendance);
var id = this.getRootClassName();
this._element.id = id;
this._element.className += ["", this.__ascendanceClasses, id, "g_measureable"].join(" ");
// TODO we add class g_measureable in all cases, we should probably just add this class if ignoreLayout=false
if (!this._ignoreLayout) {
this._initCharMeasurer();
}
},
/**
* Init the char Measurer for proper layout management
* @private
*/
_initCharMeasurer: function() {
this.__charMeasurer = __charMeasurer.cloneNode(true);
this.__charMeasurer1 = this.__charMeasurer.children[0];
this.__charMeasurer2 = this.__charMeasurer.children[1];
this._element.appendChild(this.__charMeasurer);
},
/**
* Handle request Focus
* @param {UIEvent} event - dom event
*/
_onRequestFocus: function(event) {
if (this.isInTable()) {
this.getTableWidgetBase().requestFocusFromWidget(this, event);
// TODO check if test isInMatrix is still necessary with bellow check Display Array
} else if (this.isInMatrix() ||
this._inScrollGrid ||
(this.isFocusable() &&
(this.isEnabled() || this.getDialogType() === "DisplayArray"))) {
this.emit(context.constants.widgetEvents.requestFocus, event);
}
},
/**
* Returns id widget should show application contextmenu
* @returns {boolean} true if application contextmenu should be displayed
*/
shouldShowApplicationContextMenu: function() {
return true;
},
/**
* Build/add extra actions to app contextmenu
* Must be redefine by widget which must add extra actions
* @param {classes.ContextMenuWidget} contextMenu - widget
*/
buildExtraContextMenuActions: function(contextMenu) {
// prepare copy action
var copyFunction = null;
var clipboardValue = this.getClipboardValue();
if (clipboardValue !== null) {
copyFunction = function(contextMenu) {
contextMenu.hide();
cls.ClipboardHelper.copyTo(this.getClipboardValue(), this._element);
};
}
// if copyfunction exists add it to contextmenu
if (copyFunction) {
// in table display "Copy cell" instead of "Copy"
var copyTranslation = this.isInTable() ? i18next.t("gwc.contextMenu.copyCell") : i18next.t("gwc.clipboard.copy");
contextMenu.addAction("copy", copyTranslation, null, null, {
clickCallback: copyFunction.bind(this, contextMenu)
}, true);
}
if (this.isInTable()) {
// build table contextmenu
this.getTableWidgetBase().buildExtraContextMenuActions(contextMenu);
}
},
/**
* Defines if the widget is focusable
* @param {boolean} focusable - State of focusable
* @publicdoc
*/
setFocusable: function(focusable) {
this._focusable = focusable;
this._setElementAttribute('tabindex', focusable ? '0' : null);
},
/**
* Returns if the widget is focusable
* @return {boolean} State of focusable
* @publicdoc
*/
isFocusable: function() {
return this._focusable;
},
/**
* Tests if the widget has really the DOM focus (check document.activeElement)
* @returns {boolean} true if the widget has the DOM focus
*/
hasDOMFocus: function() {
return this._element === document.activeElement;
},
/**
* Initialization of internationalization engine
* @private
*/
_initTranslation: function() {
// Will ask the translation once ready
this._i18NList = this._element.querySelectorAll("[data-i18n]");
this._i18nTranslateListener = context.I18NService.translate(this);
this._i18nTranslateChangeListener = context.I18NService.whenLangChange(function() {
context.I18NService.translate(this);
}.bind(this));
},
/**
* Translate the widget
* @publicdoc
*/
translate: function() {
var allSelectors = this._i18NList;
for (var i = 0; i < allSelectors.length; i++) {
allSelectors[i].innerHTML = i18next.t(allSelectors[i].getAttribute("data-i18n"));
}
},
/**
* Get the unique identifier of the widget
* @returns {string} the unique identifier of the widget
* @publicdoc
*/
getUniqueIdentifier: function() {
return this._uuid;
},
/**
* Get the increment identifier of the widget if linked to AUI, 0 otherwise
* @returns {number} the increment identifier of the widget if linked to AUI, 0 otherwise
*/
getAuiLinkedUniqueIdentifier: function() {
return this._nUuid;
},
/**
* Get the unique identifier of the application
* @returns {string} the unique identifier of the application
* @publicdoc
*/
getApplicationIdentifier: function() {
return this._appHash !== undefined ? this._appHash : null;
},
/**
* Get the root element of the widget
* @returns {HTMLElement} the root element of the widget
* @publicdoc
*/
getElement: function() {
return this._element;
},
/**
* Get the main class name of the widget
* @return {string} the main class name
* @publicdoc
*/
getClassName: function() {
return "gbc_" + this.__name;
},
/**
* Get the name of the widget class
* @return {string} the widget class name
* @publicdoc
*/
getName: function() {
return this.__name;
},
/**
* Get the Aui Tree Tag
* @return {string} html class ready name
* @private
*/
_getAuiTagClass: function() {
return ".aui__" + this._auiTag;
},
/**
* Get the unique class name identifying a widget instance
* @returns {*|string} the unique class name identifying a widget instance
*/
getRootClassName: function() {
return this._rootClassName;
},
/**
* Get the CSS id selector of the widget
* @param {string=} [subSelector] selector targeting an element below the widget's root node
* @param {boolean=} [appliesOnRoot] true if the returned selector should match the root too.
* @param {string} [preSelector] pre selector rule, if any
* @returns {string} the CSS selector corresponding to the requested DOM element
* @public
*/
_getCssSelector: function(subSelector, appliesOnRoot, preSelector) {
return (preSelector || "") + "#" + this.getRootClassName() +
(appliesOnRoot ? "" : " ") +
(subSelector || "");
},
/**
* Get widget style property value
* @param {?string} [selector] additional sub selector
* @param {string} property property name
* @param {boolean=} appliesOnRoot - true if the returned selector should match the root too.
* @returns {*} property value if set, undefined otherwise
* @publicdoc
*/
getStyle: function(selector, property, appliesOnRoot) {
if (!property) {
property = selector;
selector = null;
}
var cssSelector = this._getCssSelector(selector, appliesOnRoot);
return this._stylesheet && this._stylesheet[cssSelector] && this._stylesheet[cssSelector][property];
},
/**
* Updates widget style with new rules
* @param {?string|{selector:String, preSelector:String, appliesOnRoot:boolean=}} [selector] additional sub selector
* @param {Object.<string, *>} style style properties to set
* @publicdoc
*/
setStyle: function(selector, style) {
if (!style) {
style = selector;
selector = null;
}
var subSelector = selector,
preSelector = null,
appliesOnRoot = null;
if (selector && (selector.selector || selector.preSelector)) {
subSelector = selector.selector;
preSelector = selector.preSelector;
appliesOnRoot = selector.appliesOnRoot;
}
var cssSelector = this._getCssSelector(subSelector, appliesOnRoot, preSelector);
if (!this._stylesheet) {
this._stylesheet = {};
}
var localStyle = this._stylesheet[cssSelector];
if (!localStyle) {
localStyle = this._stylesheet[cssSelector] = {};
}
var keys = Object.keys(style);
for (var k = 0; k < keys.length; k++) {
if (style[keys[k]] === null) {
delete localStyle[keys[k]];
} else {
localStyle[keys[k]] = style[keys[k]];
}
}
var win = this.getWindowWidget(),
contextChanged = (this._stylingContext === "global" && win) || (this._stylingContext === "window" && !win);
context.styler.appendStyleSheet(this._stylesheet,
this.getRootClassName(), true, this._stylingContext === "widget" ? this.getUniqueIdentifier() : this.getStyleSheetId()
);
if (contextChanged) {
this._stylingContext = win ? "window" : "global";
if (win) {
context.styler.appendStyleSheet({}, this.getRootClassName(), true, this._appHash || "_");
}
}
},
getStyleSheetId: function() {
var windowWidget = this.getWindowWidget(),
windowWidgetId = windowWidget && windowWidget.getUniqueIdentifier();
return windowWidgetId || this._appHash || "_";
},
/**
* Get the raw styles from VM
* @returns {?string} the raw styles from VM
*/
getRawStyles: function() {
return this._rawStyles;
},
setApplicationStyles: function(styles) {
this._rawStyles = styles;
var i, oldClasses = this._applicationStyles,
oldlen = oldClasses ? oldClasses.length : 0,
newClasses = styles && styles.split(SPACES_RE),
newlen = newClasses ? newClasses.length : 0;
for (i = 0; i < oldlen; i++) {
if (!newClasses || newClasses.indexOf(oldClasses[i]) < 0) {
this.removeClass("gbc_style_" + oldClasses[i]);
}
}
for (i = 0; i < newlen; i++) {
if (!oldClasses || oldClasses.indexOf(newClasses[i]) < 0) {
this.addClass("gbc_style_" + newClasses[i]);
}
}
this._applicationStyles = newClasses;
},
/**
* Defines the parent widget
* @param {classes.WidgetGroupBase} widget - the widget to use as parent
* @param {Object=} options - possible options
* @param {boolean=} options.noLayoutInvalidation - won't affect parent layout
* @publicdoc
*/
setParentWidget: function(widget, options) {
options = options || {};
this._parentWidget = widget;
if (this._layoutEngine && !options.noLayoutInvalidation) {
this._layoutEngine.invalidateMeasure();
}
},
/**
* Get the parent widget
* @returns {classes.WidgetGroupBase} the parent widget
* @publicdoc
*/
getParentWidget: function() {
return this._parentWidget;
},
/**
* Get the UI widget related to the widget
* @returns {classes.UserInterfaceWidget} UserInterfaceWidget
* @publicdoc
*/
getUserInterfaceWidget: function() {
if (this._uiWidget === null) {
var result = this;
while (result && !result.isInstanceOf(gbc.classes.UserInterfaceWidget)) {
result = result.getParentWidget();
}
this._uiWidget = result;
}
return this._uiWidget;
},
/**
* Get Application Widget related to the widget
* @returns {classes.ApplicationWidget} ApplicationWidget
* @publicdoc
*/
getApplicationWidget: function() {
if (this._appWidget === null) {
var result = this;
while (result && !result.isInstanceOf(gbc.classes.ApplicationWidget)) {
result = result.getParentWidget();
}
this._appWidget = result;
}
return this._appWidget;
},
/**
* Get the Window Widget related to the widget
* @returns {classes.WindowWidget} WindowWidget
* @publicdoc
*/
getWindowWidget: function() {
if (this._windowWidget === null) {
var result = this;
while (result && !result.isInstanceOf(gbc.classes.WindowWidget)) {
result = result.getParentWidget();
}
this._windowWidget = result;
}
return this._windowWidget;
},
/**
* Get the Form Widget related to the widget
* @returns {classes.FormWidget} FormWidget
* @publicdoc
*/
getFormWidget: function() {
if (this._formWidget === null) {
var result = this;
while (result && !result.isInstanceOf(gbc.classes.FormWidget)) {
result = result.getParentWidget();
}
this._formWidget = result;
}
return this._formWidget;
},
/**
* Get the table Widget base class related to the widget
* @returns {classes.TableWidgetBase} TableWidgetBase
* @publicdoc
*/
getTableWidgetBase: function() {
if (this._tableWidgetBase === null) {
var result = this;
while (result && !result.isInstanceOf(gbc.classes.TableWidgetBase)) {
result = result.getParentWidget();
}
this._tableWidgetBase = result;
}
return this._tableWidgetBase;
},
/**
* Check if this widget is a child of a given one
* @param {classes.WidgetBase} parent the reference parent widget
* @return {boolean} true if is a child, false otherwise
* @publicdoc
*/
isChildOf: function(parent) {
var result = this.getParentWidget();
while (result && result !== parent) {
result = result.getParentWidget();
}
return Boolean(result);
},
/**
* Replace the current widget with a given one
* @param {classes.WidgetBase} widget the new widget
* @publicdoc
*/
replaceWith: function(widget) {
if (this._parentWidget) {
this._parentWidget.replaceChildWidget(this, widget);
}
},
/**
* Detach the widget from the dom
* @publicdoc
*/
detach: function() {
if (this._element && this._element.parentNode) {
this._element.parentNode.removeChild(this._element);
} else {
context.LogService.warn("Trying to detach a widget which is already outside of DOM " + this.__name);
}
},
/**
* Set widget current dialog type.
* Can be Input, Input Array, Display, Display Array or Construct
* @param {string} dialogType Dialog type
* @publicdoc
*/
setDialogType: function(dialogType) {
this._dialogType = dialogType;
},
/**
* return widget current dialog type
* @returns {string} values can be : Input, InputArray, Display, DisplayArray or Construct
* @publicdoc
*/
getDialogType: function() {
return this._dialogType;
},
/**
* Defines the enabled status of the widget
* @param {boolean} enabled true if the widget allows user interaction, false otherwise.
* @publicdoc
*/
setEnabled: function(enabled) {
if (this._enabled !== Boolean(enabled)) {
this._enabled = Boolean(enabled);
if (this._enabled) {
this.removeClass("disabled");
} else {
this.addClass("disabled");
}
}
},
/**
* Check if widget is enabled
* @returns {boolean} true if the widget allows user interaction, false otherwise.
* @publicdoc
*/
isEnabled: function() {
return this._enabled;
},
/**
* Defines if the widget should be hidden or not
* @param {boolean} hidden true if the widget is hidden, false otherwise
* @publicdoc
*/
setHidden: function(hidden) {
if (this._hidden !== Boolean(hidden)) {
this._hidden = Boolean(hidden);
if (this._element) {
if (this._hidden) {
this.addClass("hidden");
} else {
this.removeClass("hidden");
}
}
if (this._layoutEngine) {
this._layoutEngine.changeHidden(hidden);
}
this.emit(context.constants.widgetEvents.visibilityChange);
}
},
/**
* Check if the widget is hidden
* @returns {boolean} true if the widget is hidden, false otherwise
* @publicdoc
*/
isHidden: function() {
return this._hidden;
},
/**
* Check if the widget is part of layout computing
* @param {boolean} [deep] true to test against parent widgets as well
* @returns {boolean} true if the widget is part of layout computing
*/
isLayoutMeasureable: function(deep) {
if (!deep) {
return !this.isHidden();
}
if (this.isHidden()) {
return false;
}
var parent = this;
while (parent) {
if (!parent.isLayoutMeasureable()) {
return false;
}
if (parent.isLayoutTerminator() && parent.isLayoutMeasureable()) {
return true;
}
parent = parent.getParentWidget();
}
return true;
},
/**
* Check if the widget is visible
* @return {boolean} true if visible, false otherwise
* @publicdoc
*/
isVisible: function() {
return !this.isHidden();
},
/**
* Check if widget or one of its parent is hidden
* @return {boolean} true if hidden, false otherwise
*/
isHiddenRecursively: function() {
var parent = this;
while (parent) {
if (parent.isHidden()) {
return true;
}
parent = parent.getParentWidget();
}
return false;
},
/**
* Check if widget and all of its parent are visible
* @return {boolean} true if visible, false otherwise
*/
isVisibleRecursively: function() {
var parent = this;
while (parent) {
if (!parent.isVisible()) {
return false;
}
parent = parent.getParentWidget();
}
return true;
},
isLayoutTerminator: function() {
return false;
},
/**
* Remove or add borders to the widget
* @param {boolean} noBorder - true if the widget has no border class, false otherwise
* @publicdoc
*/
setNoBorder: function(noBorder) {
if (this._noBorder !== Boolean(noBorder)) {
this._noBorder = Boolean(noBorder);
if (this._noBorder) {
this.addClass("gbc_NoBorder");
} else {
this.removeClass("gbc_NoBorder");
}
}
},
/**
* Check if the widget is displayed without border
* @returns {boolean} true if the widget has no border class, false otherwise
* @publicdoc
*/
isNoBorder: function() {
return this._noBorder;
},
/**
* Set the title of the widget
* @param {string} title - the tooltip text
* @publicdoc
*/
setTitle: function(title) {
if (title === "") {
this._setElementAttribute("title", null);
this.setAriaAttribute("label", null);
} else {
this._setElementAttribute("title", title);
this.setAriaAttribute("label", title);
}
},
/**
* Get the title of the widget
* @returns {string} the tooltip text
* @publicdoc
*/
getTitle: function() {
return this._element.getAttribute("title");
},
/**
* Called when widget obtains the focus
* @param {boolean} [fromMouse] - true if focus comes from mouse event
* @param {boolean} [stayOnSameWidget] - true if we want to set the focus to the current focused widget
* @publicdoc
*/
setFocus: function(fromMouse, stayOnSameWidget) {
var userInterfaceWidget = this.getUserInterfaceWidget();
if (userInterfaceWidget) {
userInterfaceWidget.setFocusedWidget(this);
// emit current view change (used for hbox splitview)
userInterfaceWidget.emit(context.constants.widgetEvents.splitViewChange);
this.setAriaSelection();
}
// rare case when we are going to focus an hidden widget. To avoid fallback focus to body, we focus userinterface widget instead.
if (this.isHidden()) {
var uiWidget = this.getUserInterfaceWidget();
if (uiWidget) {
uiWidget.getElement().domFocus();
}
}
},
/**
* Called before setting VM focus to notify previous VM focused widget
* @publicdoc
*/
loseVMFocus: function() {},
/**
* Called before setFocus to notify previous focused widget
* @publicdoc
*/
loseFocus: function() {},
/**
* Check if widget node has VM focus
* @returns {boolean} true if widget node has VM focus
* @publicdoc
*/
hasVMFocus: function() {
var ui = this.getUserInterfaceWidget();
return !ui || this === ui.getVMFocusedWidget();
},
/**
* Check if widget node has focus (class gbc_Focus)
* @returns {boolean} true if widget node has focus
* @publicdoc
*/
hasFocus: function() {
var ui = this.getUserInterfaceWidget();
return !ui || this === ui.getFocusedWidget();
},
/**
* Checks if the widget element has the given class
* @param {string} className - class to check
* @publicdoc
*/
hasClass: function(className) {
return this._element.hasClass(className);
},
/**
* Add the given class to element
* @param {string} className - class to add
* @publicdoc
*/
addClass: function(className) {
this._element.addClass(className);
},
/**
* Remove the given class from element
* @param {string} className - class to delete
* @publicdoc
*/
removeClass: function(className) {
this._element.removeClass(className);
},
/**
* Toggle the given class to element
* @param {string} className - class to toggle
* @param {boolean=} switcher forced new state
* @publicdoc
*/
toggleClass: function(className, switcher) {
this._element.toggleClass(className, switcher);
},
/**
* Add QA informations to the widget
* @param {string} name - AUI tree name
* @param {string} value - AUI tree value
*/
setQAInfo: function(name, value) {
if (this._element) {
this._setElementAttribute("data-gqa-" + name, value);
}
},
/**
* Defines the AUI tree name of the widget
* @param {string} name the name
*/
setAuiName: function(name) {
if (this._element && (name !== this._auiName)) {
this._auiName = name;
this._setElementAttribute("data-aui-name", name);
}
},
/**
* Check if the widget is in a table
* @returns {boolean} true if the widget is in a table, false otherwise.
* @publicdoc
*/
isInTable: function() {
return this._inTable;
},
/**
* Check if the widget is in a matrix
* @returns {boolean} true if the widget is in a matrix, false otherwise.
* @publicdoc
*/
isInMatrix: function() {
return this._inMatrix;
},
/**
* Does the widget ignore layouting
* @returns {boolean} true if the widget ignore all layout.
* @publicdoc
*/
ignoreLayout: function() {
return this._ignoreLayout;
},
/**
* Set Arabic mode
* @param {boolean} rtl - true if widget is right to left
* @publicdoc
*/
setReverse: function(rtl) {
if (this._isReversed !== rtl) {
this._isReversed = rtl;
if (rtl) {
this.addClass("reverse");
} else {
this.removeClass("reverse");
}
}
},
/**
* Check if arabic mode is enabled
* @return {boolean} true if enabled
* @publicdoc
*/
isReversed: function() {
return this._isReversed;
},
/**
* Get start (for reversed mode)
* @return {string} start keyword for rtl
* @publicdoc
*/
getStart: function() {
return this.isReversed() ? "right" : "left";
},
/**
* Get end (for reversed mode)
* @return {string} end keyword for rtl
* @publicdoc
*/
getEnd: function() {
return this.isReversed() ? "left" : "right";
},
/**
* Method called when the widget is attached/detached from the DOM
* Override this in inherited widget if necessary
*/
_setDOMAttachedOrDetached: function() {
},
/**
* Returns if element is in the DOM
* @return {boolean} true if element in the DOM
*/
isElementInDOM: function() {
return Boolean(this._element) && this._element.isInDOM();
},
/**
* Could the widget get interrupt?
* @param {boolean} interruptable - true if interruptable, false otherwise
*/
setInterruptable: function(interruptable) {
this._interruptable = interruptable;
if (this._element) {
this._setElementAttribute("interruptable", interruptable ? "interruptable" : null);
}
},
/**
* returns true if widget acts as an interruptable
* @return {boolean} true if widget acts as an interruptable
*/
isInterruptable: function() {
return this._interruptable;
},
/**
* Updates widget interruptable active
* @param isActive is interruptable active?
*/
setInterruptableActive: function(isActive) {
if (this._element) {
this._setElementAttribute("interruptable-active", isActive ? "interruptable-active" : null);
}
},
/**
* Make the widget flash (basically when some action are forbidden)
*/
flash: function() {
if (this.isEnabled()) {
this.addClass("disabled");
this._registerTimeout(function() {
this.removeClass("disabled");
}.bind(this), 50);
}
},
/**
* Returns if widget has cursors
* @return {boolean} true if widget has cursors
*/
hasCursors: function() {
// if widget has setCursors & getCursors functions defined -> it supports cursors
return this.setCursors && this.getCursors;
},
/**
* Manage key
* @param {string} keyString - key string representation
* @param {Object} domKeyEvent - key event from DOM
* @param {boolean} repeat - true if key is being pressed
* @returns {boolean} returns if the domKeyEvent has been processed by the widget
*/
manageKeyDown: function(keyString, domKeyEvent, repeat) {
if (this.isInTable()) {
return this.getTableWidgetBase().manageKeyDown(keyString, domKeyEvent, repeat);
}
return false;
},
/**
* Manage key before any action
* @param {string} keyString - key string representation
* @param {Object} domKeyEvent - key event from DOM
* @param {boolean} repeat - true if key is being pressed
* @returns {boolean} returns if the domKeyEvent has been processed by the widget
*/
managePriorityKeyDown: function(keyString, domKeyEvent, repeat) {
if (this.isInTable()) {
return this.getTableWidgetBase().managePriorityKeyDown(keyString, domKeyEvent, repeat);
}
return false;
},
/**
* Manage key once released (on key up).
* @param {string} keyString - keys string representation (can be a combinaison eg: shift+a)
* @param {Object} domKeyEvent - key event from DOM
*/
manageKeyUp: function(keyString, domKeyEvent) {
if (this.isInTable()) {
this.getTableWidgetBase().manageKeyUp(keyString, domKeyEvent);
}
},
/**
* Manage mouse click
* @param {*} domEvent - mouse click event from DOM
* @returns {boolean} returns if event must be bubbled to parent DOM widget
*/
manageMouseClick: function(domEvent) {
return true;
},
/**
* Manage mouse double click
* @param {*} domEvent - mouse dblclick event from DOM
* @returns {boolean} returns if event must be bubbled to parent DOM widget
*/
manageMouseDblClick: function(domEvent) {
return true;
},
/**
* Manage mouse right click
* @param {*} domEvent - mouse click event from DOM
* @returns {boolean} returns if event must be bubbled to parent DOM widget
*/
manageMouseRightClick: function(domEvent) {
if (domEvent.shiftKey) {
return false; // don't show context menu if shift key is pressed
}
if (context.DebugService.isActive() && domEvent.ctrlKey) {
domEvent.preventCancelableDefault();
return false; // right click + CTRL is used to show debugTree
}
this._onRequestFocus(domEvent); // request focus
if (this.shouldShowApplicationContextMenu()) {
var appWidget = this.getApplicationWidget();
if (appWidget && context.ThemeService.getValue("theme-disable-context-menu") === false) {
if (!domEvent.target.elementOrParent("gbc_ContextMenuWidget")) { // if right-click is not on a contextmenu
appWidget.showContextMenu(domEvent.data ? domEvent.data[0] : domEvent, this);
} else {
// If right-click on context menu item: use a regular click instead
domEvent.preventCancelableDefault();
this.manageMouseClick(domEvent);
}
return false; // if contextmenu is managed by this widget don't bubble
}
}
return false;
},
/**
* Define the aria role of this widget,
* Mostly already defined in template
* @param {string} roleName - aria role name to set
*/
setAriaRole: function(roleName) {
if (roleName && this._element) {
this._setElementAttribute("role", roleName);
}
},
/**
* Set the aria attribute of this widget,
* @param {string} attrName - aria attribute Name to set
* @param {*} attrVal - aria attribute value to set
*/
setAriaAttribute: function(attrName, attrVal) {
if (this._element && attrName) {
this._setElementAttribute("aria-" + attrName, attrVal);
}
},
/**
* Get the aria attribute of this widget,
* @param {string} attrName - aria attribute Name to get
* @return {*} aria attribute value
*/
getAriaAttribute: function(attrName) {
if (this._element && attrName) {
return this._element.getAttribute("aria-" + attrName);
}
},
/**
* Set the aria-selected attribute to help screen-reader to know wich widget is the current one
*/
setAriaSelection: function() {
this.domAttributesMutator(function() {
var currentSelected = document.querySelector('[aria-selected="true"]');
if (currentSelected) {
currentSelected.removeAttribute('aria-selected');
}
});
this.setAriaAttribute('selected', "true");
},
/**
* Set the widget has "expanded" for better accessibility
* @param {Boolean} expanded - true if widget is expanded, false otherwise
*/
setAriaExpanded: function(expanded) {
this.setAriaAttribute("expanded", expanded);
},
/**
* Get the value to put in the clipboard when copying
* @return {?string|number}
*/
getClipboardValue: function() {
return null;
},
/**
* Helper method to update attirbutes in DOM using buffering system
* @param {string} attr the attribute name
* @param {*} val the attribute new value
* @param {string|Function} [elementSelector] a string identifier of this class member or a method returning the element to set the attributes value
* @protected
*/
_setElementAttribute: function(attr, val, elementSelector) {
this.domAttributesMutator(function(attr, val, elementSelector) {
var target = null;
if (elementSelector) {
if (typeof elementSelector === "string") {
target = this[elementSelector];
} else if (typeof elementSelector === "function") {
target = elementSelector(this);
}
} else {
target = this._element;
}
if (target) {
if (val === null || val === "" || typeof(val) === "undefined") {
target.removeAttribute(attr);
} else {
target.setAttribute(attr, val.toString());
}
}
}.bind(this, attr, val, elementSelector));
},
/**
* Helper method to update textContent in DOM using buffering system
* @param {string} text the new text
* @param {string|function} [elementSelector] a string identifier of this class member or a method returning the element to set the textContent
* @protected
*/
_setTextContent: function(text, elementSelector) {
this.domAttributesMutator(function(text, elementSelector) {
var target = null;
if (elementSelector) {
if (typeof elementSelector === "string") {
target = this[elementSelector];
} else if (typeof elementSelector === "function") {
target = elementSelector(this);
}
} else {
target = this._element;
}
if (target) {
target.textContent = text;
}
}.bind(this, text, elementSelector));
},
/**
* Use this to update attributes of dom nodes using a buffering system
* @param fn the function to bufferize - don't forget to bind to context
*/
domAttributesMutator: function(fn) {
var appWidget = this.getApplicationWidget();
if (!appWidget || !appWidget.domAttributesMutationBuffer(fn, this)) {
fn();
}
},
/**
* @param fn the function to bufferize - don't forget to bind to context
*/
afterDomMutator: function(fn) {
var appWidget = this.getApplicationWidget();
if (!appWidget || !appWidget.afterDomMutationBuffer(fn, this)) {
this._registerAnimationFrame(fn);
}
},
/**
* Add the widget in the DOM
*/
addInDom: function() {
if (!this.getElement().parentNode && this._replacerElement && this._replacerElement.parentNode) {
this._replacerElement.parentNode.replaceChild(this.getElement(), this._replacerElement);
}
},
/**
* Remove widget from DOM and replace it by an empty DIV
*/
removeFromDom: function() {
if (this.getElement() && this.getElement().parentNode) {
this.getElement().parentNode.replaceChild(this.getReplacer(), this.getElement());
}
},
/**
* DOM node intended to replace a widget node temporarely
* @returns {HTMLDivElement}
*/
getReplacer: function() {
if (!this._replacerElement) {
this._replacerElement = document.createElement("div");
this._replacerElement.setAttribute("tabindex", "0");
this._replacerElement.classList.add("replacer");
}
return this._replacerElement;
}
};
});
});