view class doc
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136/// FOURJS_START_COPYRIGHT(D,2020)
/// Property of Four Js*
/// (c) Copyright Four Js 2020, 2022. 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('RTableColumnWidget', ['WidgetGroupBase', 'WidgetFactory'],
  function(context, cls) {

    /**
     * Responsive Table column widget.
     * @class RTableColumnWidget
     * @memberOf classes
     * @extends classes.WidgetGroupBase
     * @publicdoc
     */
    cls.RTableColumnWidget = context.oo.Class(cls.WidgetGroupBase, function($super) {
      return /** @lends classes.RTableColumnWidget.prototype */ {
        __name: "RTableColumnWidget",

        /** @type {boolean} */
        _isTreeView: false,

        /** @type {boolean} */
        _isAlwaysHidden: false,

        /** @type {boolean} */
        _isUnhidable: false,

        /** @type {boolean} */
        _isLeftFrozen: false,
        /** @type {boolean} */
        _isLastLeftFrozen: false,
        /** @type {boolean} */
        _isRightFrozen: false,
        /** @type {boolean} */
        _isFirstRightFrozen: false,

        /** @type {number} */
        _leftFrozenPosition: 0,
        /** @type {number} */
        _rightFrozenPosition: 0,

        /**
         * The aggregate widget
         * @type {classes.LabelWidget}
         */
        _aggregateLabelWidget: null,

        /** @type {classes.ContextMenuWidget} */
        _contextMenu: null,

        /**
         * column width (user value)
         * @type {number|null}
         */
        _userWidth: null,

        /** @type {boolean} */
        _isSorted: false,

        /**
         * column order
         * @type {number}
         */
        _order: -1,

        /**
         * is column draggable/movable
         * @type {boolean}
         */
        _isMovable: true,

        /**
         * is column resizable
         * @type {boolean}
         */
        _isSizable: true,

        /** @type {boolean} */
        _isCurrent: false,

        /** @type {number|null} */
        _resizerDragX: null,

        /**
         * @constructs
         * @param {*} opts - Options passed to the constructor
         */
        constructor: function(opts) {
          opts = (opts || {});
          opts.inTable = true;
          this._isTreeView = opts.isTreeView;
          this._tableWidgetBase = opts.tableWidget;

          this.getTableWidgetBase().addColumn(this);
          $super.constructor.call(this, opts);
        },

        /**
         * @inheritDoc
         */
        destroy: function() {
          this._children = [];

          if (this._aggregateLabelWidget) {
            this._aggregateLabelWidget.destroy();
            this._aggregateLabelWidget = null;
          }

          if (this._contextMenu) {
            this._contextMenu.destroyChildren();
            this._contextMenu.destroy();
            this._contextMenu = null;
          }

          $super.destroy.call(this);
        },

        /**
         * @inheritDoc
         */
        managePriorityKeyDown: function(keyString, domKeyEvent, repeat) {
          let keyProcessed = false;
          if (this._contextMenu && this._contextMenu.isVisible()) {
            keyProcessed = this._contextMenu.managePriorityKeyDown(keyString, domKeyEvent, repeat);
          }
          return keyProcessed;
        },

        /**
         * @inheritDoc
         */
        manageMouseClick: function(domEvent) {
          if (domEvent.target.hasClass("headerText")) { // click on header text
            this.getTableWidgetBase().emit(context.constants.widgetEvents.tableHeaderSort, this.getColumnIndex());
          }
          return false;
        },

        /**
         * @inheritDoc
         */
        manageMouseDblClick: function(domEvent) {
          let target = domEvent.target;

          if (target.hasClass("resizer")) { // double click on resizer
            this.onResizerDoubleClick(domEvent);
            return false;
          }

          return true;
        },

        /**
         * @inheritDoc
         */
        manageMouseRightClick: function(domEvent) {
          if (domEvent.shiftKey) {
            return false;
          }
          domEvent.preventCancelableDefault();
          this._buildContextMenu(domEvent);

          return false;
        },

        /**
         * @inheritDoc
         */
        addChildWidget: function(widget, options) {

          options = options || {};
          options.isRowData = true;

          options.position = typeof(options.position) === "undefined" ? null : options.position;
          options.rowIndex = options.position !== null ? options.position : this._children.length;

          options.colWidget = this;
          options.colIndex = this.getColumnIndex();

          if (this.isHidden()) { // if col is hidden do not add item in the DOM
            options.noDOMInsert = true;
          }
          this.getTableWidgetBase().addChildWidget(widget, options);
        },

        /**
         * Build context menu and show it
         */
        _buildContextMenu: function(domEvent) {

          if (this._contextMenu) {
            this._contextMenu.destroyChildren();
            this._contextMenu.destroy();
            this._contextMenu = null;
          }

          let opts = this.getBuildParameters();
          opts.inTable = false; // contextmenu is not really in the table, it is outside
          opts.ignoreLayout = true;

          this._contextMenu = cls.WidgetFactory.createWidget("ContextMenu", opts);
          this._contextMenu.allowMultipleChoices(true);
          this._contextMenu.setParentWidget(this);
          this._contextMenu.setColor(this.getColor());
          this._contextMenu.setBackgroundColor(this.getBackgroundColor());
          this._contextMenu.onClose(function() {
            this.afterDomMutator(function() {
              if (this._contextMenu) {
                this._contextMenu.destroyChildren();
                this._contextMenu.destroy();
                this._contextMenu = null;
              }
            }.bind(this));
          }.bind(this), true);

          let tableWidget = this.getTableWidgetBase();
          let columns = tableWidget.getColumns();

          if (tableWidget.isFrozenTable()) {
            let leftFrozenLabel = cls.WidgetFactory.createWidget("Label", opts);
            let rightFrozenLabel = cls.WidgetFactory.createWidget("Label", opts);
            let unfreezeLabel = cls.WidgetFactory.createWidget("Label", opts);
            let freezeIndex = 0;
            let columnCount = 0;

            leftFrozenLabel.setValue(i18next.t("gwc.contextMenu.freezeLeft"));
            rightFrozenLabel.setValue(i18next.t("gwc.contextMenu.freezeRight"));
            unfreezeLabel.setValue(i18next.t("gwc.contextMenu.unfreezeAll"));

            leftFrozenLabel.addClass("gbc_freezeLeft_action");
            rightFrozenLabel.addClass("gbc_freezeRight_action");
            unfreezeLabel.addClass("gbc_unfreezeAll_action");

            leftFrozenLabel.setEnabled(!this.isRightFrozen());
            rightFrozenLabel.setEnabled(!this.isLeftFrozen());

            this._contextMenu.addChildWidget(leftFrozenLabel, {
              clickCallback: function() {
                if (leftFrozenLabel.isEnabled()) {
                  freezeIndex = this.getOrderedColumnIndex() + 1;
                  tableWidget.setLeftFrozenColumns(freezeIndex);
                  tableWidget.updateFrozenColumns();
                  tableWidget.emit(gbc.constants.widgetEvents.tableLeftFrozen, freezeIndex);
                }
              }.bind(this)
            });
            this._contextMenu.addChildWidget(rightFrozenLabel, {
              clickCallback: function() {
                if (rightFrozenLabel.isEnabled()) {
                  columnCount = columns.length;
                  freezeIndex = this.getOrderedColumnIndex();
                  tableWidget.setRightFrozenColumns(columnCount - freezeIndex);
                  tableWidget.updateFrozenColumns();
                  tableWidget.emit(gbc.constants.widgetEvents.tableRightFrozen, columnCount - freezeIndex);
                }
              }.bind(this)
            });
            this._contextMenu.addChildWidget(unfreezeLabel, {
              clickCallback: function() {
                tableWidget.setLeftFrozenColumns(0);
                tableWidget.setRightFrozenColumns(0);
                tableWidget.updateFrozenColumns();
                tableWidget.emit(gbc.constants.widgetEvents.tableLeftFrozen, 0);
                tableWidget.emit(gbc.constants.widgetEvents.tableRightFrozen, 0);
              }.bind(this)
            });
          }

          // Reset sort order action
          if (this._isSorted) {
            if (tableWidget.isFrozenTable()) {
              this._contextMenu.addSeparator();
            }

            let resetLabel = cls.WidgetFactory.createWidget("Label", opts);
            resetLabel.setValue(i18next.t("gwc.contextMenu.restoreColumnSort"));
            resetLabel.addClass("gbc_restoreColumnSort_action");
            this._contextMenu.addChildWidget(resetLabel, {
              clickCallback: function() {
                tableWidget.emit(context.constants.widgetEvents.tableHeaderSort, -1);
              }.bind(this)
            });
          }

          // hide other columns action
          let hideOtherColumnsLabel = cls.WidgetFactory.createWidget("Label", opts);
          hideOtherColumnsLabel.setValue(i18next.t("gwc.contextMenu.hideAllButSelected"));
          hideOtherColumnsLabel.addClass("gbc_hideAllButSelected_action");
          this._contextMenu.addChildWidget(hideOtherColumnsLabel, {
            clickCallback: function() {
              let columns = tableWidget.getColumns();
              for (let i = 0; i < columns.length; i++) {
                let tc = columns[i];
                if (!tc.isAlwaysHidden() && !tc.isUnhidable() && tc !== this) {
                  tc.emit(gbc.constants.widgetEvents.tableShowHideCol, "hide");
                }
              }
              // we hide other columns but we must show current column
              this.emit(gbc.constants.widgetEvents.tableShowHideCol, "show");
            }.bind(this)
          });

          // beware setFocus should not raise a scroll event (it will immediately close contextmenu)
          this._element.domFocus(null, this.getElement());

          this._contextMenu.parentElement = this.getElement();
          this._contextMenu.show();
        },

        /**
         * Returns column item at the specified index (row)
         * @param {number} index - index of the item (row)
         * @returns {classes.RTableItemWidget} item widget
         * @publicdoc
         */
        getItem: function(index) {
          let widget = this._children[index];
          return widget;
        },

        /**
         * Sets column title text
         * @param {string} text - the title text
         * @publicdoc
         */
        setTitleText: function(text) {
          this._setTextContent(text, function() {
            return this._element.getElementsByClassName("headerText")[0];
          }.bind(this));

          // add data-header on each item
          for (const item of this._children) {
            item.getElement().setAttribute("data-header", text);
          }
        },

        /**
         * Returns column title text
         * @returns {string} the title text
         * @publicdoc
         */
        getText: function() {
          return this._element.getElementsByClassName("headerText")[0].textContent;
        },

        /**
         * Returns the element used to resize the column
         * @returns {HTMLElement} the resizer element
         * @publicdoc
         */
        getResizerElement: function() {
          return this._element.getElementsByClassName("resizer")[0];
        },

        /**
         * Returns index of the column in the parent table (vm aui index)
         * @returns {number} index of the column in the table
         * @publicdoc
         */
        getColumnIndex: function() {
          let tableWidget = this.getTableWidgetBase();
          if (tableWidget) {
            return tableWidget.getColumns().indexOf(this);
          }
          return -1;
        },

        /**
         * Returns index of the column in the parent table only in visible columns
         * @returns {number} index of the visible column in the table
         * @publicdoc
         */
        getVisibleColumnIndex: function() {
          let tableWidget = this.getTableWidgetBase();

          let index = 0;
          let found = false;
          for (const colWidget of tableWidget.getOrderedColumns()) {
            if (colWidget === this) {
              found = true;
              break;
            }
            if (!colWidget.isHidden()) {
              index++;
            }
          }

          return found ? index : -1;
        },

        /**
         * Returns index of the column in the parent table (visual index)
         * @returns {number} index of the column in the table
         * @publicdoc
         */
        getOrderedColumnIndex: function() {
          let tableWidget = this.getTableWidgetBase();
          if (tableWidget) {
            return tableWidget.getOrderedColumns().indexOf(this);
          }
          return -1;
        },

        /**
         * @inheritDoc
         */
        setHidden: function(state) {
          if (this.isHidden() !== state) { // optimization : do not recalculate when no state change
            $super.setHidden.call(this, state);

            // remove/add from/to DOM all items of the columns
            const visibleColIndex = this.getVisibleColumnIndex();
            for (const itemWidget of this.getChildren()) {
              let rowWidget = itemWidget.getParentWidget();
              if (rowWidget) {
                if (state) {
                  rowWidget._removeChildWidgetFromDom(itemWidget);
                } else {
                  rowWidget._addChildWidgetToDom(itemWidget, visibleColIndex);
                }
              }
            }

            if (this._aggregateLabelWidget) {
              this._aggregateLabelWidget.setHidden(this.isHidden());
            }

            // update table
            let tableWidget = this.getTableWidgetBase();
            tableWidget.update(false, tableWidget.isFlipped(), !tableWidget.isFlipped());
          }
        },

        /**
         * Sets if the column must be always hidden
         * @param {boolean} b - is always hidden ?
         * @publicdoc
         */
        setAlwaysHidden: function(b) {
          this._isAlwaysHidden = b;
        },

        /**
         * Returns true if column must be always hidden
         * @returns {boolean} true if column is always hidden
         * @publicdoc
         */
        isAlwaysHidden: function() {
          return this._isAlwaysHidden;
        },

        /**
         * Sets if the column can be hidden by the user
         * @param {boolean} b - is not hiddable ?
         */
        setUnhidable: function(b) {
          this._isUnhidable = b;
        },

        /**
         * Returns true if column is unhidable
         * @returns {boolean} true if column is unhidable
         */
        isUnhidable: function() {
          return this._isUnhidable;
        },

        /**
         * Sets if the column can be moved by the user
         * @param {boolean} b - is movable ?
         * @publicdoc
         */
        setMovable: function(b) {
          this._isMovable = b;
        },

        /**
         * Returns true if column is movable
         * @returns {boolean} true if column is movable
         * @publicdoc
         */
        isMovable: function() {
          return this._isMovable;
        },

        /**
         * Sets if the column can be sized by the user
         * @param {boolean} b - is sizable ?
         * @publicdoc
         */
        setSizable: function(b) {
          if (this._isSizable !== b) {
            this._isSizable = b;
            this.getResizerElement().toggleClass("unresizable", !b);
          }
        },

        /**
         * Returns true if column is sizable
         * @returns {boolean} true il column is sizable
         * @publicdoc
         */
        isSizable: function() {
          return this._isSizable;
        },

        /**
         * Returns true if column is a tree
         * @returns {boolean} true if is a tree
         * @publicdoc
         */
        isTreeView: function() {
          return this._isTreeView;
        },

        /**
         * Sets the sort decorator caret.
         * @param {string} sortType - "asc", "desc" or ""
         */
        setSortDecorator: function(sortType) {

          let sortClass = null;
          if (sortType === "asc") {
            sortClass = "sort-asc";
          } else if (sortType === "desc") {
            sortClass = "sort-desc";
          }

          this._element.removeClass("sort-asc");
          this._element.removeClass("sort-desc");

          if (sortClass !== null) {
            this._element.addClass(sortClass);
          }

          this._isSorted = (sortClass !== null);
        },

        /**
         * Set text alignment
         * @param {string} align - (left, center, right)
         * @param {string} [force] - force alignment even if not auto
         */
        setTextAlign: function(align, force = false) {

          let tableWidget = this.getTableWidgetBase();

          // alignment is applied only if headerAlignment is auto
          if (force || tableWidget.getHeaderAlignment() === "auto") {
            if (align && this._textAlign !== align) {
              this._textAlign = align;

              this.setStyle({
                "text-align": align
              });

              if (this._aggregateLabelWidget) {
                this._aggregateLabelWidget.setTextAlign(align);

                let rightAlign = (align === "right");
                this._aggregateLabelWidget.setStyle(".gbc-label-text-container", {
                  "float": rightAlign ? "right" : null
                }); // float right to overflow on left side
                this._aggregateLabelWidget.setStyle({
                  "overflow": rightAlign ? "visible" : null
                }); // activate overflow on left side
              }

            }
          }
        },

        /**
         * Set/add an aggregate footer
         * @param {String} text - aggregate text & value, if null don't upgrade aggregate value, if "" do nothing
         */
        setAggregate: function(text) {
          if (text && text !== "") {
            let tableWidget = this.getTableWidgetBase();
            tableWidget.setHasFooter(true);

            if (!this._aggregateLabelWidget) {
              this._aggregateLabelWidget = cls.WidgetFactory.createWidget("Label", this.getBuildParameters());

              let options = {};
              options.footerAggregateItem = true;
              options.colWidget = this;
              tableWidget.getFooterAggregatesRowWidget().addChildWidget(this._aggregateLabelWidget, options);
            }

            if (text !== null) {
              this._aggregateLabelWidget.setValue(text);
            }
            this._aggregateLabelWidget.setHidden(this.isHidden());

            this._aggregateLabelWidget.setStyle({
              "grid-column-start": this.getVisibleColumnIndex() + 1
            });

            if (this._textAlign) {
              this._aggregateLabelWidget.setTextAlign(this._textAlign);
            }

          }
        },

        /**
         * Sets the width of column (define from user)
         * @param {?number} width - column width (pixels)
         * @publicdoc
         */
        setUserWidth: function(width) {
          if (this._userWidth !== width) {
            this._userWidth = width;

            // update table
            let tableWidget = this.getTableWidgetBase();
            tableWidget.update(false, false, true);
          }
        },

        /**
         * Set width (from a user interaction)
         * @param {number} width - column width (pixels)
         */
        setUserWidthFromInteraction: function(width) {
          this.setUserWidth(width);
          this.emit(gbc.constants.widgetEvents.tableResizeCol, width);
        },

        /**
         * Returns column width (define from user)
         * @returns {?number} column width (pixels)
         * @publicdoc
         */
        getUserWidth: function() {
          return this._userWidth;
        },

        /**
         * Reset width column
         * @publicdoc
         */
        resetWidth: function() {
          this.setUserWidth(null);
        },

        /**
         * Returns column width (user or if not define measured)
         * @returns {?number} column width (pixels)
         * @publicdoc
         */
        getWidth: function() {
          return this.getUserWidth() || this.getLayoutInformation().getMeasured().getWidth();
        },

        /**
         * Sets index order of column
         * @param {number} index - order index
         */
        setOrder: function(index) {
          if (this._order !== index) {

            // set order style on header column
            this.setStyle({
              "order": index
            });

            // set order style on aggregate
            if (this._aggregateLabelWidget) {
              this._aggregateLabelWidget.setStyle({
                "order": index
              });
            }

            // set order style on each column item
            for (const itemWidget of this.getChildren()) {
              itemWidget.setOrder(index);
            }

            this._order = index;

            // update table
            let tableWidget = this.getTableWidgetBase();
            tableWidget.update(true, false, true);
          }
        },

        /**
         * Returns index order of column
         * @returns {number} order index
         * @publicdoc
         */
        getOrder: function() {
          return this._order;
        },

        /**
         * Defines if column is the current or not
         * @param {boolean} current - true if the column is the current one, false otherwise
         */
        setCurrent: function(current) {

          if (this._isCurrent !== Boolean(current)) {
            this._isCurrent = Boolean(current);
            this.getElement().toggleClass("currentColumn", this._isCurrent);

            for (const itemWidget of this.getChildren()) {
              itemWidget.setCurrentColumn(this._isCurrent);
            }
          }
        },

        /**
         * Returns if column is the current one
         * @returns {boolean} is the current column ?
         * @publicdoc
         */
        isCurrent: function() {
          return this._isCurrent;
        },

        /**
         * Handle resizer double click event
         */
        onResizerDoubleClick: function() {
          this.autoSetWidth();
        },

        /**
         * Auto set width according to max length of column values
         */
        autoSetWidth: function() {
          if (!this.isHidden()) {
            let children = this.getChildren();
            let width = null;
            let widget = null;
            let tableColumnItemWidget = null;
            let i = 0;

            // measure title width

            this.getElement().addClass("g_TableMeasuring");
            let maxWidth = this.getElement().getBoundingClientRect().width;
            this.getElement().removeClass("g_TableMeasuring");

            // measure widgets width
            if (children.length > 0) {
              let firstWidget = children[0].getChildren()[0];
              let measureDataElement = firstWidget.getLayoutEngine().getDataContentMeasureElement();
              let hasInputElement = firstWidget.getElement().getElementsByTagName("input").length > 0;
              // if widgets are inputs, use the first charMeasurer to measure to search the larger
              if (hasInputElement && measureDataElement) {
                this.getElement().addClass("g_measuring");
                firstWidget.getElement().addClass("g_TableMeasuring");
                let initialContent = measureDataElement.textContent;

                for (i = 0; i < children.length; ++i) {
                  tableColumnItemWidget = children[i];
                  widget = tableColumnItemWidget.getChildren()[0];
                  measureDataElement.textContent = widget.getValue();
                  width = firstWidget.getElement().getBoundingClientRect().width;
                  if (width > maxWidth) {
                    maxWidth = width;
                  }
                }
                measureDataElement.textContent = initialContent;
                firstWidget.getElement().removeClass("g_TableMeasuring");
                this.getElement().removeClass("g_measuring");
              }
              // if widgets are not inputs, measure each widget and keep the larger size
              else {
                for (i = 0; i < children.length; ++i) {
                  tableColumnItemWidget = children[i];
                  widget = tableColumnItemWidget.getChildren()[0];

                  widget.getElement().addClass("g_TableMeasuring");
                  width = widget.getElement().getBoundingClientRect().width;
                  widget.getElement().removeClass("g_TableMeasuring");

                  if (width > maxWidth) {
                    maxWidth = width;
                  }
                }
              }
            }
            this.setUserWidthFromInteraction(maxWidth);
          }
        },

        /**
         * Enable Dnd of items
         * @param {boolean} b
         */
        setDndItemEnabled: function(b) {
          let items = this.getChildren();
          for (let i = 0; i < items.length; i++) {
            let item = items[i];
            item.setDndEnabled(b);
          }
        },

        /**
         * Handle drop event
         */
        onDropAfterLastItem: function() {
          this.emit(gbc.constants.widgetEvents.tableDrop, this.getTableWidgetBase().getVisibleRows());
        },

        /**
         * Handle dragOver event
         * @param {Object} evt - dragover event
         */
        onDragOverAfterLastItem: function(evt) {
          this.emit(gbc.constants.widgetEvents.tableDragOver, this.getTableWidgetBase().getVisibleRows(), evt);
        },

        // ============== START - Resize Event/DnD FUNCTIONS ===================
        /**
         * Handle resizer dragStart event on resizer element
         * @param {Object} evt dragstart event
         */
        onResizerDragStart: function(evt) {
          if (this.isSizable()) {
            this.getTableWidgetBase()._dndMode = "columnResizing";
            this._resizerDragX = evt.clientX || evt.screenX;
          }
        },

        /**
         * Handle resizer drag event on resizer element
         */
        onResizerDrag: function() {
          if (this.getTableWidgetBase()._dndMode === "columnResizing") {
            if (!this._resizerDragX || !this.getTableWidgetBase()._dndMouseDragX) {
              return;
            }
            let size = this.getTableWidgetBase()._dndMouseDragX - this._resizerDragX;
            let initialWidth = this.getWidth();

            let newWidth = initialWidth + size;
            if (this.isReversed()) {
              newWidth = initialWidth - size;
            }
            if (newWidth > 30) {
              this._resizerDragX = this.getTableWidgetBase()._dndMouseDragX;
              this.setUserWidthFromInteraction(newWidth);
            }
          }
        },

        /**
         * Handle resizer dragEnd event on resizer element
         * @param {Object} evt dragend event
         */
        onResizerDragEnd: function(evt) {
          this._resizerDragX = null;
          evt.preventCancelableDefault();
          this.getTableWidgetBase()._dndMode = null;
        },

        // ============== END - Resize Event/DnD FUNCTIONS ===================

        // ============== START - Reordering Event/DnD FUNCTIONS ===================
        /**
         * Handle reordering dragStart event
         * @param {Object} evt - dragstart event
         */
        onReorderingDragStart: function(evt) {
          if (this.isMovable()) {
            this.getTableWidgetBase()._dndMode = "columnReordering";
            this.getTableWidgetBase()._dndDraggedColumnWidget = this;
          } else {
            evt.preventCancelableDefault();
          }
        },

        /**
         * Handle reordering dragEnd event
         * @param {Object} evt - dragend event
         */
        onReorderingDragEnd: function(evt) {
          if (this.getTableWidgetBase()._dndReorderingDragOverWidget !== null) {
            this.getTableWidgetBase()._dndReorderingDragOverWidget.getElement()
              .removeClass("reordering_left").removeClass("reordering_right");
            this.getTableWidgetBase()._dndReorderingDragOverWidget = null;
          }
          this.getTableWidgetBase()._dndMode = null;
          this.getTableWidgetBase()._dndDraggedColumnWidget = null;
        },

        /**
         * Handle reordering drop event
         */
        onReorderingDrop: function() {
          if (this.getTableWidgetBase()._dndMode === "columnReordering") {
            if (this.getTableWidgetBase()._dndReorderingDragOverWidget &&
              this.getTableWidgetBase()._dndDraggedColumnWidget !== this.getTableWidgetBase()._dndReorderingDragOverWidget) {
              this.reorderColumns(
                this.getTableWidgetBase()._dndDraggedColumnWidget, this.getTableWidgetBase()._dndReorderingDragOverWidget);
            }
          }
        },

        /**
         * Reorder columns
         * @param {classes.RTableColumnWidget} draggedColumn - dragged column
         * @param {classes.RTableColumnWidget} dropColumn - drop column
         */
        reorderColumns: function(draggedColumn, dropColumn) {
          let tableWidget = this.getTableWidgetBase();

          let orderedColumns = tableWidget.getOrderedColumns();
          let newOrderedColumns = orderedColumns.slice();

          let dragColIndex = newOrderedColumns.indexOf(draggedColumn);
          let dropColIndex = newOrderedColumns.indexOf(dropColumn);

          newOrderedColumns.removeAt(dragColIndex);
          newOrderedColumns.insert(this.getTableWidgetBase()._dndDraggedColumnWidget, dropColIndex);

          // First set correct order on each column
          for (let i = 0; i < newOrderedColumns.length; i++) {
            let col = newOrderedColumns[i];
            col.setOrder(i);
          }

          // And only after emit signal to send tabIndex to VM
          // (Don't do it in the same loop)
          for (let i = 0; i < newOrderedColumns.length; i++) {
            let col = newOrderedColumns[i];
            col.emit(context.constants.widgetEvents.tableOrderColumn, i);
          }
        },

        /**
         * Handle reordering dragOver event
         * @param {Object} evt - dragover event
         */
        onReorderingDragOver: function(evt) {

          if (this.getTableWidgetBase()._dndMode === "columnReordering") {

            var lastReorderingDragOverColumnWidget = this.getTableWidgetBase()._dndReorderingDragOverWidget;
            if (lastReorderingDragOverColumnWidget !== this) {

              if (lastReorderingDragOverColumnWidget !== null) {
                lastReorderingDragOverColumnWidget.getElement()
                  .removeClass("reordering_left").removeClass("reordering_right");
              }
              this.getTableWidgetBase()._dndReorderingDragOverWidget = this;
            }

            var overIndex = this.getOrderedColumnIndex();
            var startIndex = this.getTableWidgetBase()._dndDraggedColumnWidget.getOrderedColumnIndex();

            this.getElement().addClass(overIndex >= startIndex ? "reordering_right" : "reordering_left");
          }
        },

        /**
         * Handle reordering dragLeave event
         */
        onReorderingDragLeave: function() {
          if (this.getTableWidgetBase()._dndMode === "columnReordering") {
            this.getElement().removeClass("reordering_left").removeClass("reordering_right");
            this.getTableWidgetBase()._dndReorderingDragOverWidget = null;
          }
        },

        // ============== END - Reordering Event/DnD FUNCTIONS ===================

        // ============== START - FROZEN COLUMNS FUNCTIONS ===================
        /**
         * Sets if the column is left frozen
         * @param {boolean} b - is left frozen ?
         * @publicdoc
         */
        setLeftFrozen: function(b) {
          this._isLeftFrozen = b;
          this.toggleClass("leftFrozen", b);
          this.toggleClass("lastLeftFrozen", this.isLastLeftFrozen());

          let tableWidget = this.getTableWidgetBase();

          // search the sum of all previous columns widths
          this._leftFrozenPosition = 0;
          for (const colWidget of tableWidget.getOrderedColumns()) {
            if (colWidget === this) {
              break;
            }
            if (!colWidget.isHidden()) {
              this._leftFrozenPosition += colWidget.getWidth();
            }
          }

          for (const itemWidget of this._children) {
            itemWidget.setLeftFrozen(b);
          }

          if (b) {
            this.setStyle({
              "left": this._leftFrozenPosition + "px"
            });
          }
        },

        /**
         * Sets if the column is right frozen
         * @param {boolean} b - is right frozen ?
         * @publicdoc
         */
        setRightFrozen: function(b) {
          this._isRightFrozen = b;
          this.toggleClass("rightFrozen", b);
          this.toggleClass("firstRightFrozen", this.isFirstRightFrozen());

          let tableWidget = this.getTableWidgetBase();

          // search the sum of all previous columns widths
          this._rightFrozenPosition = 0;
          for (let i = tableWidget.getOrderedColumns().length - 1; i >= 0; i--) {
            const colWidget = tableWidget.getOrderedColumns()[i];
            if (colWidget === this) {
              break;
            }
            if (!colWidget.isHidden()) {
              this._rightFrozenPosition += colWidget.getWidth();
            }
          }

          for (const itemWidget of this._children) {
            itemWidget.setRightFrozen(b);
          }

          if (b) {
            this.setStyle({
              "right": this._rightFrozenPosition + "px"
            });
          }
        },

        /**
         * Returns if the column is left frozen
         * @return {boolean} is left frozen ?
         * @publicdoc
         */
        isLeftFrozen: function() {
          return this._isLeftFrozen;
        },

        /**
         * Sets if the column is last left frozen
         * @param {boolean} b - is last left frozen ?
         * @publicdoc
         */
        setLastLeftFrozen: function(b) {
          this._isLastLeftFrozen = b;
        },

        /**
         * Returns if the column is the last left frozen
         * @return {boolean} is last left frozen ?
         * @publicdoc
         */
        isLastLeftFrozen: function() {
          return this._isLastLeftFrozen;
        },

        /**
         * Returns the left frozen position (pixels)
         * @return {number} left frozen column position
         */
        getLeftFrozenPosition: function() {
          return this._leftFrozenPosition;
        },

        /**
         * Returns if the column is right frozen
         * @return {boolean} is right frozen ?
         * @publicdoc
         */
        isRightFrozen: function() {
          return this._isRightFrozen;
        },

        /**
         * Sets if the column is first right frozen
         * @param {boolean} b - is first right frozen ?
         * @publicdoc
         */
        setFirstRightFrozen: function(b) {
          this._isFirstRightFrozen = b;
        },

        /**
         * Returns if the column is the first right frozen
         * @return {boolean} is first right frozen ?
         * @publicdoc
         */
        isFirstRightFrozen: function() {
          return this._isFirstRightFrozen;
        },

        /**
         * Returns the right frozen position (pixels)
         * @return {number} right frozen column position
         */
        getRightFrozenPosition: function() {
          return this._rightFrozenPosition;
        },

        /**
         * Indicates if table has frozen columns
         * @returns {boolean} returns true if table has frozen columns
         * @publicdoc
         */
        isFrozen: function() {
          return this.isRightFrozen() || this.isLeftFrozen();
        },

        // ============== END - FROZEN COLUMNS FUNCTIONS ===================

      };
    });
    cls.WidgetFactory.registerBuilder('RTableColumn', cls.RTableColumnWidget);
  });