view class doc
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486/// FOURJS_START_COPYRIGHT(D,2015)
/// Property of Four Js*
/// (c) Copyright Four Js 2015, 2019. 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;

          $super.constructor.call(this, opts);
          this._uuid = context.InitService.uniqueIdAsString();
          this._nUuid = this._auiTag ? context.InitService.uniqueId() : 0;
          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);
        },

        /**
         * 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() {
          if (this._stylingContext === "widget") {
            context.styler.removeStyleSheet(this.getUniqueIdentifier());
          } else 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();

          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);

          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);
        },

        /**
         * 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 !!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 !== enabled) {
            this._enabled = !!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 !== hidden) {
            this._hidden = !!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();
          } else {
            if (this.isHidden()) {
              return false;
            } else {
              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 !== noBorder) {
            this._noBorder = !!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
         * @publicdoc
         */
        setFocus: function(fromMouse) {
          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 !!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);
          }
        }
      };
    });
  });