tui-grid
Version:
TOAST UI Grid : Powerful data grid control supported by TOAST UI
467 lines (402 loc) • 15.3 kB
JavaScript
/**
* @fileoverview Row Model for Rendering (View Model)
* @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');
/**
* Row Model
* @module model/row
* @param {object} attributes - Attributes
* @param {object} options - Options
* @extends module:base/model
* @ignore
*/
var Row = Model.extend(/** @lends module:model/row.prototype */{
initialize: function(attributes, options) {
var rowKey = attributes && attributes.rowKey;
var dataModel = options.dataModel;
var rowData = dataModel.get(rowKey);
this.dataModel = dataModel;
this.columnModel = options.columnModel;
this.focusModel = options.focusModel;
if (rowData) {
this.listenTo(rowData, 'change', this._onDataModelChange);
this.listenTo(rowData, 'restore', this._onDataModelRestore);
this.listenTo(rowData, 'extraDataChanged', this._setRowExtraData);
this.listenTo(dataModel, 'disabledChanged', this._onDataModelDisabledChanged);
this.rowData = rowData;
}
},
idAttribute: 'rowKey',
/**
* Event handler for 'change' event on module:data/row
* @param {Object} rowData - RowData model on which event occurred
* @private
*/
_onDataModelChange: function(rowData) {
_.each(rowData.changed, function(value, columnName) {
var column, isTextType;
if (this.has(columnName)) {
column = this.columnModel.getColumnModel(columnName);
isTextType = this.columnModel.isTextType(columnName);
this.setCell(columnName, this._getValueAttrs(value, rowData, column, isTextType));
}
}, this);
},
/**
* Event handler for 'restore' event on module:data/row
* @param {String} columnName - columnName
* @private
*/
_onDataModelRestore: function(columnName) {
var cellData = this.get(columnName);
if (cellData) {
this.trigger('restore', cellData);
}
},
/**
* Returns an array of visible column names.
* @returns {Array.<String>} Visible column names
* @private
*/
_getColumnNameList: function() {
var columnModels = this.columnModel.getVisibleColumns(null, true);
return _.pluck(columnModels, 'name');
},
/**
* Event handler for 'disabledChanged' event on dataModel
*/
_onDataModelDisabledChanged: function() {
var columnNames = this._getColumnNameList();
_.each(columnNames, function(columnName) {
this.setCell(columnName, {
disabled: this.rowData.isDisabled(columnName),
className: this._getClassNameString(columnName)
});
}, this);
},
/**
* Sets the 'disabled', 'editable', 'className' property of each cell data.
* @private
*/
_setRowExtraData: function() {
_.each(this._getColumnNameList(), function(columnName) {
var cellData = this.get(columnName);
if (!snippet.isUndefined(cellData) && cellData.isMainRow) {
if (cellData.tree) {
this._setTreeCell(columnName);
} else {
this._setCell(columnName);
}
}
}, this);
},
/**
* Set normal cell's properties
* @param {string} columnName - Column name
* @private
*/
_setCell: function(columnName) {
var cellState = this.rowData.getCellState(columnName);
this.setCell(columnName, {
disabled: cellState.disabled,
editable: cellState.editable,
className: this._getClassNameString(columnName)
});
},
/**
* Set tree-cell's property
* @param {string} columnName - Column name
* @private
*/
_setTreeCell: function(columnName) {
this.setCell(columnName, {
isExpanded: this.rowData.getTreeExpanded()
});
},
/**
* Overrides Backbone.Model.parse
* (this method is called before initialize method)
* @param {Array} data - Original data
* @param {Object} options - Options
* @returns {Array} - Converted data.
* @override
*/
parse: function(data, options) {
return this._formatData(data, options.dataModel, options.columnModel, options.focusModel);
},
/**
* Convert the original data to the rendering data.
* @param {Array} data - Original data
* @param {module:model/data/rowList} dataModel - Data model
* @param {module:model/data/columnModel} columnModel - Column model
* @param {module:model/data/focusModel} focusModel - focus model
* @param {Number} rowHeight - The height of a row
* @returns {Array} - Converted data
* @private
*/
_formatData: function(data, dataModel, columnModel, focusModel) {
var rowKey = data.rowKey;
var rowHeight = data.height;
var columnData, row;
if (_.isUndefined(rowKey)) {
return data;
}
row = dataModel.get(rowKey);
columnData = _.omit(data, 'rowKey', '_extraData', 'height', 'rowNum');
_.each(columnData, function(value, columnName) {
var rowSpanData = this._getRowSpanData(columnName, data, dataModel.isRowSpanEnable());
var cellState = row.getCellState(columnName);
var isTextType = columnModel.isTextType(columnName);
var column = columnModel.getColumnModel(columnName);
data[columnName] = {
rowKey: rowKey,
height: rowHeight,
columnName: columnName,
rowSpan: rowSpanData.count,
isMainRow: rowSpanData.isMainRow,
mainRowKey: rowSpanData.mainRowKey,
editable: cellState.editable,
disabled: cellState.disabled,
editing: focusModel.isEditingCell(rowKey, columnName),
whiteSpace: column.whiteSpace || 'nowrap',
valign: column.valign,
listItems: snippet.pick(column, 'editOptions', 'listItems'),
className: this._getClassNameString(columnName, row, focusModel),
columnModel: column,
changed: [] // changed property names
};
_.assign(data[columnName], this._getValueAttrs(value, row, column, isTextType));
_.assign(data[columnName], this._getTreeAttrs(value, row, column, columnModel));
}, this);
return data;
},
/**
* Returns the class name string of the a cell.
* @param {String} columnName - column name
* @param {module:model/data/row} [row] - data model of a row
* @param {module:model/focus} [focusModel] - focus model
* @returns {String}
*/
_getClassNameString: function(columnName, row, focusModel) {
var classNames;
if (!row) {
row = this.dataModel.get(this.get('rowKey'));
if (!row) {
return '';
}
}
if (!focusModel) {
focusModel = this.focusModel;
}
classNames = row.getClassNameList(columnName);
return classNames.join(' ');
},
/**
* Returns the tree values of the attributes related to the cell value.
* @param {String|Number} value - Value
* @param {module:model/data/row} row - Row data model
* @param {Object} column - Column model object
* @param {module:model/data/columnModel} columnModel - column model
* @returns {Object}
* @private
*/
_getTreeAttrs: function(value, row, column, columnModel) {
var attrs = {};
if (columnModel.isTreeType(column.name)) {
attrs = {
tree: columnModel.hasTreeColumn(),
depth: row.getTreeDepth(),
isExpanded: row.getTreeExpanded(),
hasChildren: row.hasTreeChildren(),
useIcon: columnModel.useTreeIcon()
};
}
return attrs;
},
/**
* Returns the values of the attributes related to the cell value.
* @param {String|Number} value - Value
* @param {module:model/data/row} row - Row data model
* @param {Object} column - Column model object
* @param {Boolean} isTextType - True if the cell is the text-type
* @returns {Object}
* @private
*/
_getValueAttrs: function(value, row, column, isTextType) {
var prefix = snippet.pick(column, 'editOptions', 'prefix');
var postfix = snippet.pick(column, 'editOptions', 'postfix');
var converter = snippet.pick(column, 'editOptions', 'converter');
var rowAttrs = row.toJSON();
return {
value: this._getValueToDisplay(value, column, isTextType),
formattedValue: this._getFormattedValue(value, rowAttrs, column),
prefix: this._getExtraContent(prefix, value, rowAttrs),
postfix: this._getExtraContent(postfix, value, rowAttrs),
convertedHTML: this._getConvertedHTML(converter, value, rowAttrs)
};
},
/**
* If the column has a 'formatter' function, exeucute it and returns the result.
* @param {String} value - value to display
* @param {Object} rowAttrs - All attributes of the row
* @param {Object} column - Column info
* @returns {String}
* @private
*/
_getFormattedValue: function(value, rowAttrs, column) {
var result;
if (_.isFunction(column.formatter)) {
result = column.formatter(value, rowAttrs, column);
} else {
result = value;
}
if (_.isNumber(result)) {
result = String(result);
} else if (!result) {
result = '';
}
return result;
},
/**
* Returns the value of the 'prefix' or 'postfix'.
* @param {(String|Function)} content - content
* @param {String} cellValue - cell value
* @param {Object} rowAttrs - All attributes of the row
* @returns {string}
* @private
*/
_getExtraContent: function(content, cellValue, rowAttrs) {
var result = '';
if (_.isFunction(content)) {
result = content(cellValue, rowAttrs);
} else if (snippet.isExisty(content)) {
result = content;
}
return result;
},
/**
* If the 'converter' function exist, execute it and returns the result.
* @param {Function} converter - converter
* @param {String} cellValue - cell value
* @param {Object} rowAttrs - All attributes of the row
* @returns {(String|Null)} - HTML string or Null
* @private
*/
_getConvertedHTML: function(converter, cellValue, rowAttrs) {
var convertedHTML = null;
if (_.isFunction(converter)) {
convertedHTML = converter(cellValue, rowAttrs);
}
if (convertedHTML === false) {
convertedHTML = null;
}
return convertedHTML;
},
/**
* Returns the value to display
* @param {String|Number} value - value
* @param {String} column - column name
* @param {Boolean} isTextType - True if the cell is the text-typee
* @returns {String}
* @private
*/
_getValueToDisplay: function(value, column, isTextType) {
var isExisty = snippet.isExisty;
var useHtmlEntity = column.useHtmlEntity;
var defaultValue = column.defaultValue;
if (!isExisty(value)) {
value = isExisty(defaultValue) ? defaultValue : '';
}
if (isTextType && useHtmlEntity && snippet.hasEncodableString(value)) {
value = snippet.encodeHTMLEntity(value);
}
return value;
},
/**
* Returns the rowspan data.
* @param {String} columnName - column name
* @param {Object} data - data
* @param {Boolean} isRowSpanEnable - Whether the rowspan enable
* @returns {Object} rowSpanData
* @private
*/
_getRowSpanData: function(columnName, data, isRowSpanEnable) {
var rowSpanData = snippet.pick(data, '_extraData', 'rowSpanData', columnName);
if (!isRowSpanEnable || !rowSpanData) {
rowSpanData = {
mainRowKey: data.rowKey,
count: 0,
isMainRow: true
};
}
return rowSpanData;
},
/**
* Updates the className attribute of the cell identified by a given column name.
* @param {String} columnName - column name
*/
updateClassName: function(columnName) {
this.setCell(columnName, {
className: this._getClassNameString(columnName)
});
},
/**
* Sets the cell data.
* (Each cell data is reference type, so do not change the cell data directly and
* use this method to trigger change event)
* @param {String} columnName - Column name
* @param {Object} param - Key-Value pair of the data to change
*/
setCell: function(columnName, param) {
var isValueChanged = false;
var changed = [];
var rowIndex, rowKey, data;
if (!this.has(columnName)) {
return;
}
rowKey = this.get('rowKey');
data = _.clone(this.get(columnName));
_.each(param, function(changeValue, name) {
if (!util.isEqual(data[name], changeValue)) {
isValueChanged = (name === 'value') ? true : isValueChanged;
data[name] = changeValue;
changed.push(name);
}
}, this);
if (changed.length) {
data.changed = changed;
this.set(columnName, data, {
silent: this._shouldSetSilently(data, isValueChanged)
});
if (isValueChanged) {
rowIndex = this.dataModel.indexOfRowKey(rowKey);
this.trigger('valueChange', rowIndex);
}
}
},
/**
* Returns whether the 'set' method should be called silently.
* @param {Object} cellData - cell data
* @param {Boolean} valueChanged - true if value changed
* @returns {Boolean}
* @private
*/
_shouldSetSilently: function(cellData, valueChanged) {
var valueChangedOnEditing = cellData.editing && valueChanged;
var useViewMode = snippet.pick(cellData, 'columnModel', 'editOptions', 'useViewMode') !== false;
var editingChangedToTrue = _.contains(cellData.changed, 'editing') && cellData.editing;
// Silent Cases
// 1: If values have been changed while the editing is true,
// prevent the related cell-view from changing its value-state until editing is finished.
// 2: If useViewMode is true and editing is changing to true,
// prevent the related cell-view from changing its state to enable editing,
// as the editing-layer will be used for editing instead.
return valueChangedOnEditing || (useViewMode && editingChangedToTrue);
}
});
module.exports = Row;