123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641/// FOURJS_START_COPYRIGHT(D,2015)
/// Property of Four Js*
/// (c) Copyright Four Js 2015, 2024. 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 cut/copy/past actions
var copyFunction = null;
var cutFunction = null;
var pasteFunction = null;
var clipboardValue = this.getClipboardValue();
var authAction = this.getClipboardAuthorizedAction();
if (authAction.copy) {
if (clipboardValue !== null) {
copyFunction = function(contextMenu) {
contextMenu.hide();
cls.ClipboardHelper.copyTo(clipboardValue, this._element);
}.bind(this);
}
}
if (authAction.paste && this.isEnabled() && this.isVisible()) {
if (context.ClipboardService.canPaste() && !context.ClipboardService.isClipboardEmty()) {
pasteFunction = function(contextMenu) {
contextMenu.hide();
context.ClipboardService.pasteTo(this);
};
}
//We can only have a cut where we can paste
if (authAction.cut && copyFunction) {
cutFunction = function(contextMenu) {
contextMenu.hide();
cls.ClipboardHelper.copyTo(this.getClipboardValue(), this._element);
this.cutValue();
};
}
}
if (cutFunction) {
contextMenu.addAction("cut", i18next.t("gwc.clipboard.cut"), null, null, {
clickCallback: cutFunction.bind(this, contextMenu)
}, true);
}
if (this.getDialogType() !== "DisplayArray" && this.getDialogType() !== "Display") {
// if copyfunction exists add it to contextmenu
if (authAction.copy && copyFunction) {
contextMenu.addAction("copy", i18next.t("gwc.clipboard.copy"), null, "Control+C", {
clickCallback: copyFunction.bind(this, contextMenu)
}, true);
}
}
if (this.isInTable()) {
var widgetValue = this.getClipboardValue(true);
var widgetTable = this.getTableWidgetBase();
if (widgetValue !== null && widgetTable.canShowCopyCellAndRow()) {
var copyWidgetValueFunction = function(contextMenu) {
contextMenu.hide();
cls.ClipboardHelper.copyTo(widgetValue, this._element);
}.bind(this);
contextMenu.addAction("copyCell", i18next.t("gwc.contextMenu.copyCell"), null, null, {
clickCallback: copyWidgetValueFunction.bind(this, contextMenu)
}, true);
}
// build table contextmenu
this.getTableWidgetBase().buildExtraContextMenuActions(contextMenu);
}
// if pasteFunction exists add it to contextmenu
if (pasteFunction) {
contextMenu.addAction("paste", i18next.t("gwc.clipboard.paste"), null, null, {
clickCallback: pasteFunction.bind(this, contextMenu)
}, true);
}
},
/**
* 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 clipboard on paste data to the widget
* @param {string} text - pasted text
* @returns {boolean|string} returns if event must be bubbled to parent DOM widget
*/
manageClipboardPaste: function(text) {
// Do whatever with text data
return text;
},
/**
* Manage data to be copied to the clipboard
* @param {string} copiedText - is the text copied by clipboard
* @returns {string} the modified text, default is unchanged
*/
manageClipboardCopy: function(copiedText) {
// Do whatever with text data you want to be added to the clipboard when copy
return copiedText;
},
/**
* 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 (domEvent.target.elementOrParent && appWidget && context.ThemeService.getValue("theme-disable-context-menu") === false) {
if (!domEvent.target.elementOrParent("gbc_ContextMenuWidget")) { // if right-click is not on a contextmenu
context.ClipboardService.getClipboardData().then(function() {
appWidget.showContextMenu(domEvent.data ? domEvent.data[0] : domEvent, this);
}.bind(this));
domEvent.preventCancelableDefault();
} 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
* @param {boolean} ignoreSelection true to send the widget value
* @return {?string|number}
*/
getClipboardValue: function(ignoreSelection) {
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;
},
/**
* Get the clipboard authorized actions
* @returns {object}
*/
getClipboardAuthorizedAction: function() {
return {
paste: false,
copy: false,
cut: false
};
}
};
});
});