view class doc
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896/// FOURJS_START_COPYRIGHT(D,2015)
/// Property of Four Js*
/// (c) Copyright Four Js 2015, 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('TableColumnWidget', ['WidgetGroupBase'],
  function(context, cls) {

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

        /**
         * the title widget
         * @type {classes.TableColumnTitleWidget}
         */
        _title: null,
        /**
         * the aggregate widget
         * @type {classes.TableColumnAggregateWidget}
         */
        _aggregate: null,
        /**
         * is parent widget a tree view
         * @type {boolean}
         */
        _isTreeView: false,
        /**
         * is column always visible
         * @type {boolean}
         */
        _isUnhidable: false,
        /**
         * is column draggable/movable
         * @type {boolean}
         */
        _isMovable: true,
        /**
         * is column resizable
         * @type {boolean}
         */
        _isSizable: true,
        /**
         * is column always hidden
         * @type {boolean}
         */
        _alwaysHidden: false,
        /**
         * column order
         * @type {number}
         */
        _order: -1,
        /**
         * is current focused column
         * @type {boolean}
         */
        _current: false,
        /**
         * is drag & drop enabled
         * @type {boolean}
         */
        _dndItemEnabled: false,
        /**
         * is column detached from dom
         * @type {boolean}
         */
        _itemsDetachedFromDom: false,
        /**
         * column width (store settings or measured)
         * @type {number}
         */
        _width: null,
        /**
         * column width from store settings
         * @type {number}
         */
        _defaultWidth: null,
        /**
         * column measured width
         * @type {number}
         */
        _initialWidth: null,
        /**
         * is column left frozen
         * @type {boolean}
         */
        _isLeftFrozen: false,
        /**
         * is column right frozen
         * @type {boolean}
         */
        _isRightFrozen: false,
        /**
         * is column first child item measured (flag used for layout engine)
         * @type {boolean}
         */
        _firstWidgetMeasured: false,

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

        /**
         * @inheritDoc
         */
        _initLayout: function() {
          this._layoutInformation = new cls.LayoutInformation(this);
          this._layoutEngine = new cls.TableColumnLayoutEngine(this);
        },

        /**
         * @inheritDoc
         */
        _initElement: function() {
          $super._initElement.call(this);
          this._title = cls.WidgetFactory.createWidget("TableColumnTitle", this.getBuildParameters());
          this._title.setParentWidget(this);
        },

        /**
         * @inheritDoc
         */
        resetLayout: function() {
          this._firstWidgetMeasured = false;
          this._width = null;
          this.attachItemsToDom();
        },

        /**
         * @inheritDoc
         */
        destroy: function() {
          this._title.destroy();
          this._title = null;
          if (this._aggregate) {
            this._aggregate.destroy();
            this._aggregate = null;
          }
          this.destroyChildren();
          $super.destroy.call(this);
        },

        /**
         * @inheritDoc
         */
        manageMouseClick: function(domEvent) {

          if (domEvent.target.hasClass("gbc_TableAfterLastItemZone")) { // click on afterLastItemZone
            this.emit(context.constants.widgetEvents.tableColumnAfterLastItemClick);
            return false;
          }
          return true;
        },

        /**
         * @inheritDoc
         */
        addChildWidget: function(widget, options) {
          options = options || {};

          var opts = this.getBuildParameters();
          opts.isTreeItem = this._isTreeView;

          var tableColumnItem = null;
          if (widget.getParentWidget() !== null) {
            // if widget has already a parent it means that it as already been attached
            tableColumnItem = widget.getParentWidget();
          } else {
            tableColumnItem = cls.WidgetFactory.createWidget("TableColumnItem", opts);
            tableColumnItem.setDndEnabled(this._dndItemEnabled);
            tableColumnItem.addChildWidget(widget);
            $super.addChildWidget.call(this, tableColumnItem, options);
          }

          if (this.getParentWidget()) {
            // if first widget has not been measured need to relayout to measure it
            if (!this._firstWidgetMeasured) {
              this.resetMeasure();
              if (this.getParentWidget().resetMeasure) {
                this.getParentWidget().resetMeasure();
              }

            }
            // Set the current row
            tableColumnItem.setCurrent(tableColumnItem.getItemIndex() === this.getParentWidget().getCurrentRow());
          }
        },

        /**
         * Invalidate measure to force width & row height measure in next layout cycle
         */
        resetMeasure: function() {
          this.getLayoutEngine().forceMeasurement();
          this.getLayoutEngine().invalidateMeasure();
        },

        /**
         * @inheritDoc
         */
        removeChildWidget: function(widget) {
          var item = widget.getParentWidget() !== this ? widget.getParentWidget() : widget;
          $super.removeChildWidget.call(this, item);
        },

        /**
         *  Remove all items (container element) from DOM
         */
        detachItemsFromDom: function() {
          if (this._itemsDetachedFromDom === false) {
            this._itemsDetachedFromDom = true;
            this.getContainerElement().remove();
          }
        },

        /**
         * Attach all items (container element) to DOM
         */
        attachItemsToDom: function() {
          if (this._itemsDetachedFromDom === true) {
            this._itemsDetachedFromDom = false;
            this.getContainerElement().insertAt(0, this.getElement());
          }
        },

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

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

        /**
         * Indicates if table column is left frozen
         * @returns {boolean} returns true if column is left frozen
         * @publicdoc
         */
        isLeftFrozen: function() {
          return this._isLeftFrozen === true;
        },

        /**
         * Indicates if table column is right frozen
         * @returns {boolean} returns true if column is right frozen
         * @publicdoc
         */
        isRightFrozen: function() {
          return this._isRightFrozen === true;
        },

        /**
         * @returns {number} returns 0 if unfrozen, 1 if left frozen and 2 if right frozen
         */
        getFrozenIndex: function() {
          return this.isFrozen() ? (this.isLeftFrozen() ? 1 : 2) : 0;
        },

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

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

        /**
         * Returns true if column must be always visible
         * @returns {boolean} true if column is always visible
         * @publicdoc
         */
        isAlwaysVisible: function() {
          return false;
        },

        /**
         * 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;
            if (b) {
              this.getTitleWidget().getResizer().removeClass("unresizable");
            } else {
              this.getTitleWidget().getResizer().addClass("unresizable");
            }
          }
        },

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

        /**
         * 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;
        },

        /**
         * Update aggregate width
         */
        updateAggregateWidth: function() {
          // search column which contain an aggregate
          var tableWidget = this.getParentWidget();
          if (tableWidget && tableWidget.hasFooter()) {
            var columns = tableWidget.getOrderedColumns();
            for (var i = this.getOrderedColumnIndex(); i < columns.length; i++) {
              var col = columns[i];
              if (col.getAggregateWidget() && !col.isHidden()) {
                this.afterDomMutator(function(col) {
                  col.setAggregate(col.getAggregateWidget().getText());
                }.bind(this, col));
                break;
              }
            }
          } else if (this.getAggregateWidget()) {
            this.afterDomMutator(function() {
              this.setAggregate(this.getAggregateWidget().getText());
            }.bind(this));
          }
        },

        /**
         * Set/add an aggregate cell
         * @param text - aggregate text & value
         * @param width -
         */
        setAggregate: function(text, width) {
          var tableWidget = this.getParentWidget();
          if (text !== "") {
            if (!this._aggregate) {
              this._aggregate = cls.WidgetFactory.createWidget("TableColumnAggregate", this.getBuildParameters());
              this._aggregate.setParentWidget(this);
              var footer = null;
              if (this._isLeftFrozen) {
                footer = tableWidget.getLeftColumnsFooter();
              } else if (this._isRightFrozen) {
                footer = tableWidget.getRightColumnsFooter();
              } else {
                footer = tableWidget.getColumnsFooter();
              }
              if (footer) {
                footer.appendChild(this._aggregate.getElement());
                tableWidget.setHasFooter(true);
              }
            }

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

            this._aggregate.setText(text);
            this._aggregate.setHidden(this.isHidden());
            this._aggregate.setOrder(this.getOrder());

            var aggregateWidth = this.getWidth();

            if (!width) {
              var columns = tableWidget.getOrderedColumns();
              for (var i = this.getOrderedColumnIndex() - 1; i >= 0; i--) {
                var col = columns[i];
                if (col._aggregate === null && col.getFrozenIndex() === this.getFrozenIndex()) {
                  if (!col.isHidden()) {
                    aggregateWidth += col.getWidth();
                  }
                } else {
                  if (!col.isHidden()) {
                    break;
                  }
                }
              }
            } else {
              aggregateWidth = width;
            }
            this._aggregate.computeWidth(aggregateWidth);

          } else {
            if (this._aggregate) {
              this._aggregate.setText(text);
            }
          }
        },

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

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

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

        /**
         * Returns title widget of the column
         * @returns {classes.TableColumnTitleWidget} the title widget
         * @publicdoc
         */
        getTitleWidget: function() {
          return this._title;
        },

        /**
         * Returns aggregate widget of the column
         * @returns {classes.TableColumnAggregateWidget} the aggregate widget
         */
        getAggregateWidget: function() {
          return this._aggregate;
        },

        /**
         * Sets column text (title)
         * @param {string} text - the text (title) to display
         * @publicdoc
         */
        setTitleText: function(text) {
          this.getTitleWidget().setText(text);
        },

        /**
         * Returns column text (title)
         * @returns {string} the column text
         * @publicdoc
         */
        getText: function() {
          return this.getTitleWidget().getText();
        },

        /**
         * Set text alignment
         * @param {string} align - (left, center, right)
         */
        setTextAlign: function(align) {

          if (this._textAlign !== align) {
            this._textAlign = align;

            var titleWidget = this.getTitleWidget();

            if (titleWidget.isAutoTextAlignement()) {
              titleWidget.setTextAlign(align);
            }

            if (this._aggregate) {
              this._aggregate.setTextAlign(align);
            }
          }
        },

        /**
         * Sets the width of column
         * @param {number} width - column width (pixels)
         * @publicdoc
         */
        setWidth: function(width) {
          width = Math.round(width);
          if (this._width !== width) {
            this._width = width;
            this.setStyle({
              "width": width + "px !important"
            });
            this.getTitleWidget().setWidth(width);
            this.updateAggregateWidth();
          }
        },

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

        /**
         * Returns column width (pixels)
         * @returns {?number} column width
         * @publicdoc
         */
        getWidth: function() {
          return this._width;
        },

        /**
         * Set measured column width as initial
         * @param {?number} width - column width (pixels)
         * @publicdoc
         */
        setInitialWidth: function(width) {
          this._initialWidth = width;
        },

        /**
         * Returns measured column width
         * @returns {?number} initial column width
         * @publicdoc
         */
        getInitialWidth: function() {
          return this._initialWidth;
        },

        /**
         * Set the default column width from store settings
         * @param {?number} width - column width (pixels)
         * @publicdoc
         */
        setDefaultWidth: function(width) {
          this._defaultWidth = width;
        },

        /**
         * Return the column default width defined in store settings
         * @returns {?number} default column width
         * @publicdoc
         */
        getDefaultWidth: function() {
          return this._defaultWidth;
        },

        /**
         * Reset width column (set with to initial width)
         * @publicdoc
         */
        resetWidth: function() {
          this.setWidth(this._initialWidth);
        },

        /**
         * Returns column width style
         * @returns {string} column width (ex:"42px")
         */
        getWidthStyle: function() {
          return this.getStyle("width");
        },

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

            this.getTitleWidget().setOrder(index);
            if (this._aggregate) {
              this._aggregate.setOrder(index);
            }

            let tableWidget = this.getParentWidget();
            if (tableWidget) {
              tableWidget.resetOrderedColumns();
              tableWidget.updateAllAggregate();
            }
          }
        },

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

        /**
         * Changes current row
         * @param {number} row - current row
         */
        setCurrentRow: function(row) {
          var children = this.getChildren();
          var length = children.length;
          for (var i = 0; i < length; ++i) {
            var tableColumnItem = children[i];
            tableColumnItem.setCurrent(i === row);

            var tableWidget = this.getParentWidget();
            if (tableWidget) {
              this.getElement().toggleClass("highlight", tableWidget.isHighlightCurrentRow());
              this.getElement().toggleClass("nohighlight", !tableWidget.isHighlightCurrentRow());
            }
          }
        },

        /**
         * 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._current !== Boolean(current)) {
            this._current = Boolean(current);
            this.getElement().toggleClass("currentColumn", Boolean(current));
          }

          if (this._current) {
            var tableWidget = this.getParentWidget();
            if (tableWidget) {
              this.getElement().toggleClass("highlight", tableWidget.isHighlightCurrentCell());
              this.getElement().toggleClass("nohighlight", !tableWidget.isHighlightCurrentCell());
            }
            this.attachItemsToDom(); // current should be always attached to DOM
          }

        },

        /**
         * Check if column is current one
         * @returns {boolean}  true if the column is the current one, false otherwise
         * @publicdoc
         */
        isCurrent: function() {
          return this._current;
        },

        /**
         * Updates rows visibility depending on the number of visible rows defined in the parent TableWidget
         */
        updateRowsVisibility: function() {
          var visibleRows = this.getParentWidget().getVisibleRows();
          var children = this.getChildren();
          for (var i = 0; i < children.length; ++i) {
            var tableColumnItemWidget = children[i];
            tableColumnItemWidget.setHidden(i >= visibleRows);
          }
        },

        /**
         * Sets if the a row is selected (mrs)
         * @param {number} row - index of the row
         * @param {boolean} selected - true if the row should be selected, false otherwise
         */
        setRowSelected: function(row, selected) {
          var children = this.getChildren();
          if (row < children.length) {
            children[row].setSelected(selected);
          }
        },

        /**
         * Check if a row is selected
         * @param {number} row - index of the row
         * @returns {boolean} true if the row is selected, false otherwise
         */
        isRowSelected: function(row) {
          var children = this.getChildren();
          if (row < children.length) {
            return children[row].isSelected();
          }
          return false;
        },

        /**
         * @inheritDoc
         */
        setHidden: function(state) {
          if (this.isHidden() !== state) { // optimization : do not recalculate when no state change
            $super.setHidden.call(this, state);
            //hide title as well
            this.getTitleWidget().setHidden(state);
            //hide aggregate as well too
            if (this._aggregate) {
              this._aggregate.setHidden(state);
            }
            var tableWidget = this.getParentWidget();
            if (tableWidget) {
              tableWidget.updateAllAggregate();
              tableWidget.synchronizeHeadersHeight();
            }
          }
        },

        /**
         * Returns afterLastItemZone element
         * @returns {HTMLElement} afterLastItemZone element
         */
        getAfterLastItemZone: function() {
          return this._element.getElementsByClassName("gbc_TableAfterLastItemZone")[0];
        },

        /**
         * Returns widget at the specified row
         * @param {number} row - row of item
         * @returns {classes.WidgetBase} widget
         * @publicdoc
         */
        getWidgetAt: function(row) {
          return this.getChildren()[row] &&
            this.getChildren()[row].getChildren() &&
            this.getChildren()[row].getChildren()[0];
        },

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

            // measure title width
            var titleWidget = this.getTitleWidget();
            titleWidget.getElement().addClass("g_TableMeasuring");
            var maxWidth = titleWidget.getElement().getBoundingClientRect().width;
            titleWidget.getElement().removeClass("g_TableMeasuring");

            // measure widgets width
            if (children.length > 0) {
              var firstWidget = children[0].getChildren()[0];
              var measureDataElement = firstWidget.getLayoutEngine().getDataContentMeasureElement();
              var 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");
                var 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.setWidthFromUserInteraction(maxWidth);
          }
        },

        /**
         * Enable Dnd of items
         * @param {boolean} b
         */
        setDndItemEnabled: function(b) {
          if (this._dndItemEnabled !== b) {

            this._dndItemEnabled = b;
            var items = this.getChildren();
            for (var j = 0; j < items.length; j++) {
              var item = items[j];
              item.setDndEnabled(b);
            }
          }
        },

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

        /**
         * Handle dragOver event
         * @param {Object} evt - dragover event
         */
        onDragOverAfterLastItem: function(evt) {
          this.emit(gbc.constants.widgetEvents.tableDragOver, this.getParentWidget().getVisibleRows(), evt);
        }
      };
    });
    cls.WidgetFactory.registerBuilder('TableColumn', cls.TableColumnWidget);
  });