UNPKG

tui-grid

Version:

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

1,015 lines (883 loc) 32.5 kB
/** * @fileoverview Selection Model class * @author NHN. FE Development Lab <dl_javascript@nhn.com> */ 'use strict'; var $ = require('jquery'); var _ = require('underscore'); var Model = require('../base/model'); var GridEvent = require('../event/gridEvent'); var util = require('../common/util'); var typeConst = require('../common/constMap').selectionType; /** * Selection Model class * @module model/selection * @extends module:base/view * @param {Object} attr - Attributes * @param {Object} options - Options * @ignore */ var Selection = Model.extend(/** @lends module:model/selection.prototype */{ initialize: function(attr, options) { var domEventBus; Model.prototype.initialize.apply(this, arguments); _.assign(this, { dataModel: options.dataModel, columnModel: options.columnModel, dimensionModel: options.dimensionModel, focusModel: options.focusModel, renderModel: options.renderModel, coordRowModel: options.coordRowModel, coordConverterModel: options.coordConverterModel, domEventBus: options.domEventBus, inputRange: null, minimumColumnRange: null, intervalIdForAutoScroll: null, scrollPixelScale: 40, enabled: true, selectionType: typeConst.CELL }); this.listenTo(this.dataModel, 'add remove sort reset', this.end); this.listenTo(this.dataModel, 'paste', this._onPasteData); if (this.isEnabled() && options.domEventBus) { domEventBus = options.domEventBus; this.listenTo(domEventBus, 'dragstart:header', this._onDragStartHeader); this.listenTo(domEventBus, 'dragmove:header', this._onDragMoveHeader); this.listenTo(domEventBus, 'dragmove:body', this._onDragMoveBody); this.listenTo(domEventBus, 'dragend:body', this._onDragEndBody); this.listenTo(domEventBus, 'mousedown:body', this._onMouseDownBody); this.listenTo(domEventBus, 'key:move key:edit', this._onKeyMoveOrEdit); this.listenTo(domEventBus, 'key:select', this._onKeySelect); this.listenTo(domEventBus, 'key:delete', this._onKeyDelete); } this.on('change:range', this._triggerSelectionEvent); }, defaults: { /** * Selection range * ex) {row: [0, 1], column: [1, 2]} * @type {{row: array, column: array}} * @ignore */ range: null, selectionUnit: 'cell' }, /** * Event handler for 'dragstart:header' event on domEventBus * @param {module:event/gridEvent} gridEvent - GridEvent * @private */ _onDragStartHeader: function(gridEvent) { var columnModel = this.columnModel; var columnNames = columnModel.getUnitColumnNamesIfMerged(gridEvent.columnName); var columnRange; if (_.some(columnNames, util.isMetaColumn)) { gridEvent.stop(); return; } columnRange = this._getColumnRangeWithNames(columnNames); if (gridEvent.shiftKey) { this.update(0, columnRange[1], typeConst.COLUMN); this._extendColumnSelection(columnRange, gridEvent.pageX, gridEvent.pageY); } else { this.minimumColumnRange = columnRange; this.selectColumn(columnRange[0]); this.update(0, columnRange[1]); } }, /** * Event handler for 'dragmove:header' event on domEventBus * @param {module:event/gridEvent} gridEvent - GridEvent * @private */ _onDragMoveHeader: function(gridEvent) { var columnModel = this.columnModel; var columnNames, columnRange; if (gridEvent.isOnHeaderArea && !gridEvent.columnName) { return; } columnNames = columnModel.getUnitColumnNamesIfMerged(gridEvent.columnName); if (columnNames.length) { columnRange = this._getColumnRangeWithNames(columnNames); } this._extendColumnSelection(columnRange, gridEvent.pageX, gridEvent.pageY); }, /** * Event handler for key:move/key:edit fevent on domEventBus * @private */ _onKeyMoveOrEdit: function() { this.end(); }, /** * Event handler for key:select event on domEventBus * @param {module:event/gridEvent} ev - GridEvent * @private */ _onKeySelect: function(ev) { // eslint-disable-line complexity var address = this._getRecentAddress(); var lastRowIndex = this.dataModel.length - 1; var lastColummnIndex = this.columnModel.getVisibleColumns().length - 1; var rowKey = this.dataModel.at(address.row).get('rowKey'); switch (ev.command) { case 'up': address.row += this.coordRowModel.getPreviousOffset(rowKey); break; case 'down': address.row += this.coordRowModel.getNextOffset(rowKey); break; case 'left': address.column -= 1; break; case 'right': address.column += 1; break; case 'pageUp': address.row = this.coordRowModel.getPageMovedIndex(address.row, false); break; case 'pageDown': address.row = this.coordRowModel.getPageMovedIndex(address.row, true); break; case 'firstColumn': address.column = 0; break; case 'lastColumn': address.column = lastColummnIndex; break; case 'firstCell': address.row = 0; address.column = 0; break; case 'lastCell': address.row = lastRowIndex; address.column = lastColummnIndex; break; case 'all': this.selectAll(); address = null; break; default: address = null; } if (address) { this.update(address.row, address.column); this._scrollTo(address.row, address.column); } }, /** * Event handler for key:delete event on domEventBus * @private */ _onKeyDelete: function() { var dataModel = this.dataModel; var focused; if (this.hasSelection()) { dataModel.delRange(this.get('range')); } else { focused = this.focusModel.which(); dataModel.del(focused.rowKey, focused.columnName); } }, /** * Return an address of recently extended cell * @returns {{row: number, column:number}} index * @private */ _getRecentAddress: function() { var focusedIndex = this.focusModel.indexOf(); var selectionRange = this.get('range'); var index = _.assign({}, focusedIndex); var selectionRowRange, selectionColumnRange; if (selectionRange) { selectionRowRange = selectionRange.row; selectionColumnRange = selectionRange.column; index.row = selectionRowRange[0]; index.column = selectionColumnRange[0]; if (selectionRowRange[1] > focusedIndex.row) { index.row = selectionRowRange[1]; } if (selectionColumnRange[1] > focusedIndex.column) { index.column = selectionColumnRange[1]; } } return index; }, /** * Returns whether the given address is valid * @param {{row: number, column: number}} address - address * @returns {boolean} * @private */ _isValidAddress: function(address) { return !!this.dataModel.at(address.row) && !!this.columnModel.at(address.colummn); }, /** * Scrolls to the position of given address * @param {number} rowIndex - row index * @param {number} columnIndex - column index * @private */ _scrollTo: function(rowIndex, columnIndex) { var row = this.dataModel.at(rowIndex); var column = this.columnModel.at(columnIndex); var rowKey, columnName, selectionType, scrollPosition; if (!row || !column) { return; } rowKey = row.get('rowKey'); columnName = column.name; scrollPosition = this.coordConverterModel.getScrollPosition(rowKey, columnName); if (scrollPosition) { selectionType = this.getType(); if (selectionType === typeConst.COLUMN) { delete scrollPosition.scrollTop; } else if (selectionType === typeConst.ROW) { delete scrollPosition.scrollLeft; } this.renderModel.set(scrollPosition); } }, /** * Examine the type of selection with given column index * @param {Number} columnIndex - columnIndex * @returns {String} * @private */ _getTypeByColumnIndex: function(columnIndex) { var visibleColumns = this.columnModel.getVisibleColumns(null, true); var columnName = visibleColumns[columnIndex].name; switch (columnName) { case '_button': return null; case '_number': return typeConst.ROW; default: return typeConst.CELL; } }, /** * Event handler for 'mousedown:body' event on domEventBus * @param {module:event/gridEvent} gridEvent - GridEvent * @private */ _onMouseDownBody: function(gridEvent) { var address = this.coordConverterModel.getIndexFromMousePosition(gridEvent.pageX, gridEvent.pageY, true); var selType = this._getTypeByColumnIndex(address.column); var rowIndex, columnIndex; if (!selType) { return; } rowIndex = address.row; columnIndex = address.column - this.columnModel.getVisibleMetaColumnCount(); if (gridEvent.shiftKey) { this.update(rowIndex, Math.max(columnIndex, 0)); } else if (selType === typeConst.ROW) { this.selectRow(rowIndex); } else { this.focusModel.focusAt(rowIndex, columnIndex); this.end(); } }, /** * Event handler for 'dragmove:body' event on domEventBus * @param {module:event/gridEvent} gridEvent - GridEvent * @private */ _onDragMoveBody: function(gridEvent) { var address = this.coordConverterModel.getIndexFromMousePosition(gridEvent.pageX, gridEvent.pageY); this.update(address.row, address.column); this._setScrolling(gridEvent.pageX, gridEvent.pageY); }, /** * Event handler for 'dragend:body' event on domEventBus * @private */ _onDragEndBody: function() { this.stopAutoScroll(); }, /** * Event handler for 'paste' event on DataModel * @param {Object} range - Range */ _onPasteData: function(range) { this.start(range.startIdx.row, range.startIdx.column); this.update(range.endIdx.row, range.endIdx.column); }, /** * Returns the range of column index of given column names * @param {Array.<string>} columnNames - column names * @returns {Array.<number>} * @private */ _getColumnRangeWithNames: function(columnNames) { var columnModel = this.columnModel; var columnIndexes = _.map(columnNames, function(name) { return columnModel.indexOfColumnName(name, true); }); var minMax = util.getMinMax(columnIndexes); return [minMax.min, minMax.max]; }, /** * Set selection type * @param {string} type - Selection type (CELL, ROW, COLUMN) */ setType: function(type) { this.selectionType = typeConst[type] || this.selectionType; }, /** * Returns the selection type (using internal state) * @returns {string} type - Selection type (CELL, ROW, COLUMN) */ getType: function() { return this.selectionType; }, /** * Returns the selection unit (by options) * @returns {string} unit - Selection unit (CELL, ROW) */ getSelectionUnit: function() { return this.get('selectionUnit').toUpperCase(); }, /** * Enables the selection. */ enable: function() { this.enabled = true; }, /** * Disables the selection. */ disable: function() { this.end(); this.enabled = false; }, /** * Returns whether the selection is enabled. * @returns {boolean} True if the selection is enabled. */ isEnabled: function() { return this.enabled; }, /** * Starts the selection. * @param {Number} rowIndex - Row index * @param {Number} columnIndex - Column index * @param {string} type - Selection type */ start: function(rowIndex, columnIndex, type) { if (!this.isEnabled()) { return; } this.setType(type); this.inputRange = { row: [rowIndex, rowIndex], column: [columnIndex, columnIndex] }; this._resetRangeAttribute(); }, /** * Updates the selection range. * @param {number} rowIndex - Row index * @param {number} columnIndex - Column index * @param {string} [type] - Selection type */ update: function(rowIndex, columnIndex, type) { // eslint-disable-line complexity var focusedIndex; if (!this.enabled || (type !== typeConst.COLUMN && rowIndex < 0) || (type !== typeConst.ROW && columnIndex < 0)) { return; } if (!this.hasSelection()) { focusedIndex = this.focusModel.indexOf(); if (this.getSelectionUnit() === typeConst.ROW) { this.start(focusedIndex.row, 0, typeConst.ROW); } else { this.start(focusedIndex.row, focusedIndex.column, typeConst.CELL); } } else { this.setType(type); } this._updateInputRange(rowIndex, columnIndex); this._resetRangeAttribute(); }, /** * Update input range (end range, not start range) * @param {number} rowIndex - Row index * @param {number} columnIndex - Column index * @private */ _updateInputRange: function(rowIndex, columnIndex) { var inputRange = this.inputRange; if (this.selectionType === typeConst.ROW) { columnIndex = this.columnModel.getVisibleColumns().length - 1; } else if (this.selectionType === typeConst.COLUMN) { rowIndex = this.dataModel.length - 1; } inputRange.row[1] = rowIndex; inputRange.column[1] = columnIndex; }, /** * Extend column selection * @param {undefined|Array} columnIndexes - Column indexes * @param {number} pageX - Mouse position X * @param {number} pageY - Mouse positino Y * @private */ _extendColumnSelection: function(columnIndexes, pageX, pageY) { var minimumColumnRange = this.minimumColumnRange; var index = this.coordConverterModel.getIndexFromMousePosition(pageX, pageY); var range = { row: [0, this.dataModel.length - 1], column: [] }; var minMax; if (!columnIndexes || !columnIndexes.length) { columnIndexes = [index.column]; } this._setScrolling(pageX, pageY); if (minimumColumnRange) { minMax = util.getMinMax(columnIndexes.concat(minimumColumnRange)); } else { columnIndexes.push(this.inputRange.column[0]); minMax = util.getMinMax(columnIndexes); } range.column.push(minMax.min, minMax.max); this._resetRangeAttribute(range); }, /** * Set auto scrolling for selection * @param {number} pageX - Mouse position X * @param {number} pageY - Mouse positino Y * @private */ _setScrolling: function(pageX, pageY) { var overflow = this.dimensionModel.getOverflowFromMousePosition(pageX, pageY); this.stopAutoScroll(); if (this._isAutoScrollable(overflow.x, overflow.y)) { this.intervalIdForAutoScroll = setInterval( _.bind(this._adjustScroll, this, overflow.x, overflow.y) ); } }, /** * selection 영역 선택을 종료하고 selection 데이터를 초기화한다. */ end: function() { this.inputRange = null; this.unset('range'); this.minimumColumnRange = null; }, /** * Stops the auto-scroll interval. */ stopAutoScroll: function() { if (!_.isNull(this.intervalIdForAutoScroll)) { clearInterval(this.intervalIdForAutoScroll); this.intervalIdForAutoScroll = null; } }, /** * Select all data in a row * @param {Number} rowIndex - Row idnex */ selectRow: function(rowIndex) { if (this.isEnabled()) { this.focusModel.focusAt(rowIndex, 0); this.start(rowIndex, 0, typeConst.ROW); this.update(rowIndex, this.columnModel.getVisibleColumns().length - 1); } }, /** * Select all data in a column * @param {Number} columnIdx - Column index */ selectColumn: function(columnIdx) { if (this.isEnabled()) { this.focusModel.focusAt(0, columnIdx); this.start(0, columnIdx, typeConst.COLUMN); this.update(this.dataModel.length - 1, columnIdx); } }, /** * Selects all data range. */ selectAll: function() { if (this.isEnabled()) { this.start(0, 0, typeConst.CELL); this.update(this.dataModel.length - 1, this.columnModel.getVisibleColumns().length - 1); } }, /** * Returns the row and column indexes of the starting position. * @returns {{row: number, column: number}} Objects containing indexes */ getStartIndex: function() { var range = this.get('range'); return { row: range.row[0], column: range.column[0] }; }, /** * Returns the row and column indexes of the ending position. * @returns {{row: number, column: number}} Objects containing indexes */ getEndIndex: function() { var range = this.get('range'); return { row: range.row[1], column: range.column[1] }; }, /** * selection 데이터가 존재하는지 확인한다. * @returns {boolean} selection 데이터 존재여부 */ hasSelection: function() { return !!this.get('range'); }, /** * Returns whether given range is a single cell. (include merged cell) * @param {Array.<String>} columnNames - columnNames * @param {Array.<Object>} rowList - rowList * @returns {Boolean} */ _isSingleCell: function(columnNames, rowList) { var isSingleColumn = columnNames.length === 1; var isSingleRow = rowList.length === 1; var isSingleMergedCell = isSingleColumn && !isSingleRow && (rowList[0].getRowSpanData(columnNames[0]).count === rowList.length); return (isSingleColumn && isSingleRow) || isSingleMergedCell; }, /** * Returns the string value of all cells in the selection range as a single string. * @returns {String} */ getValuesToString: function() { var self = this; var rowList = this._getRangeRowList(); var columnNames = this._getRangeColumnNames(); var rowValues = _.map(rowList, function(row) { return _.map(columnNames, function(columnName) { return self.getValueToString(row.get('rowKey'), columnName); }).join('\t'); }); if (this._isSingleCell(columnNames, rowList)) { return rowValues[0]; } return rowValues.join('\n'); }, /** * Returns the string value of a single cell by copy options. * @param {Nubmer} rowKey - Row key * @param {Number} columnName - Column name * @returns {String} */ getValueToString: function(rowKey, columnName) { var columnModel = this.columnModel; var cellData = this.renderModel.getCellData(rowKey, columnName); var copyOptions = columnModel.getCopyOptions(columnName); var column = columnModel.getColumnModel(columnName); var row = this.dataModel.get(rowKey); var value = row.getValueString(columnName); var text; if (copyOptions.customValue) { text = this._getCustomValue( copyOptions.customValue, value, row.toJSON(), column ); } else if (copyOptions.useListItemText) { text = value; } else if (copyOptions.useFormattedValue) { text = cellData.formattedValue; } else { text = value; } return text; }, /** * If the column has a 'copyOptions.customValue' function, exeucute it and returns the result. * @param {String} customValue - value to display * @param {String} value - value to display * @param {Object} rowAttrs - All attributes of the row * @param {Object} column - Column info * @returns {String} * @private */ _getCustomValue: function(customValue, value, rowAttrs, column) { var result; if (_.isFunction(customValue)) { result = customValue(value, rowAttrs, column); } else { result = customValue; } return result; }, /** * Returns an array of selected row list * @returns {Array.<module:model/data/row>} * @private */ _getRangeRowList: function() { var rowRange = this.get('range').row; var index = rowRange[0]; var len = rowRange[1] + 1; var rowList = []; for (; index < len; index += 1) { if (this.coordRowModel.getHeightAt(index)) { rowList.push(this.dataModel.at(index)); } } return rowList; }, /** * Returns an array of selected column names * @returns {Array.<string>} * @private */ _getRangeColumnNames: function() { var columnRange = this.get('range').column; var columns = this.columnModel.getVisibleColumns().slice(columnRange[0], columnRange[1] + 1); return _.pluck(columns, 'name'); }, /** * 마우스 드래그로 selection 선택 시 auto scroll 조건에 해당하는지 반환한다. * @param {Number} overflowX 가로축 기준 영역 overflow 값 * @param {Number} overflowY 세로축 기준 영역 overflow 값 * @returns {boolean} overflow 되었는지 여부 * @private */ _isAutoScrollable: function(overflowX, overflowY) { return !(overflowX === 0 && overflowY === 0); }, /** * Adjusts scrollTop and scrollLeft value. * @param {Number} overflowX 가로축 기준 영역 overflow 값 * @param {Number} overflowY 세로축 기준 영역 overflow 값 * @private */ _adjustScroll: function(overflowX, overflowY) { var renderModel = this.renderModel; if (overflowX) { this._adjustScrollLeft(overflowX, renderModel.get('scrollLeft'), renderModel.get('maxScrollLeft')); } if (overflowY) { this._adjustScrollTop(overflowY, renderModel.get('scrollTop'), renderModel.get('maxScrollTop')); } }, /** * Adjusts scrollLeft value. * @param {number} overflowX - 1 | 0 | -1 * @param {number} scrollLeft - Current scrollLeft value * @param {number} maxScrollLeft - Max scrollLeft value * @private */ _adjustScrollLeft: function(overflowX, scrollLeft, maxScrollLeft) { var adjusted = scrollLeft; var pixelScale = this.scrollPixelScale; if (overflowX < 0) { adjusted = Math.max(0, scrollLeft - pixelScale); } else if (overflowX > 0) { adjusted = Math.min(maxScrollLeft, scrollLeft + pixelScale); } this.renderModel.set('scrollLeft', adjusted); }, /** * Adjusts scrollTop value. * @param {number} overflowY - 1 | 0 | -1 * @param {number} scrollTop - Current scrollTop value * @param {number} maxScrollTop - Max scrollTop value * @private */ _adjustScrollTop: function(overflowY, scrollTop, maxScrollTop) { var adjusted = scrollTop; var pixelScale = this.scrollPixelScale; if (overflowY < 0) { adjusted = Math.max(0, scrollTop - pixelScale); } else if (overflowY > 0) { adjusted = Math.min(maxScrollTop, scrollTop + pixelScale); } this.renderModel.set('scrollTop', adjusted); }, /** * Expands the 'this.inputRange' if rowspan data exists, and resets the 'range' attributes to the value. * @param {{column: number[], row: number[]}} [inputRange] - Input range. Default is this.inputRange * @private */ _resetRangeAttribute: function(inputRange) { // eslint-disable-line complexity var dataModel = this.dataModel; var hasSpannedRange, spannedRange, tmpRowRange; inputRange = inputRange || this.inputRange; if (!inputRange) { this.set('range', null); return; } spannedRange = { row: _.sortBy(inputRange.row), column: _.sortBy(inputRange.column) }; if (dataModel.isRowSpanEnable() && this.selectionType === typeConst.CELL) { do { tmpRowRange = _.assign([], spannedRange.row); spannedRange = this._getRowSpannedIndex(spannedRange); hasSpannedRange = ( spannedRange.row[0] !== tmpRowRange[0] || spannedRange.row[1] !== tmpRowRange[1] ); } while (hasSpannedRange); this._setRangeMinMax(spannedRange.row, spannedRange.column); } this.set('range', spannedRange); }, /** * Trigger 'selection' event * @private */ _triggerSelectionEvent: function() { var range = this.get('range'); var dataModel = this.dataModel; var columnModel = this.columnModel; var rowRange, columnRange, gridEvent; var startRow, endRow, startColumn, endColumn; if (!range) { return; } rowRange = range.row; columnRange = range.column; startRow = dataModel.getRowDataAt(rowRange[0]); startColumn = columnModel.at(columnRange[0]); endRow = dataModel.getRowDataAt(rowRange[1]); endColumn = columnModel.at(columnRange[1]); if (!startRow || !endRow || !startColumn || !endColumn) { return; } gridEvent = new GridEvent(null, { range: { start: [startRow.rowKey, startColumn.name], end: [endRow.rowKey, endColumn.name] } }); /** * Occurs when selecting cells * @event Grid#selection * @type {module:event/gridEvent} * @property {Object} range - Range of selection * @property {Array} range.start - Info of start cell (ex: [rowKey, columName]) * @property {Array} range.end - Info of end cell (ex: [rowKey, columnName]) * @property {Grid} instance - Current grid instance */ this.trigger('selection', gridEvent); }, /** * Set min, max value of range(row, column) * @param {Array} rowRange - Row range * @param {Array} columnRange - Column range * @private */ _setRangeMinMax: function(rowRange, columnRange) { if (rowRange) { rowRange[0] = Math.max(0, rowRange[0]); rowRange[1] = Math.min(this.dataModel.length - 1, rowRange[1]); } if (columnRange) { columnRange[0] = Math.max(0, columnRange[0]); columnRange[1] = Math.min(this.columnModel.getVisibleColumns().length - 1, columnRange[1]); } }, /** * row start index 기준으로 rowspan 을 확인하며 startRangeList 업데이트 하는 함수 * @param {object} param - parameters * @private */ _concatRowSpanIndexFromStart: function(param) { var startIndex = param.startIndex; var endIndex = param.endIndex; var columnName = param.columnName; var rowSpanData = param.startRowSpanDataMap && param.startRowSpanDataMap[columnName]; var startIndexList = param.startIndexList; var endIndexList = param.endIndexList; var spannedIndex; if (!rowSpanData) { return; } if (!rowSpanData.isMainRow) { spannedIndex = startIndex + rowSpanData.count; startIndexList.push(spannedIndex); } else { spannedIndex = startIndex + rowSpanData.count - 1; if (spannedIndex > endIndex) { endIndexList.push(spannedIndex); } } }, /** * row end index 기준으로 rowspan 을 확인하며 endRangeList 를 업데이트 하는 함수 * @param {object} param - parameters * @private */ _concatRowSpanIndexFromEnd: function(param) { var endIndex = param.endIndex; var columnName = param.columnName; var rowSpanData = param.endRowSpanDataMap && param.endRowSpanDataMap[columnName]; var endIndexList = param.endIndexList; var dataModel = param.dataModel; var spannedIndex, tmpRowSpanData; if (!rowSpanData) { return; } if (!rowSpanData.isMainRow) { spannedIndex = endIndex + rowSpanData.count; tmpRowSpanData = dataModel.at(spannedIndex).getRowSpanData(columnName); spannedIndex += tmpRowSpanData.count - 1; if (spannedIndex > endIndex) { endIndexList.push(spannedIndex); } } else { spannedIndex = endIndex + rowSpanData.count - 1; endIndexList.push(spannedIndex); } }, /** * rowSpan 된 Index range 를 반환한다. * @param {{row: Array, column: Array}} spannedRange 인덱스 정보 * @returns {{row: Array, column: Array}} New Range * @private */ _getRowSpannedIndex: function(spannedRange) { var columns = this.columnModel.getVisibleColumns() .slice(spannedRange.column[0], spannedRange.column[1] + 1); var dataModel = this.dataModel; var startIndexList = [spannedRange.row[0]]; var endIndexList = [spannedRange.row[1]]; var startRow = dataModel.at(spannedRange.row[0]); var endRow = dataModel.at(spannedRange.row[1]); var newSpannedRange = $.extend({}, spannedRange); var startRowSpanDataMap, endRowSpanDataMap, param; if (!startRow || !endRow) { return newSpannedRange; } startRowSpanDataMap = dataModel.at(spannedRange.row[0]).getRowSpanData(); endRowSpanDataMap = dataModel.at(spannedRange.row[1]).getRowSpanData(); // 모든 열을 순회하며 각 열마다 설정된 rowSpan 정보에 따라 인덱스를 업데이트 한다. _.each(columns, function(columnModel) { param = { columnName: columnModel.name, startIndex: spannedRange.row[0], endIndex: spannedRange.row[1], endRowSpanDataMap: endRowSpanDataMap, startRowSpanDataMap: startRowSpanDataMap, startIndexList: startIndexList, endIndexList: endIndexList, dataModel: dataModel }; this._concatRowSpanIndexFromStart(param); this._concatRowSpanIndexFromEnd(param); }, this); newSpannedRange.row = [Math.min.apply(null, startIndexList), Math.max.apply(null, endIndexList)]; return newSpannedRange; } }); module.exports = Selection;