UNPKG

tui-grid

Version:

TOAST UI Grid : Powerful data grid control supported by TOAST UI

504 lines (430 loc) 17.4 kB
/** * @fileoverview Manage coordinates of rows * @author NHN. FE Development Lab <dl_javascript@nhn.com> */ 'use strict'; var _ = require('underscore'); var snippet = require('tui-code-snippet'); var Model = require('../base/model'); var util = require('../common/util'); var constMap = require('../common/constMap'); var dimensionConst = constMap.dimension; var frameConst = constMap.frame; var CELL_BORDER_WIDTH = dimensionConst.CELL_BORDER_WIDTH; /** * @module model/coordColumn * @param {Object} attrs - Attributes * @param {Object} options - Options * @extends module:base/model * @ignore */ var CoordColumn = Model.extend(/** @lends module:model/coordColumn.prototype */{ initialize: function(attrs, options) { this.dimensionModel = options.dimensionModel; this.columnModel = options.columnModel; /** * An array of the fixed flags of the columns * @private * @type {boolean[]} */ this._fixedWidthFlags = null; /** * An array of the minimum width of the columns * @private * @type {number[]} */ this._minWidths = null; /** * Whether the column width is modified by user. * @type {boolean} */ this._isModified = false; this.listenTo(this.columnModel, 'columnModelChange', this.resetColumnWidths); this.listenTo(this.dimensionModel, 'change:width', this._onDimensionWidthChange); if (options.domEventBus) { this.listenTo(options.domEventBus, 'dragmove:resizeColumn', this._onDragResize); this.listenTo(options.domEventBus, 'dblclick:resizeColumn', this._onDblClick); } this.resetColumnWidths(); }, defaults: { widths: [], resizable: true }, /** * Reset the width of each column by using initial setting of column models. */ resetColumnWidths: function() { var columns = this.columnModel.getVisibleColumns(null, true); var commonMinWidth = this.dimensionModel.get('minimumColumnWidth'); var widths = []; var fixedFlags = []; var minWidths = []; _.each(columns, function(columnModel) { var columnWidth = columnModel.width || 'auto'; var fixedWidth = !isNaN(columnWidth); var width, minWidth; // Meta columns are not affected by common 'minimumColumnWidth' value if (util.isMetaColumn(columnModel.name)) { minWidth = width; } else { minWidth = columnModel.minWidth || commonMinWidth; } width = fixedWidth ? columnWidth : minWidth; if (width < minWidth) { width = minWidth; } // If the width is not assigned (in other words, the width is not positive number), // set it to zero (no need to worry about minimum width at this point) // so that #_fillEmptyWidth() can detect which one is empty. // After then, minimum width will be applied by #_applyMinimumWidth(). widths.push(width); minWidths.push(minWidth); fixedFlags.push(fixedWidth); }, this); this._fixedWidthFlags = fixedFlags; this._minWidths = minWidths; this._setColumnWidthVariables(this._calculateColumnWidth(widths), true); }, /** * Event handler for dragmove event on domEventBus * @param {module:event/gridEvent} ev - GridEvent * @private */ _onDragResize: function(ev) { this.setColumnWidth(ev.columnIndex, ev.width); }, /** * Event handler for dblclick event on domEventBus * @param {module:event/gridEventd} ev - GridEvent * @private */ _onDblClick: function(ev) { this.restoreColumnWidth(ev.columnIndex); }, /** * widths 로 부터, lside 와 rside 의 전체 너비를 계산하여 저장한다. * @param {array} widths - 컬럼 넓이값 배열 * @param {boolean} [saveWidths] - 저장 여부. true이면 넓이값 배열을 originalWidths로 저장한다. * @private */ _setColumnWidthVariables: function(widths, saveWidths) { var totalWidth = this.dimensionModel.get('width'); var frozenBorderWidth = this.dimensionModel.get('frozenBorderWidth'); var maxLeftSideWidth = this.dimensionModel.getMaxLeftSideWidth(); var frozenCount = this.columnModel.getVisibleFrozenCount(true); var rsideWidth, lsideWidth, lsideWidths, rsideWidths; lsideWidths = widths.slice(0, frozenCount); rsideWidths = widths.slice(frozenCount); lsideWidth = this._getFrameWidth(lsideWidths); if (maxLeftSideWidth && maxLeftSideWidth < lsideWidth) { lsideWidths = this._adjustLeftSideWidths(lsideWidths, maxLeftSideWidth); lsideWidth = this._getFrameWidth(lsideWidths); widths = lsideWidths.concat(rsideWidths); } rsideWidth = totalWidth - lsideWidth; this.set({ widths: widths }); this.dimensionModel.set({ rsideWidth: rsideWidth, lsideWidth: lsideWidth - frozenBorderWidth }); if (saveWidths) { this.set('originalWidths', _.clone(widths)); } this.trigger('columnWidthChanged'); }, /** * columnFrozenCount 가 적용되었을 때, window resize 시 left side 의 너비를 조정한다. * @param {Array} lsideWidths 열고정 영역의 너비 리스트 배열 * @param {Number} totalWidth grid 전체 너비 * @returns {Array} 열고정 영역의 너비 리스트 * @private */ _adjustLeftSideWidths: function(lsideWidths, totalWidth) { var i = lsideWidths.length - 1; var minimumColumnWidth = this.dimensionModel.get('minimumColumnWidth'); var currentWidth = this._getFrameWidth(lsideWidths); var diff = currentWidth - totalWidth; var changedWidth; if (diff > 0) { while (i >= 0 && diff > 0) { changedWidth = Math.max(minimumColumnWidth, lsideWidths[i] - diff); diff -= lsideWidths[i] - changedWidth; lsideWidths[i] = changedWidth; i -= 1; } } else if (diff < 0) { lsideWidths[i] += Math.abs(diff); } return lsideWidths; }, /** * calculate column width list * @param {Array.<Number>} widths - widths * @returns {Array.<Number>} * @private */ _calculateColumnWidth: function(widths) { widths = this._fillEmptyWidth(widths); widths = this._applyMinimumWidth(widths); widths = this._adjustWidths(widths); return widths; }, /** * Sets the width of columns whose width is not assigned by distributing extra width to them equally. * @param {number[]} widths - An array of column widths * @returns {number[]} - A new array of column widths * @private */ _fillEmptyWidth: function(widths) { var totalWidth = this.dimensionModel.getAvailableTotalWidth(widths.length); var remainTotalWidth = totalWidth - util.sum(widths); var emptyIndexes = []; _.each(widths, function(width, index) { if (!width) { emptyIndexes.push(index); } }); return this._distributeExtraWidthEqually(widths, remainTotalWidth, emptyIndexes); }, /** * widths 로부터 보더 값을 포함하여 계산한 frameWidth 를 구한다. * @param {Array} widths 너비 리스트 배열 * @returns {Number} 계산된 frame 너비값 * @private */ _getFrameWidth: function(widths) { var frameWidth = 0; if (widths.length) { frameWidth = util.sum(widths) + ((widths.length + 1) * CELL_BORDER_WIDTH); } return frameWidth; }, /** * Adds extra widths of the column equally. * @param {number[]} widths - An array of column widths * @param {number} totalExtraWidth - Total extra width * @returns {number[]} - A new array of column widths * @private */ _addExtraColumnWidth: function(widths, totalExtraWidth) { var fixedFlags = this._fixedWidthFlags; var columnIndexes = []; _.each(fixedFlags, function(flag, index) { if (!flag) { columnIndexes.push(index); } }); return this._distributeExtraWidthEqually(widths, totalExtraWidth, columnIndexes); }, /** * Reduces excess widths of the column equally. * @param {number[]} widths - An array of column widths * @param {number} totalExcessWidth - Total excess width (negative number) * @returns {number[]} - A new array of column widths * @private */ _reduceExcessColumnWidth: function(widths, totalExcessWidth) { var minWidths = this._minWidths; var fixedFlags = this._fixedWidthFlags; var availableList = []; _.each(widths, function(width, index) { if (!fixedFlags[index]) { availableList.push({ index: index, width: width - minWidths[index] }); } }); return this._reduceExcessColumnWidthSub(_.clone(widths), totalExcessWidth, availableList); }, /** * Reduce the (remaining) excess widths of the column. * This method will be called recursively by _reduceExcessColumnWidth. * @param {number[]} widths - An array of column Width * @param {number} totalRemainWidth - Remaining excess width (negative number) * @param {object[]} availableList - An array of infos about available column. * Each item of the array has {index:number, width:number}. * @returns {number[]} - A new array of column widths * @private */ _reduceExcessColumnWidthSub: function(widths, totalRemainWidth, availableList) { var avgValue = Math.round(totalRemainWidth / availableList.length); var newAvailableList = []; var columnIndexes; _.each(availableList, function(available) { // note that totalRemainWidth and avgValue are negative number. if (available.width < Math.abs(avgValue)) { totalRemainWidth += available.width; widths[available.index] -= available.width; } else { newAvailableList.push(available); } }); // call recursively until all available width are less than average if (availableList.length > newAvailableList.length) { return this._reduceExcessColumnWidthSub(widths, totalRemainWidth, newAvailableList); } columnIndexes = _.pluck(availableList, 'index'); return this._distributeExtraWidthEqually(widths, totalRemainWidth, columnIndexes); }, /** * Distributes the extra width equally to each column at specified indexes. * @param {number[]} widths - An array of column width * @param {number} extraWidth - Extra width * @param {number[]} columnIndexes - An array of indexes of target columns * @returns {number[]} - A new array of column widths * @private */ _distributeExtraWidthEqually: function(widths, extraWidth, columnIndexes) { var length = columnIndexes.length; var avgValue = Math.round(extraWidth / length); var errorValue = (avgValue * length) - extraWidth; // to correct total width var resultList = _.clone(widths); _.each(columnIndexes, function(columnIndex) { resultList[columnIndex] += avgValue; }); if (columnIndexes.length) { resultList[_.last(columnIndexes)] -= errorValue; } return resultList; }, /** * Makes all width of columns not less than minimumColumnWidth. * @param {number[]} widths - 컬럼 넓이값 배열 * @returns {number[]} - 수정된 새로운 넓이값 배열 * @private */ _applyMinimumWidth: function(widths) { var minWidths = this._minWidths; var appliedList = _.clone(widths); _.each(appliedList, function(width, index) { var minWidth = minWidths[index]; if (width < minWidth) { appliedList[index] = minWidth; } }); return appliedList; }, /** * Adjust the column widths to make them fit into the dimension. * @param {number[]} widths - An array of column width * @param {boolean} [fitToReducedTotal] - If set to true and the total width is smaller than dimension(width), * the column widths will be reduced. * @returns {number[]} - A new array of column widths * @private */ _adjustWidths: function(widths, fitToReducedTotal) { var columnLength = widths.length; var availableWidth = this.dimensionModel.getAvailableTotalWidth(columnLength); var totalExtraWidth = availableWidth - util.sum(widths); var fixedCount = _.filter(this._fixedWidthFlags).length; var adjustedWidths; if (totalExtraWidth > 0 && (columnLength > fixedCount)) { adjustedWidths = this._addExtraColumnWidth(widths, totalExtraWidth); } else if (fitToReducedTotal && totalExtraWidth < 0) { adjustedWidths = this._reduceExcessColumnWidth(widths, totalExtraWidth); } else { adjustedWidths = widths; } return adjustedWidths; }, /** * width 값 변경시 각 column 별 너비를 계산한다. * @private */ _onDimensionWidthChange: function() { var widths = this.get('widths'); if (!this._isModified) { widths = this._adjustWidths(widths, true); } this._setColumnWidthVariables(widths); }, /** * L side 와 R side 에 따른 widths 를 반환한다. * @param {String} [whichSide] 어느 영역인지 여부. L,R 중 하나를 인자로 넘긴다. 생략시 전체 columnList 반환 * @returns {Array} 조회한 영역의 widths */ getWidths: function(whichSide) { var columnFrozenCount = this.columnModel.getVisibleFrozenCount(true); var widths = []; switch (whichSide) { case frameConst.L: widths = this.get('widths').slice(0, columnFrozenCount); break; case frameConst.R: widths = this.get('widths').slice(columnFrozenCount); break; default: widths = this.get('widths'); break; } return widths; }, /** * L, R 중 하나를 입력받아 frame 의 너비를 구한다. * @param {String} [whichSide] 지정하지 않을 경우 전체 너비. * @returns {Number} 해당 frame 의 너비 */ getFrameWidth: function(whichSide) { var columnFrozenCount = this.columnModel.getVisibleFrozenCount(true); var widths = this.getWidths(whichSide); var frameWidth = this._getFrameWidth(widths); if (_.isUndefined(whichSide) && columnFrozenCount > 0) { frameWidth += CELL_BORDER_WIDTH; } return frameWidth; }, /** * columnResize 발생 시 index 에 해당하는 컬럼의 width 를 변경하여 반영한다. * @param {Number} index 너비를 변경할 컬럼의 인덱스 * @param {Number} width 변경할 너비 pixel값 */ setColumnWidth: function(index, width) { var widths = this.get('widths'); var minWidth = this._minWidths[index]; if (widths[index]) { widths[index] = Math.max(width, minWidth); this._setColumnWidthVariables(widths); this._isModified = true; } }, /** * Returns column index from X-position relative to the body-area * @param {number} posX - X-position relative to the body-area * @param {boolean} withMeta - Whether the meta columns go with this calculation * @returns {number} Column index * @private */ indexOf: function(posX, withMeta) { var widths = this.getWidths(); var totalColumnWidth = this.getFrameWidth(); var adjustableIndex = (withMeta) ? 0 : this.columnModel.getVisibleMetaColumnCount(); var columnIndex = 0; if (posX >= totalColumnWidth) { columnIndex = widths.length - 1; } else { snippet.forEachArray(widths, function(width, index) { // eslint-disable-line consistent-return width += CELL_BORDER_WIDTH; columnIndex = index; if (posX > width) { posX -= width; } else { return false; } }); } return Math.max(0, columnIndex - adjustableIndex); }, /** * Restore a column to the default width. * @param {Number} index - target column index */ restoreColumnWidth: function(index) { var orgWidth = this.get('originalWidths')[index]; this.setColumnWidth(index, orgWidth); } }); module.exports = CoordColumn;