fin-hypergrid
Version:
Canvas-based high-performance grid
1,409 lines (1,242 loc) • 53.5 kB
JavaScript
'use strict';
var Point = require('rectangular').Point;
var Base = require('../Base');
var Column = require('./Column');
var cellEventFactory = require('../lib/cellEventFactory');
var featureRegistry = require('../features');
var Synonomous = require('synonomous');
var propClassEnum = require('../defaults.js').propClassEnum;
var assignOrDelete = require('../lib/misc').assignOrDelete;
var noExportProperties = [
'columnHeader',
'columnHeaderColumnSelection',
'filterProperties',
'rowHeader',
'rowHeaderRowSelection',
'rowNumbersProperties',
'treeColumnProperties',
'treeColumnPropertiesColumnSelection',
];
/**
* @constructor
* @abstract
* @desc A sort of "model++." It contains all code/data that's necessary for easily implementing a virtual data source and its manipulation/analytics.
*
*/
var Behavior = Base.extend('Behavior', {
/**
* @desc this is the callback for the plugin pattern of nested tags
* @param {Hypergrid} grid
* @param {object} [options] - _(See {@link behaviors.JSON#setData} for additional options.)_
* @param {DataModels[]} [options.subgrids]
* @memberOf Behavior#
*/
initialize: function(grid, options) {
/**
* @type {Hypergrid}
* @memberOf Behavior#
*/
this.grid = grid;
this.synonomous = new Synonomous;
this.initializeFeatureChain();
this.grid.behavior = this;
this.reset(options);
},
/**
* @desc Create the feature chain - this is the [chain of responsibility](http://c2.com/cgi/wiki?ChainOfResponsibilityPattern) pattern.
* @param {Hypergrid} [grid] Unnecesary legacy parameter. May be omitted.
* @memberOf Behavior#
*/
initializeFeatureChain: function(grid) {
var constructors;
/**
* @summary Controller chain of command.
* @desc Each feature is linked to the next feature.
* @type {Feature}
* @memberOf Behavior#
*/
this.featureChain = undefined;
/**
* @summary Hash of instantiated features by class names.
* @desc Built here but otherwise not in use.
* @type {object}
* @memberOf Behavior#
*/
this.featureMap = {};
this.featureRegistry = this.featureRegistry || featureRegistry;
if (this.grid.properties.features) {
var getFeatureConstructor = this.featureRegistry.get.bind(this.featureRegistry);
constructors = this.grid.properties.features.map(getFeatureConstructor);
} else if (this.features) {
constructors = this.features;
warnBehaviorFeaturesDeprecation.call(this);
}
constructors.forEach(function(FeatureConstructor, i) {
var feature = new FeatureConstructor;
this.featureMap[feature.$$CLASS_NAME] = feature;
if (i) {
this.featureChain.setNext(feature);
} else {
this.featureChain = feature;
}
}, this);
if (this.featureChain) {
this.featureChain.initializeOn(this.grid);
}
},
features: [], // override in implementing class; or provide feature names in grid.properties.features; else no features
/**
* @param {object} [options]
* @param {object} [options.pipeline] - Consumed by {@link dataModels.JSON#reset}.
*
* If omitted, previously established pipeline is reused.
* @param {object} [options.controllers] - Consumed by {@link dataModels.JSON#reset}.
*
* If omitted, previously established controllers list is reused.
* @memberOf Behavior#
*/
reset: function(options) {
if (this.dataModel) {
options = options || {};
this.dataModel.reset({
pipeline: options.pipeline,
controllers: options.controllers
});
} else {
/**
* @type {dataModelAPI}
* @memberOf Behavior#
*/
this.dataModel = this.getNewDataModel(options);
// recreate `CellEvent` class so it can set up its internal `grid`, `behavior`, and `dataModel` convenience properties
this.CellEvent = cellEventFactory(this.grid);
}
this.scrollPositionX = this.scrollPositionY = 0;
this.rowPropertiesPrototype = Object.create(this.grid.properties,
require('./rowProperties').rowPropertiesPrototypeDescriptors);
this.clearColumns();
this.createColumns();
/**
* Ordered list of subgrids to render.
* @type {subgridSpec[]}
* @memberOf Hypergrid#
*/
this.subgrids = options.subgrids || this.subgrids || this.grid.properties.subgrids;
},
get renderedColumnCount() {
return this.grid.renderer.visibleColumns.length;
},
get renderedRowCount() {
return this.grid.renderer.visibleRows.length;
},
get leftMostColIndex() {
return this.grid.properties.showRowNumbers ? this.rowColumnIndex : (this.hasTreeColumn() ? this.treeColumnIndex : 0);
},
clearColumns: function() {
var schema = this.dataModel.schema,
tc = this.treeColumnIndex,
rc = this.rowColumnIndex;
schema[tc] = schema[tc] || {
name: 'Tree',
header: 'Tree'
};
schema[rc] = schema[rc] || {
name: '',
header: ''
};
/**
* @type {Column[]}
* @memberOf Behavior#
*/
this.columns = [];
/**
* @type {Column[]}
* @memberOf Behavior#
*/
this.allColumns = [];
this.allColumns[tc] = this.columns[tc] = this.newColumn({
index: tc,
header: schema[tc].header
});
this.allColumns[rc] = this.columns[rc] = this.newColumn({
index: rc,
header: schema[rc].header
});
this.columns[tc].properties.propClassLayers = this.columns[rc].properties.propClassLayers = [propClassEnum.COLUMNS];
// Signal the renderer to size the now-reset handle column before next render
this.grid.renderer.resetRowHeaderColumnWidth();
},
getActiveColumn: function(x) {
return this.columns[x];
},
/**
* The "grid index" of an active column given a "data index" (number), column name (string), or column object
* @param {Column|number} columnOrIndex
* @returns {undefined|number} The grid index of the column or undefined if column not in grid.
* @memberOf Hypergrid#
*/
getActiveColumnIndex: function(columnOrIndexOrName) {
var value = columnOrIndexOrName instanceof Column ? columnOrIndexOrName.index : columnOrIndexOrName,
key = typeof index === 'number' ? 'index' : 'name';
return this.columns.findIndex(function(column) { return column[key] === value; });
},
getVisibleColumn: function() {
return this.deprecated('getVisibleColumn(x)', 'getActiveColumn(x)', '1.0.6', arguments);
},
getVisibleColumnName: function() {
return this.deprecated('getVisibleColumnName(x)', 'getActiveColumn(x).name', '1.0.6', arguments);
},
getColumnId: function() {
return this.deprecated('getColumnId(x)', 'getActiveColumn(x).header', '1.0.6', arguments);
},
getHeader: function() {
return this.deprecated('getHeader(x)', 'getActiveColumn(x).header', '1.0.6', arguments);
},
getColumn: function(x) {
return this.allColumns[x];
},
newColumn: function(options) {
return new Column(this, options);
},
addColumn: function(options) {
var column = this.newColumn(options),
columns = this.columns,
allColumns = this.allColumns;
columns.push(column);
this.synonomous.decorateList(columns.length - 1, columns);
allColumns.push(column);
this.synonomous.decorateList(allColumns.length - 1, allColumns);
return column;
},
createColumns: function() {
this.clearColumns();
//concrete implementation here
},
getColumnWidth: function(x) {
var column = this.getActiveColumn(x);
return column ? column.getWidth() : 0;
},
/**
* @param {Column|number} columnOrIndex - The column or active column index.
* @param width
* @memberOf Hypergrid#
*/
setColumnWidth: function(columnOrIndex, width) {
var column = columnOrIndex >= -2 ? this.getActiveColumn(columnOrIndex) : columnOrIndex;
column.setWidth(width);
this.stateChanged();
},
getCellProvider: function(name) {
return this.deprecated('getCellProvider()', 'grid.cellRenderers', '1.0.6', arguments);
},
createCellProvider: function(name) {
console.error('getCellProvider() is deprecated as of v1.0.6. No replacement; do not call. Previously called by `Behavior` constructor; `new CellRenderers()` is now called by `Hypergrid` constructor instead.', arguments);
},
/**
* @memberOf Behavior#
*/
reindex: function() {
this.dataModel.reindex();
this.shapeChanged();
},
/**
* @memberOf Behavior#
* @desc utility function to empty an object of its members
* @param {object} obj - the object to empty
* @param {boolean} [exportProps]
* * `undefined` (omitted) - delete *all* properties
* * **falsy** - delete *only* the export properties
* * **truthy** - delete all properties *except* the export properties
*/
clearObjectProperties: function(obj, exportProps) {
for (var key in obj) {
if (
obj.hasOwnProperty(key) && (
exportProps === undefined ||
!exportProps && noExportProperties.indexOf(key) >= 0 ||
exportProps && noExportProperties.indexOf(key) < 0
)
) {
delete obj[key];
}
}
},
/**
* @memberOf Behavior#
* @desc getter for a [Memento](http://c2.com/cgi/wiki?MementoPattern) Object
* @returns {object}
*/
getPrivateState: function() {
return this.deprecated('getPrivateState()', 'grid.properties', '1.2.0');
},
//this is effectively a clone, with certain things removed....
getState: function() {
var copy = JSON.parse(JSON.stringify(this.grid.properties));
this.clearObjectProperties(copy.columnProperties, false);
return copy;
},
/**
* @memberOf Behavior#
* @desc clear all table state
*/
clearState: function() {
this.grid.clearState();
this.createColumns();
},
/**
* @memberOf Behavior#
* @desc Restore this table to a previous state.
* See the [memento pattern](http://c2.com/cgi/wiki?MementoPattern).
* @param {Object} properties - assignable grid properties
*/
setState: function(properties) {
this.addState(properties, true);
},
/**
*
* @param {Object} properties - assignable grid properties
* @param {boolean} [settingState] - Clear properties object before assignments.
*/
addState: function(properties, settingState) {
if (settingState) {
this.clearState();
}
var gridProps = this.grid.properties;
gridProps.settingState = settingState;
assignOrDelete(gridProps, properties);
delete gridProps.settingState;
this.reindex();
},
/**
* @summary Sets properties for active columns.
* @desc Sets multiple columns' properties from elements of given array or collection. Keys may be column indexes or column names. The properties collection is cleared first. Falsy elements are ignored.
* @param {object[]|undefined} columnsHash - If undefined, this call is a no-op.
*/
setAllColumnProperties: function(columnsHash) {
this.addAllColumnProperties(columnsHash, true);
},
/**
* @summary Adds properties for multiple columns.
* @desc Adds . The properties collection is optionally cleared first. Falsy elements are ignored.
* @param {object[]|undefined} columnsHash - If undefined, this call is a no-op.
* @param {boolean} [settingState] - Clear columns' properties objects before copying properties.
*/
addAllColumnProperties: function(columnsHash, settingState) {
if (!columnsHash) {
return;
}
var columns = this.grid.behavior.getColumns();
Object.keys(columnsHash).forEach(function(key) {
var column = columns[key];
if (column) {
column.addProperties(columnsHash[key], settingState);
}
});
},
setColumnOrder: function(columnIndexes) {
if (Array.isArray(columnIndexes)){
var columns = this.columns,
allColumns = this.allColumns;
// avoid recreating the array to keep refs valid; just empty it (including name dictionary)
columns.length = 0;
var tc = this.treeColumnIndex.toString(), rc = this.rowColumnIndex.toString();
Object.keys(columns).forEach(function(key) {
switch (key) {
case tc:
case rc:
break;
default:
delete columns[key];
}
});
columnIndexes.forEach(function(index) {
columns.push(allColumns[index]);
});
this.synonomous.decorateList(columns);
}
},
setColumnOrderByName: function(columnNames) {
if (Array.isArray(columnNames)) {
var allColumns = this.allColumns;
this.setColumnOrder(columnNames.map(function(name) { return allColumns[name].index; }));
}
},
_setColumnOrder: function(columnIndexes) {
this.deprecated('_setColumnOrder(columnIndexes)', 'setColumnOrder(columnIndexes)', '1.2.10', arguments);
},
/**
* @memberOf Behavior#
* @desc Rebuild the column order indexes
* @param {Array} columnIndexes - list of column indexes
* @param {Boolean} [silent=false] - whether to trigger column changed event
*/
setColumnIndexes: function(columnIndexes, silent) {
this.grid.properties.columnIndexes = columnIndexes;
if (!silent) {
this.grid.fireSyntheticOnColumnsChangedEvent();
}
},
/**
* @summary Show inactive column(s) or move active column(s).
*
* @desc Adds one or several columns to the "active" column list.
*
* @param {boolean} [isActiveColumnIndexes=false] - Which list `columnIndexes` refers to:
* * `true` - The active column list. This can only move columns around within the active column list; it cannot add inactive columns (because it can only refer to columns in the active column list).
* * `false` - The full column list (as per column schema array). This inserts columns from the "inactive" column list, moving columns that are already active.
*
* @param {number|number[]} columnIndexes - Column index(es) into list as determined by `isActiveColumnIndexes`. One of:
* * **Scalar column index** - Adds single column at insertion point.
* * **Array of column indexes** - Adds multiple consecutive columns at insertion point.
*
* _This required parameter is promoted left one arg position when `isActiveColumnIndexes` omitted._
*
* @param {number} [referenceIndex=this.columns.length] - Insertion point, _i.e.,_ the element to insert before. A negative values skips the reinsert. Default is to insert new columns at end of active column list.
*
* _Promoted left one arg position when `isActiveColumnIndexes` omitted._
*
* @param {boolean} [allowDuplicateColumns=false] - Unless true, already visible columns are removed first.
*
* _Promoted left one arg position when `isActiveColumnIndexes` omitted + one position when `referenceIndex` omitted._
*
* @memberOf Behavior#
*/
showColumns: function(isActiveColumnIndexes, columnIndexes, referenceIndex, allowDuplicateColumns) {
// Promote args when isActiveColumnIndexes omitted
if (typeof isActiveColumnIndexes === 'number' || Array.isArray(isActiveColumnIndexes)) {
allowDuplicateColumns = referenceIndex;
referenceIndex = columnIndexes;
columnIndexes = isActiveColumnIndexes;
isActiveColumnIndexes = false;
}
var activeColumns = this.columns,
sourceColumnList = isActiveColumnIndexes ? activeColumns : this.allColumns;
// Nest scalar index
if (typeof columnIndexes === 'number') {
columnIndexes = [columnIndexes];
}
var newColumns = columnIndexes
// Look up columns using provided indexes
.map(function(index) { return sourceColumnList[index]; })
// Remove any undefined columns
.filter(function(column) { return column; });
// Default insertion point is end (i.e., before (last+1)th element)
if (typeof referenceIndex !== 'number') {
allowDuplicateColumns = referenceIndex; // assume reference index was omitted when not a number
referenceIndex = activeColumns.length;
}
// Remove already visible columns and adjust insertion point
if (!allowDuplicateColumns) {
newColumns.forEach(function(column) {
var i = activeColumns.indexOf(column);
if (i >= 0) {
activeColumns.splice(i, 1);
if (referenceIndex > i) {
--referenceIndex;
}
}
});
}
// Insert the new columns at the insertion point
if (referenceIndex >= 0) {
activeColumns.splice.apply(activeColumns, [referenceIndex, 0].concat(newColumns));
}
this.grid.properties.columnIndexes = activeColumns.map(function(column) { return column.index; });
},
/**
* @summary Hide active column(s).
* @desc Removes one or several columns from the "active" column list.
* @param {boolean} [isActiveColumnIndexes=false] - Which list `columnIndexes` refers to:
* * `true` - The active column list.
* * `false` - The full column list (as per column schema array).
* @param {number|number[]} columnIndexes - Column index(es) into list as determined by `isActiveColumnIndexes`. One of:
* * **Scalar column index** - Adds single column at insertion point.
* * **Array of column indexes** - Adds multiple consecutive columns at insertion point.
*
* _This required parameter is promoted left one arg position when `isActiveColumnIndexes` omitted._
* @memberOf Behavior#
*/
hideColumns: function(isActiveColumnIndexes, columnIndexes) {
var args = Array.prototype.slice.call(arguments); // Convert to array so we can add an argument (element)
args.push(-1); // Remove only; do not reinsert.
this.showColumns.apply(this, args);
},
/**
* @memberOf Behavior#
* @desc fetch the value for a property key
* @returns {*} The value of the given property.
* @param {string} key - a property name
*/
resolveProperty: function(key) {
// todo: remove when we remove the deprecated grid.resolveProperty
return this.grid.resolveProperty(key);
},
/**
* @memberOf Behavior#
* @desc A specific cell was clicked; you've been notified.
* @param {Object} event - all event information
* @return {boolean} Clicked in a drill-down column.
*/
cellClicked: function(event) {
if (arguments.length === 2) {
return this.deprecated('cellClicked(cell, event)', 'cellClicked(event)', '1.2.0', arguments);
}
return this.dataModel.cellClicked(event);
},
/**
* @memberOf Behavior#
* @desc A specific cell was le double-clicked; you've been notified.
* @param {Point} cell - point of cell coordinates
* @param {Object} event - all event information
*/
cellDoubleClicked: function(cell, event) {
},
lookupFeature: function(key) {
return this.featureMap[key];
},
/**
* @param {CellEvent|number} xOrCellEvent - Grid column coordinate.
* @param {number} [y] - Grid row coordinate. Omit if `xOrCellEvent` is a CellEvent.
* @param {dataModelAPI} [dataModel] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid. If given, x and y are interpreted as data cell coordinates (unadjusted for scrolling). Does not default to the data subgrid, although you can provide it explicitly (`this.subgrids.lookup.data`).
* @memberOf Behavior#
*/
getValue: function(xOrCellEvent, y, dataModel) {
if (typeof xOrCellEvent !== 'object') {
var x = xOrCellEvent;
xOrCellEvent = new this.CellEvent;
if (dataModel) {
xOrCellEvent.resetDataXY(x, y, dataModel);
} else {
xOrCellEvent.resetGridCY(x, y);
}
}
return xOrCellEvent.value;
},
/**
* @summary Gets the number of rows in the data subgrid.
* @memberOf Behavior.prototype
*/
getRowCount: function() {
return this.dataModel.getRowCount();
},
getUnfilteredValue: function(x, y) {
return this.deprecated('getUnfilteredValue()', '', '1.4.0', arguments, 'No longer supported');
},
/**
* @memberOf Behavior#
* @desc update the data at point x, y with value
* @return The data.
* @param {CellEvent|number} xOrCellEvent - Grid column coordinate.
* @param {number} [y] - Grid row coordinate. Omit if `xOrCellEvent` is a CellEvent.
* @param {Object} value - The value to use. _When `y` omitted, promoted to 2nd arg._
* @param {dataModelAPI} [dataModel] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid. If given, x and y are interpreted as data cell coordinates (unadjusted for scrolling). Does not default to the data subgrid, although you can provide it explicitly (`this.subgrids.lookup.data`).
* @return {boolean} Consumed.
*/
setValue: function(xOrCellEvent, y, value, dataModel) {
if (typeof xOrCellEvent === 'object') {
value = y;
} else {
var x = xOrCellEvent;
xOrCellEvent = new this.CellEvent;
if (dataModel) {
xOrCellEvent.resetDataXY(x, y, dataModel);
} else {
xOrCellEvent.resetGridCY(x, y);
}
}
xOrCellEvent.value = value;
},
getDataValue: function(x, y) {
return this.deprecated('getDataValue(x, y)', 'dataModel.getValue(x, y)', '1.1.0', arguments);
},
setDataValue: function(x, y, value) {
return this.deprecated('setDataValue(x, y, value)', 'dataModel.setValue(x, y, value)', '1.1.0', arguments);
},
/**
* @summary Get the cell's own properties object.
* @desc May be undefined because cells only have their own properties object when at lest one own property has been set.
* @param {CellEvent|number} xOrCellEvent - Data x coordinate.
* @param {number} [y] - Grid row coordinate. _Omit when `xOrCellEvent` is a `CellEvent`._
* @param {dataModelAPI} [dataModel=this.subgrids.lookup.data] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid.
* @returns {undefined|object} The "own" properties of the cell at x,y in the grid. If the cell does not own a properties object, returns `undefined`.
* @memberOf Behavior#
*/
getCellOwnProperties: function(xOrCellEvent, y, dataModel) {
switch (arguments.length) {
case 1: // xOrCellEvent is cellEvent
return xOrCellEvent.column.getCellOwnProperties(xOrCellEvent.dataCell.y, xOrCellEvent.subgrid);
case 2: case 3: // xOrCellEvent is x
return this.getColumn(xOrCellEvent).getCellOwnProperties(y, dataModel);
}
},
/**
* @summary Get the properties object for cell.
* @desc This is the cell's own properties object if found else the column object.
*
* If you are seeking a single specific property, consider calling {@link Behavior#getCellProperty} instead.
* @param {CellEvent|number} xOrCellEvent - Data x coordinate.
* @param {number} [y] - Grid row coordinate. _Omit when `xOrCellEvent` is a `CellEvent`._
* @param {dataModelAPI} [dataModel=this.subgrids.lookup.data] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid.
* @return {object} The properties of the cell at x,y in the grid.
* @memberOf Behavior#
*/
getCellProperties: function(xOrCellEvent, y, dataModel) {
switch (arguments.length) {
case 1: // xOrCellEvent is cellEvent
return xOrCellEvent.properties;
case 2: case 3: // xOrCellEvent is x
return this.getColumn(xOrCellEvent).getCellProperties(y, dataModel);
}
},
/**
* @summary Return a specific cell property.
* @desc If there is no cell properties object, defers to column properties object.
* @param {CellEvent|number} xOrCellEvent - Data x coordinate.
* @param {number} [y] - Grid row coordinate._ Omit when `xOrCellEvent` is a `CellEvent`._
* @param {string} key - Name of property to get. _When `y` omitted, this param promoted to 2nd arg._
* @param {dataModelAPI} [dataModel=this.subgrids.lookup.data] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid.
* @return {object} The specified property for the cell at x,y in the grid.
* @memberOf Behavior#
*/
getCellProperty: function(xOrCellEvent, y, key, dataModel) {
if (typeof xOrCellEvent === 'object') {
key = y;
return xOrCellEvent.properties[key];
} else {
return this.getColumn(xOrCellEvent).getCellProperty(y, key, dataModel);
}
},
/**
* @memberOf Behavior#
* @desc update the data at point x, y with value
* @param {CellEvent|number} xOrCellEvent - Data x coordinate.
* @param {number} [y] - Grid row coordinate. _Omit when `xOrCellEvent` is a `CellEvent`._
* @param {Object} properties - Hash of cell properties. _When `y` omitted, this param promoted to 2nd arg._
* @param {dataModelAPI} [dataModel=this.subgrids.lookup.data] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid.
*/
setCellProperties: function(xOrCellEvent, y, properties, dataModel) {
if (typeof xOrCellEvent === 'object') {
properties = y;
return xOrCellEvent.column.setCellProperties(xOrCellEvent.dataCell.y, properties, xOrCellEvent.subgrid);
} else {
return this.getColumn(xOrCellEvent).setCellProperties(y, properties, dataModel);
}
},
/**
* @memberOf Behavior#
* @desc update the data at point x, y with value
* @param {CellEvent|number} xOrCellEvent - Data x coordinate.
* @param {number} [y] - Grid row coordinate. _Omit when `xOrCellEvent` is a `CellEvent`._
* @param {Object} properties - Hash of cell properties. _When `y` omitted, this param promoted to 2nd arg._
* @param {dataModelAPI} [dataModel=this.subgrids.lookup.data] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid.
*/
addCellProperties: function(xOrCellEvent, y, properties, dataModel) {
if (typeof xOrCellEvent === 'object') {
properties = y;
return xOrCellEvent.column.addCellProperties(xOrCellEvent.dataCell.y, properties, xOrCellEvent.subgrid); // y omitted so y here is actually properties
} else {
return this.getColumn(xOrCellEvent).addCellProperties(y, properties, dataModel);
}
},
/**
* @summary Set a specific cell property.
* @desc If there is no cell properties object, defers to column properties object.
*
* NOTE: For performance reasons, renderer's cell event objects cache their respective cell properties objects. This method accepts a `CellEvent` overload. Whenever possible, use the `CellEvent` from the renderer's cell event pool. Doing so will reset the cell properties object cache.
*
* If you use some other `CellEvent`, the renderer's `CellEvent` properties cache will not be automatically reset until the whole cell event pool is reset on the next call to {@link Renderer#computeCellBoundaries}. If necessary, you can "manually" reset it by calling {@link Renderer#resetCellPropertiesCache|resetCellPropertiesCache(yourCellEvent)} which searches the cell event pool for one with matching coordinates and resets the cache.
*
* The raw coordinates overload calls the `resetCellPropertiesCache(x, y)` overload for you.
* @param {CellEvent|number} xOrCellEvent - `CellEvent` or data x coordinate.
* @param {number} [y] - Grid row coordinate. _Omit when `xOrCellEvent` is a `CellEvent`._
* @param {string} key - Name of property to get. _When `y` omitted, this param promoted to 2nd arg._
* @param value
* @param {dataModelAPI} [dataModel=this.subgrids.lookup.data] - For use only when `xOrCellEvent` is _not_ a `CellEvent`: Provide a subgrid.
* @memberOf Behavior#
*/
setCellProperty: function(xOrCellEvent, y, key, value, dataModel) {
var cellOwnProperties;
if (typeof xOrCellEvent === 'object') {
value = key;
key = y;
cellOwnProperties = xOrCellEvent.setCellProperty(key, value);
} else {
cellOwnProperties = this.getColumn(xOrCellEvent).setCellProperty(y, key, value, dataModel);
this.grid.renderer.resetCellPropertiesCache(xOrCellEvent, y, dataModel);
}
return cellOwnProperties;
},
getUnfilteredRowCount: function() {
return this.deprecated('getUnfilteredRowCount()', '', '1.2.0', arguments, 'No longer supported');
},
/**
* @memberOf Behavior#
* @desc The value is lazily initialized and comes from the properties mechanism for '`defaultRowHeight`', which should be ~20px.
* @returns {number} The row height in pixels.
*/
getDefaultRowHeight: function() {
return this.deprecated('getDefaultRowHeight()', 'grid.properties.defaultRowHeight', '1.2.0');
},
/**
* @memberOf Behavior#
* @return {number} The width of the fixed column area in the hypergrid.
*/
getFixedColumnsWidth: function() {
var count = this.getFixedColumnCount(),
columnWidth,
total = 0,
i = this.leftMostColIndex,
gridProps = this.grid.properties,
contentBox = gridProps.boxSizing !== 'border-box',
gridLinesVWidth = gridProps.gridLinesVWidth;
for (; i < count; i++) {
columnWidth = this.getColumnWidth(i);
if (columnWidth) {
total += columnWidth;
if (contentBox) {
total += gridLinesVWidth;
}
}
}
// add in fixed rule thickness excess
if (gridProps.fixedLinesVWidth) {
total += gridProps.fixedLinesHWidth - gridLinesVWidth;
}
return total;
},
/**
* @memberOf Behavior#
* @desc This exists to support "floating" columns.
* @return {number} The total width of the fixed columns area.
*/
getFixedColumnsMaxWidth: function() {
return this.getFixedColumnsWidth();
},
/**
* @memberOf Behavior#
* @desc Set the scroll position in vertical dimension and notify listeners.
* @param {number} y - the new y value
*/
_setScrollPositionY: function(y) {
this.setScrollPositionY(y);
this.changed();
},
/**
* @memberOf Behavior#
* @desc Set the scroll position in horizontal dimension and notify listeners.
* @param {number} x - the new x value
*/
_setScrollPositionX: function(x) {
this.setScrollPositionX(x);
this.changed();
},
/**
* @memberOf Behavior#
* @desc The fixed row area has been clicked, massage the details and call the real function.
* @param {Hypergrid} grid
* @param {Object} mouse - event details
*/
_fixedRowClicked: function(grid, mouse) {
var x = this.translateColumnIndex(this.getScrollPositionX() + mouse.gridCell.x - this.getFixedColumnCount());
var translatedPoint = new Point(x, mouse.gridCell.y);
mouse.gridCell = translatedPoint;
this.fixedRowClicked(grid, mouse);
},
/**
* @memberOf Behavior#
* @desc The fixed column area has been clicked, massage the details and call the real function.
* @param {Hypergrid} grid
* @param {Object} mouse - event details
*/
_fixedColumnClicked: function(grid, mouse) {
var translatedPoint = new Point(mouse.gridCell.x, this.getScrollPositionY() + mouse.gridCell.y - this.getFixedRowCount());
mouse.gridCell = translatedPoint;
this.fixedColumnClicked(grid, mouse);
},
/**
* @memberOf Behavior#
* @desc delegate setting the cursor up the feature chain of responsibility
* @param {Hypergrid} grid
*/
setCursor: function(grid) {
grid.updateCursor();
this.featureChain.setCursor(grid);
},
/**
* @memberOf Behavior#
* @desc delegate handling mouse move to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onMouseMove: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleMouseMove(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling tap to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onClick: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleClick(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling tap to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onContextMenu: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleContextMenu(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling wheel moved to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onWheelMoved: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleWheelMoved(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling mouse up to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onMouseUp: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleMouseUp(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling mouse drag to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onMouseDrag: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleMouseDrag(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling key down to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onKeyDown: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleKeyDown(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling key up to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onKeyUp: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleKeyUp(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling double click to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
onDoubleClick: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleDoubleClick(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling mouse down to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseDown: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleMouseDown(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc delegate handling mouse exit to the feature chain of responsibility
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseExit: function(grid, event) {
if (this.featureChain) {
this.featureChain.handleMouseExit(grid, event);
this.setCursor(grid);
}
},
/**
* @memberOf Behavior#
* @desc I've been notified that the behavior has changed.
*/
changed: function() { this.grid.behaviorChanged(); },
/**
* @memberOf Behavior#
* @desc The dimensions of the grid data have changed. You've been notified.
*/
shapeChanged: function() { this.grid.behaviorShapeChanged(); },
/**
* @memberOf Behavior#
* @desc The dimensions of the grid data have changed. You've been notified.
*/
stateChanged: function() { this.grid.behaviorStateChanged(); },
/**
* @memberOf Behavior#
* @return {boolean} Can re-order columns.
*/
isColumnReorderable: function() {
return this.deprecated('isColumnReorderable()', 'grid.properties.columnsReorderable', '2.1.3');
},
/**
* @param {index} x - Data x coordinate.
* @return {Object} The properties for a specific column.
* @memberOf Behavior#
*/
getColumnProperties: function(x) {
var column = this.getColumn(x);
return column && column.properties;
},
/**
* @param {index} x - Data x coordinate.
* @return {Object} The properties for a specific column.
* @memberOf Behavior#
*/
setColumnProperties: function(x, properties) {
var column = this.getColumn(x);
if (!column) {
throw 'Expected column.';
}
var result = Object.assign(column.properties, properties);
this.changed();
return result;
},
/**
* Clears all cell properties of given column or of all columns.
* @param {number} [x] - Omit for all columns.
* @memberOf Behavior#
*/
clearAllCellProperties: function(x) {
if (x !== undefined) {
var column = this.getColumn(x);
if (column) {
column.clearAllCellProperties();
}
} else if (this.subgrids) {
this.subgrids.forEach(function(dataModel) {
for (var i = dataModel.getRowCount(); i--;) {
delete dataModel.getRow(i).__META;
// todo: test if optimizer wants following instead
// dataRow = dataModel.getRow(i);
// if (dataRow.__META !== undefined) { dataRow.__META = undefined; }
}
});
}
},
/**
* @memberOf Behavior#
* @return {string[]} All the currently hidden column header labels.
*/
getHiddenColumnDescriptors: function() {
var tableState = this.grid.properties;
var indexes = tableState.columnIndexes;
var labels = [];
var columnCount = this.getActiveColumnCount();
for (var i = 0; i < columnCount; i++) {
if (indexes.indexOf(i) === -1) {
var column = this.getActiveColumn(i);
labels.push({
id: i,
header: column.header,
field: column.name
});
}
}
return labels;
},
/**
* @memberOf Behavior#
* @return {number} The number of fixed columns.
*/
getFixedColumnCount: function() {
return this.grid.properties.fixedColumnCount;
},
/**
* @memberOf Behavior#
* @desc set the number of fixed columns
* @param {number} n - the integer count of how many columns to be fixed
*/
setFixedColumnCount: function(n) {
this.grid.properties.fixedColumnCount = n;
},
/**
* @summary The number of "fixed rows."
* @desc The number of (non-scrollable) rows preceding the (scrollable) data subgrid.
* @memberOf Behavior#
* @return {number} The sum of:
* 1. All rows of all subgrids preceding the data subgrid.
* 2. The first `fixedRowCount` rows of the data subgrid.
*/
getFixedRowCount: function() {
return (
this.getHeaderRowCount() +
this.grid.properties.fixedRowCount
);
},
/**
* @memberOf Behavior#
* @desc Set the number of fixed rows, which includes (top to bottom order):
* 1. The header rows
* 1. The header labels row (optional)
* 2. The filter row (optional)
* 3. The top total rows (0 or more)
* 2. The non-scrolling rows (externally called "the fixed rows")
*
* @returns {number} Sum of the above or 0 if none of the above are in use.
*
* @param {number} The number of rows.
*/
setFixedRowCount: function(n) {
this.grid.properties.fixedRowCount = n;
},
/**
* @memberOf Behavior#
* @desc a dnd column has just been dropped, we've been notified
*/
endDragColumnNotification: function() {},
/**
* @memberOf Behavior#
* @return {null} the cursor at a specific x,y coordinate
* @param {number} x - the x coordinate
* @param {number} y - the y coordinate
*/
getCursorAt: function(x, y) {
return null;
},
/**
* Number of _visible_ columns.
* @memberOf Behavior#
* @return {number} The total number of columns.
*/
getActiveColumnCount: function() {
return this.columns.length;
},
getColumnCount: function() {
return this.deprecated('getColumnCount()', 'getActiveColumnCount()', '1.0.6', arguments);
},
/**
* @summary Column alignment of given grid column.
* @desc One of:
* * `'left'`
* * `'center'`
* * `'right'`
*
* Cascades to grid.
* @memberOf Behavior#
* @desc Quietly set the horizontal scroll position.
* @param {number} x - The new position in pixels.
*/
setScrollPositionX: function(x) {
/**
* @memberOf Behavior#
* @type {number}
*/
this.scrollPositionX = x;
},
getScrollPositionX: function() {
return this.scrollPositionX;
},
/**
* @memberOf Behavior#
* @desc Quietly set the vertical scroll position.
* @param {number} y - The new position in pixels.
*/
setScrollPositionY: function(y) {
/**
* @memberOf Behavior#
* @type {number}
*/
this.scrollPositionY = y;
},
getScrollPositionY: function() {
return this.scrollPositionY;
},
/**
* @memberOf Behavior#
* @return {cellEditor} The cell editor for the cell at the given coordinates.
* @param {CellEvent} editPoint - The grid cell coordinates.
*/
getCellEditorAt: function(event) {
return event.isDataColumn && event.column.getCellEditorAt(event);
},
/**
* @memberOf Behavior#
* @return {boolean} `true` if we should highlight on hover
* @param {boolean} isColumnHovered - the column is hovered or not
* @param {boolean} isRowHovered - the row is hovered or not
*/
highlightCellOnHover: function(isColumnHovered, isRowHovered) {
return isColumnHovered && isRowHovered;
},
/**
* @memberOf Behavior#
* @desc this function is a hook and is called just before the painting of a cell occurs
* @param {window.fin.rectangular.Point} cell
*/
cellPropertiesPrePaintNotification: function(cell) {
},
/**
* @memberOf Behavior#
* @desc this function is a hook and is called just before the painting of a fixed row cell occurs
* @param {window.fin.rectangular.Point} cell
*/
cellFixedRowPrePaintNotification: function(cell) {
},
/**
* @memberOf Behavior#
* @desc this function is a hook and is called just before the painting of a fixed column cell occurs
* @param {window.fin.rectangular.Point} cell
*/
cellFixedColumnPrePaintNotification: function(cell) {
},
/**
* @memberOf Behavior#
* @desc this function is a hook and is called just before the painting of a top left cell occurs
* @param {window.fin.rectangular.Point} cell
*/
cellTopLeftPrePaintNotification: function(cell) {
},
/**
* @memberOf Behavior#
* @desc swap src and tar columns
* @param {number} src - column index
* @param {number} tar - column index
*/
swapColumns: function(source, target) {
var columns = this.columns;
var tmp = columns[source];
columns[source] = columns[target];
columns[target] = tmp;
this.changed();
},
getColumnEdge: function(c, renderer) {
return this.dataModel.getColumnEdge(c, renderer);
},
/**
* @memberOf Behavior#
* @return {object} The object at y index.
* @param {number} y - the row index of interest
*/
getRow: function(y) {
return this.dataModel.getRow(y);
},
convertViewPointToDataPoint: function(unscrolled) {
return new Point(
this.getActiveColumn(unscrolled.x).index,
unscrolled.y
);
},
hasTreeColumn: function() {
return false;
},
getSelectionMatrixFunction: function(selectedRows) {
return function() {
return null;
};
},
getRowHeaderColumn: function() {
return this.allColumns[this.rowColumnIndex];
},
getTreeColumn: function() {
return this.allColumns[this.treeColumnIndex];
},
autosizeAllColumns: function() {
this.checkColumnAutosizing(true);
this.changed();
},
checkColumnAutosizing: function(force) {
var autoSized = this.autoSizeRowNumberColumn() || this.autoSizeTreeColumn();
this.allColumns.forEach(function(column) {
autoSized = column.checkColumnAutosizing(force) || autoSized;
});
return autoSized;
},
autoSizeRowNumberColumn: function() {
if (this.grid.properties.showRowNumbers && this.grid.properties.rowNumberAutosizing) {
return this.getRowHeaderColumn().checkColumnAutosizing(true);
}
},
autoSizeTreeColumn: function() {
if (this.grid.properties.showTreeColumn && this.grid.properties.treeColumnAutosizing) {
return this.getTreeColumn().checkColumnAutosizing(true);
}
},
/**
* @summary Get the given data controller.
* @param {string} type
* @returns {undefined|*} The data controller; or `undefined` if data controller unknown to data model.
* @memberOf Behavior#
*/
getController: function(type) {
return this.dataModel.getController(type);
},
/**
* @summary Set the given data controller(s).
* @desc Triggers a shape change.
* @param {string} typeOrHashOfTypes - One of:
* * **object** - Hash of multiple data controllers, by type.
* * **string** - Type of the single data controller given in `controller`.
* @param {dataControlInterface} [controller] - Only required when 'hash' is a string; omit when `hash` is an object.
* @returns {object} - Hash of all results, by type. Each member will be:
* * The given data controller for that type when defined.
* * A new "null" data controller, generated by the data model when the given data controller for that type was `undefined`.
* * `undefined` - The data controller was unknown to the data model..
* @memberOf Behavior#
*/
setController: function(typeOrHashOfTypes, controller) {
var results = this.dataModel.setController(typeOrHashOfTypes, controller);
this.changed();
return results;
},
prop: function(type, columnIndex, keyOrHash, value) {
var result = this.dataModel.prop.apply(this.dataModel, arguments);
if (result === undefined) {
this.changed();
}
return result;
},
get charMap() {
return this.dataModel.charMap;
},
getColumns: function() {
return this.allColumns;
},
getActiveColumns: fun