UNPKG

tui-grid

Version:

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

313 lines (270 loc) 11 kB
/** * @fileoverview RowList View * @author NHN. FE Development Lab <dl_javascript@nhn.com> */ 'use strict'; var _ = require('underscore'); var snippet = require('tui-code-snippet'); var View = require('../base/view'); var constMap = require('../common/constMap'); var classNameConst = require('../common/classNameConst'); var attrNameConst = constMap.attrName; var frameConst = constMap.frame; var CELL_BORDER_WIDTH = constMap.dimension.CELL_BORDER_WIDTH; /** * RowList View * @module view/rowList * @extends module:baes/view * @param {object} options - Options * @param {string} [options.whichSide='R'] 어느 영역에 속하는 rowList 인지 여부. 'L|R' 중 하나를 지정한다. * @ignore */ var RowList = View.extend(/** @lends module:view/rowList.prototype */{ initialize: function(options) { var focusModel = options.focusModel; var renderModel = options.renderModel; var selectionModel = options.selectionModel; var coordRowModel = options.coordRowModel; var dataModel = options.dataModel; var whichSide = options.whichSide || 'R'; _.assign(this, { whichSide: whichSide, bodyTableView: options.bodyTableView, focusModel: focusModel, renderModel: renderModel, selectionModel: selectionModel, coordRowModel: coordRowModel, dataModel: dataModel, columnModel: options.columnModel, collection: renderModel.getCollection(whichSide), painterManager: options.painterManager, sortOptions: null, renderedRowKeys: null }); this.listenTo(this.collection, 'change', this._onModelChange) .listenTo(this.collection, 'restore', this._onModelRestore) .listenTo(focusModel, 'change:rowKey', this._refreshFocusedRow) .listenTo(renderModel, 'rowListChanged', this.render); if (this.whichSide === frameConst.L) { this.listenTo(focusModel, 'change:rowKey', this._refreshSelectedMetaColumns) .listenTo(selectionModel, 'change:range', this._refreshSelectedMetaColumns) .listenTo(renderModel, 'rowListChanged', this._refreshSelectedMetaColumns); } }, /** * Returns the list of column models in it's own side * @returns {Array} - Column model list */ _getColumns: function() { return this.columnModel.getVisibleColumns(this.whichSide, true); }, /** * 기존에 생성되어 있던 TR요소들 중 새로 렌더링할 데이터와 중복되지 않은 목록의 TR요소만 삭제한다. * @param {array} dupRowKeys 중복된 데이터의 rowKey 목록 */ _removeOldRows: function(dupRowKeys) { var firstIdx = _.indexOf(this.renderedRowKeys, dupRowKeys[0]); var lastIdx = _.indexOf(this.renderedRowKeys, _.last(dupRowKeys)); var $rows = this.$el.children('tr'); $rows.slice(0, firstIdx).remove(); $rows.slice(lastIdx + 1).remove(); }, /** * 기존의 렌더링된 데이터와 중복되지 않은 목록에 대해서만 TR요소를 추가한다. * @param {array} rowKeys 렌더링할 데이터의 rowKey 목록 * @param {array} dupRowKeys 중복된 데이터의 rowKey 목록 */ _appendNewRows: function(rowKeys, dupRowKeys) { var beforeRows = this.collection.slice(0, _.indexOf(rowKeys, dupRowKeys[0])); var afterRows = this.collection.slice(_.indexOf(rowKeys, _.last(dupRowKeys)) + 1); this.$el.prepend(this._getRowsHtml(beforeRows)); this.$el.append(this._getRowsHtml(afterRows)); }, /** * Redraw all rows. * @private */ _resetRows: function() { var html = this._getRowsHtml(this.collection.models); var $tbody; if (RowList.isInnerHtmlOfTbodyReadOnly) { $tbody = this.bodyTableView.redrawTable(html); this.setElement($tbody, false); // table이 다시 생성되었기 때문에 tbody의 참조를 갱신해준다. } else { // As using a compatibility mode in IE makes it hard to detect the actual version of the browser, // use try/catch block to make in correct. try { this.$el[0].innerHTML = html; } catch (e) { RowList.isInnerHtmlOfTbodyReadOnly = true; this._resetRows(); } } }, /** * 행데이터 목록을 받아, HTML 문자열을 생성해서 반환한다. * @param {Model.Row[]} rows - 행데이터 목록 * @returns {string} 생성된 HTML 문자열 */ _getRowsHtml: function(rows) { var rowPainter = this.painterManager.getRowPainter(); var columnNames = _.pluck(this._getColumns(), 'name'); var hasTreeColumn = this.columnModel.hasTreeColumn(); return _.map(rows, function(row) { return rowPainter.generateHtml(row, columnNames, hasTreeColumn); }).join(''); }, /** * Returns a TR element of given rowKey * @param {(string|number)} rowKey - rowKey * @returns {jquery} * @private */ _getRowElement: function(rowKey) { return this.$el.find('tr[' + attrNameConst.ROW_KEY + '=' + rowKey + ']'); }, /** * Refreshes 'selected' class of meta columns. * @private */ _refreshSelectedMetaColumns: function() { var $rows = this.$el.find('tr'); var metaSelector = '.' + classNameConst.CELL_ROW_HEAD; var filteredRowList; if (this.selectionModel.hasSelection()) { filteredRowList = this._filterRowsByIndexRange($rows, this.selectionModel.get('range').row); } else { filteredRowList = this._filterRowByKey($rows, this.focusModel.get('rowKey')); } $rows.find(metaSelector).removeClass(classNameConst.CELL_SELECTED); _.each(filteredRowList, function($row) { $row.find(metaSelector).addClass(classNameConst.CELL_SELECTED); }); }, /** * Filters the rows by given range(index) and returns them. * @param {jQuery} $rows - rows (tr elements) * @param {Array.<Number>} rowRange - [startIndex, endIndex] * @returns {Array.<jQuery>} * @private */ _filterRowsByIndexRange: function($rows, rowRange) { var index = rowRange[0]; var len = rowRange[1] + 1; var rowList = []; var rowKey; for (; index < len; index += 1) { if (this.coordRowModel.getHeightAt(index)) { rowKey = this.dataModel.at(index).get('rowKey'); rowList.push(this._getRowElement(rowKey)); } } return rowList; }, /** * Filters the row by given rowKey * @param {jQuery} $rows - rows (tr elements) * @param {Number} rowKey - rowKey * @returns {Array.<jQuery>} * @private */ _filterRowByKey: function($rows, rowKey) { var rowIndex = this.dataModel.indexOfRowKey(rowKey); var renderStartIndex = this.renderModel.get('startIndex'); var rowList = []; if (renderStartIndex > rowIndex) { return rowList; } return rowList.push($rows.eq(rowIndex - renderStartIndex)); }, /** * Removes the CURRENT_ROW class from the cells in the previously focused row and * adds it to the cells in the currently focused row. * @private */ _refreshFocusedRow: function() { var rowKey = this.focusModel.get('rowKey'); var prevRowKey = this.focusModel.get('prevRowKey'); this._setFocusedRowClass(prevRowKey, false); this._setFocusedRowClass(rowKey, true); }, /** * Finds all cells in the row indentified by given rowKey and toggles the CURRENT_ROW on them. * @param {Number|String} rowKey - rowKey * @param {Boolean} focused - if set to true, the class will be added, otherwise be removed. * @private */ _setFocusedRowClass: function(rowKey, focused) { var columnNames = _.pluck(this._getColumns(), 'name'); var trMap = {}; _.each(columnNames, function(columnName) { var mainRowKey = this.dataModel.getMainRowKey(rowKey, columnName), $td; if (!trMap[mainRowKey]) { trMap[mainRowKey] = this._getRowElement(mainRowKey); } $td = trMap[mainRowKey].find('td[' + attrNameConst.COLUMN_NAME + '="' + columnName + '"]'); $td.toggleClass(classNameConst.CELL_CURRENT_ROW, focused); }, this); }, /** * Renders. * @param {boolean} dataListChanged - 데이터 목록이 변경된 경우(add, remove..) true, 아니면(스크롤 변경 등) false * @returns {View.RowList} this 객체 */ render: function(dataListChanged) { var rowKeys = this.collection.pluck('rowKey'); var dupRowKeys; this.bodyTableView.resetTablePosition(); if (dataListChanged) { this._resetRows(); } else { dupRowKeys = _.intersection(rowKeys, this.renderedRowKeys); if (_.isEmpty(rowKeys) || _.isEmpty(dupRowKeys) || // 중복된 데이터가 70% 미만일 경우에는 성능을 위해 innerHTML을 사용. (dupRowKeys.length / rowKeys.length < 0.7)) { // eslint-disable-line no-magic-numbers this._resetRows(); } else { this._removeOldRows(dupRowKeys); this._appendNewRows(rowKeys, dupRowKeys); } } this.renderedRowKeys = rowKeys; return this; }, /** * modelChange 이벤트 발생시 실행되는 핸들러 함수. * @param {Model.Row} model Row 모델 객체 * @private */ _onModelChange: function(model) { var rowKey = model.get('rowKey'); var $tr = this._getRowElement(rowKey); if ('height' in model.changed) { $tr.css('height', model.get('height') + CELL_BORDER_WIDTH); } else { this.painterManager.getRowPainter().refresh(model.changed, $tr); this.coordRowModel.syncWithDom(); } }, /** * Event handler for 'restore' event on module:model/row * @param {Object} cellData - CellData * @private */ _onModelRestore: function(cellData) { var $td = this.dataModel.getElement(cellData.rowKey, cellData.columnName); var editType = this.columnModel.getEditType(cellData.columnName); this.painterManager.getCellPainter(editType).refresh(cellData, $td); this.coordRowModel.syncWithDom(); } }, { /** * Whether the innerHTML property of a tbody element is readonly. * @memberof RowList * @static */ isInnerHtmlOfTbodyReadOnly: (snippet.browser.msie && snippet.browser.version <= 9) // eslint-disable-line no-magic-numbers }); module.exports = RowList;