UNPKG

canvas-datagrid

Version:

Canvas based data grid web component. Capable of displaying millions of contiguous hierarchical rows and columns without paging or loading, on a single canvas element.

1,092 lines (1,091 loc) 45.6 kB
/*jslint browser: true, unparam: true, todo: true*/ /*globals HTMLElement: false, Reflect: false, define: true, MutationObserver: false, requestAnimationFrame: false, performance: false, btoa: false*/ define([], function () { 'use strict'; return function (self, ctor) { self.scale = 1; self.orders = { rows: [], columns: [] }; self.visibleRowHeights = []; self.hasFocus = false; self.activeCell = { columnIndex: 0, rowIndex: 0 }; self.storageName = 'canvasDataGrid'; self.invalidSearchExpClass = 'canvas-datagrid-invalid-search-regExp'; self.localStyleLibraryStorageKey = 'canvas-datagrid-user-style-library'; self.uniqueId = '_canvasDataGridUniqueId'; self.orderBy = self.uniqueId; self.orderDirection = 'asc'; self.columnFilters = {}; self.filters = {}; self.frozenRow = 0; self.frozenColumn = 0; self.ellipsisCache = {}; self.scrollBox = {}; self.visibleRows = []; self.sizes = { rows: {}, columns: {}, trees: {} }; self.currentFilter = function () { return true; }; self.selections = []; self.hovers = {}; self.attributes = {}; self.style = {}; self.formatters = {}; self.sorters = {}; self.schemaHashes = {}; self.events = {}; self.uId = 0; self.changes = []; self.scrollIndexTop = 0; self.scrollPixelTop = 0; self.scrollIndexLeft = 0; self.scrollPixelLeft = 0; self.childGrids = {}; self.openChildren = {}; self.scrollModes = [ 'vertical-scroll-box', 'vertical-scroll-top', 'vertical-scroll-bottom', 'horizontal-scroll-box', 'horizontal-scroll-right', 'horizontal-scroll-left' ]; self.componentL1Events = {}; self.eventNames = ['afterdraw', 'afterrendercell', 'attributechanged', 'beforebeginedit', 'beforecreatecellgrid', 'beforedraw', 'beforeendedit', 'beforerendercell', 'beforerendercellgrid', 'beginedit', 'cellmouseout', 'cellmouseover', 'click', 'collapsetree', 'contextmenu', 'copy', 'datachanged', 'dblclick', 'endedit', 'expandtree', 'formatcellvalue', 'keydown', 'keypress', 'keyup', 'mousedown', 'mousemove', 'mouseup', 'newrow', 'ordercolumn', 'rendercell', 'rendercellgrid', 'renderorderbyarrow', 'rendertext', 'rendertreearrow', 'reorder', 'reordering', 'resize', 'resizecolumn', 'resizerow', 'schemachanged', 'scroll', 'selectionchanged', 'stylechanged', 'touchcancel', 'touchend', 'touchmove', 'touchstart', 'wheel']; self.mouse = { x: 0, y: 0}; self.getSelectedData = function (expandToRow) { var d = [], s = expandToRow ? self.getSchema() : self.getVisibleSchema(), l = self.data.length; if (l === 0) { return []; } self.selections.forEach(function (row, index) { if (index === l) { return; } if (row.length === 0) { d[index] = null; return; } d[index] = {}; row.forEach(function (col) { if (col === -1 || !s[col]) { return; } d[index][s[col].name] = self.data[index][s[col].name]; }); }); return d; }; self.fillArray = function (low, high, step, def) { step = step || 1; var i = [], x; for (x = low; x <= high; x += step) { i[x] = def === undefined ? x : (typeof def === 'function' ? def(x) : def); } return i; }; self.getColumnHeaderCellHeight = function () { if (!self.attributes.showColumnHeaders) { return 0; } return ((self.sizes.rows[-1] || self.style.columnHeaderCellHeight) * self.scale); }; self.getRowHeaderCellWidth = function () { if (!self.attributes.showRowHeaders) { return 0; } return (self.sizes.columns.cornerCell || self.style.rowHeaderCellWidth) * self.scale; }; self.setStorageData = function () { if (!self.attributes.saveAppearance || !self.attributes.name) { return; } var visibility = {}; self.getSchema().forEach(function (column) { visibility[column.name] = !column.hidden; }); localStorage.setItem(self.storageName + '-' + self.attributes.name, JSON.stringify({ sizes: { rows: self.sizes.rows, columns: self.sizes.columns }, orders: { rows: self.orders.rows, columns: self.orders.columns }, orderBy: self.orderBy, orderDirection: self.orderDirection, visibility: visibility })); }; self.getSchema = function () { return self.schema || self.tempSchema; }; self.createColumnOrders = function () { var s = self.getSchema(); self.orders.columns = self.fillArray(0, s.length - 1); }; self.createRowOrders = function () { self.orders.rows = self.fillArray(0, self.data.length - 1); }; self.getVisibleSchema = function () { return self.getSchema().filter(function (col) { return !col.hidden && col.name !== self.uniqueId; }); }; self.applyDefaultValue = function (row, header) { var d = header.defaultValue || ''; if (typeof d === 'function') { d = d.apply(self.intf, [header]); } row[header.name] = d; }; self.createNewRowData = function () { self.newRow = {}; self.newRow[self.uniqueId] = self.uId; self.uId += 1; self.getSchema().forEach(function forEachHeader(header) { self.applyDefaultValue(self.newRow, header); }); }; self.getSchemaNameHash = function (key) { var n = 0; while (self.schemaHashes[key]) { n += 1; key = key + n; } return key; }; self.filter = function (type) { var f = self.filters[type]; if (!f && type !== undefined) { console.warn('Cannot find filter for type %s, falling back to substring match.', type); f = self.filters.string; } return f; }; self.getBestGuessDataType = function (columnName) { var t, x, l = self.data.length; for (x = 0; x < l; x += 1) { if ([null, undefined].indexOf(self.data[x][columnName]) !== -1) { t = typeof self.data[x]; return t === 'object' ? 'string' : t; } } return 'string'; }; self.drawChildGrids = function () { Object.keys(self.childGrids).forEach(function (gridKey) { self.childGrids[gridKey].draw(); }); }; self.resizeChildGrids = function () { Object.keys(self.childGrids).forEach(function (gridKey) { self.childGrids[gridKey].resize(); }); }; self.autoScrollZone = function (e, x, y, ctrl) { var setTimer, rowHeaderCellWidth = self.getRowHeaderCellWidth(), columnHeaderCellHeight = self.getColumnHeaderCellHeight(); if (x > self.width - self.attributes.selectionScrollZone && x < self.width) { self.scrollBox.scrollLeft += self.attributes.selectionScrollIncrement; setTimer = true; } if (y > self.height - self.attributes.selectionScrollZone && y < self.height) { self.scrollBox.scrollTop += self.attributes.selectionScrollIncrement; setTimer = true; } if (x - self.attributes.selectionScrollZone - rowHeaderCellWidth < 0) { self.scrollBox.scrollLeft -= self.attributes.selectionScrollIncrement; setTimer = true; } if (y - self.attributes.selectionScrollZone - columnHeaderCellHeight < 0) { self.scrollBox.scrollTop -= self.attributes.selectionScrollIncrement; setTimer = true; } if (setTimer && !ctrl && self.currentCell && self.currentCell.columnIndex !== -1) { self.scrollTimer = setTimeout(self.mousemove, self.attributes.scrollRepeatRate, e); } }; self.refreshFromOrigialData = function () { self.data = self.originalData.filter(function (row) { return true; }); }; self.validateColumn = function (c, s) { if (!c.name) { throw new Error('A column must contain at least a name.'); } if (s.filter(function (i) { return i.name === c.name; }).length > 0) { throw new Error('A column with the name ' + c.name + ' already exists and cannot be added again.'); } return true; }; self.setDefaults = function (obj1, obj2, key, def) { obj1[key] = obj2[key] === undefined ? def : obj2[key]; }; self.setAttributes = function () { self.defaults.attributes.forEach(function eachAttribute(i) { self.setDefaults(self.attributes, self.args, i[0], i[1]); }); }; self.setStyle = function () { self.defaults.styles.forEach(function eachStyle(i) { self.setDefaults(self.style, self.args.style || {}, i[0], i[1]); }); }; self.autosize = function (colName) { self.getVisibleSchema().forEach(function (col) { if (col.name === colName || colName === undefined) { self.fitColumnToValues(col.name); } }); self.fitColumnToValues('cornerCell'); }; self.dispose = function () { if (!self.isChildGrid && self.canvas && self.canvas.parentNode) { self.canvas.parentNode.removeChild(self.canvas); } self.eventParent.removeEventListener('mouseup', self.mouseup, false); self.eventParent.removeEventListener('mousedown', self.mousedown, false); self.eventParent.removeEventListener('dblclick', self.dblclick, false); self.eventParent.removeEventListener('click', self.click, false); self.eventParent.removeEventListener('mousemove', self.mousemove); self.eventParent.removeEventListener('wheel', self.scrollWheel, false); self.canvas.removeEventListener('contextmenu', self.contextmenu, false); self.canvas.removeEventListener('copy', self.copy); self.controlInput.removeEventListener('keypress', self.keypress, false); self.controlInput.removeEventListener('keyup', self.keyup, false); self.controlInput.removeEventListener('keydown', self.keydown, false); window.removeEventListener('resize', self.resize); if (self.observer && self.observer.disconnect) { self.observer.disconnect(); } }; self.tryLoadStoredOrders = function () { var s; if (self.storedSettings && typeof self.storedSettings.orders === 'object' && self.storedSettings.orders !== null) { if (self.storedSettings.orders.rows.length >= self.data.length) { self.orders.rows = self.storedSettings.orders.rows; } s = self.getSchema(); if (self.storedSettings.orders.columns.length === s.length) { self.orders.columns = self.storedSettings.orders.columns; } self.orderBy = self.storedSettings.orderBy === undefined ? self.uniqueId : self.storedSettings.orderBy; self.orderDirection = self.storedSettings.orderDirection === undefined ? self.uniqueId : self.storedSettings.orderDirection; if (self.orderBy !== self.uniqueId && self.getHeaderByName(self.orderBy) && self.orderDirection) { self.order(self.orderBy, self.orderDirection); } } }; self.getDomRoot = function () { return self.shadowRoot ? self.shadowRoot.host : self.parentNode; }; self.getFontName = function (fontStyle) { return fontStyle.replace(/\d+\.?\d*px/, ''); }; self.getFontHeight = function (fontStyle) { return parseFloat(fontStyle, 10); }; self.parseStyleValue = function (key) { if (/Font/.test(key)) { self.style[key + 'Height'] = self.getFontHeight(self.style[key]); self.style[key + 'Name'] = self.getFontName(self.style[key]); return; } // when inheriting styles from already instantiated grids, don't parse already parsed values. if (key === 'moveOverlayBorderSegments' && typeof self.style[key] === 'string') { self.style[key] = self.style[key].split(',') .map(function (i) { return parseInt(i, 10); }); } }; self.initProp = function (propName) { if (!self.args[propName]) { return; } Object.keys(self.args[propName]).forEach(function (key) { self[propName][key] = self.args[propName][key]; }); }; self.getStyleProperty = function (key) { if (self.styleKeys.indexOf(key) === -1) { return self.parentNodeStyle[key]; } return self.style[key]; }; self.setStyleProperty = function (key, value, supressDrawAndEvent) { if (self.styleKeys.indexOf(key) === -1) { self.parentNodeStyle[key] = value; } else { self.style[key] = value; self.parseStyleValue(key); } if (!supressDrawAndEvent) { self.draw(true); self.dispatchEvent('stylechanged', {name: 'style', value: value}); } }; self.reloadStoredValues = function () { if (self.attributes.name && self.attributes.saveAppearance) { self.storedSettings = localStorage.getItem(self.storageName + '-' + self.attributes.name); if (self.storedSettings) { try { self.storedSettings = JSON.parse(self.storedSettings); } catch (e) { console.warn('could not read settings from localStore', e); self.storedSettings = undefined; } } if (self.storedSettings) { if (typeof self.storedSettings.sizes === 'object' && self.storedSettings.sizes !== null) { self.sizes.rows = self.storedSettings.sizes.rows; self.sizes.columns = self.storedSettings.sizes.columns; ['trees', 'columns', 'rows'].forEach(function (i) { if (!self.sizes[i]) { self.sizes[i] = {}; } }); } if (typeof self.storedSettings.visibility === 'object') { self.getSchema().forEach(function (column) { if (self.storedSettings.visibility[column.name] !== undefined) { column.hidden = !self.storedSettings.visibility[column.name]; } }); } } } }; self.init = function () { if (self.initialized) { return; } var publicStyleKeyIntf = {}; self.setAttributes(); self.setStyle(); self.initScrollBox(); self.setDom(); self.type = 'canvas-datagrid'; self.initialized = true; self.ie = /Trident/.test(window.navigator.userAgent); self.edge = /Edge/.test(window.navigator.userAgent); self.webKit = /WebKit/.test(window.navigator.userAgent); self.moz = /Gecko/.test(window.navigator.userAgent); self.webKit = /WebKit/.test(window.navigator.userAgent); self.mobile = /Mobile/i.test(window.navigator.userAgent); self.cursorGrab = 'grab'; self.cursorGrabing = 'grabbing'; self.cursorGrab = self.webKit ? '-webkit-grab' : self.cursorGrab; self.cursorGrabing = self.moz ? '-webkit-grabbing' : self.cursorGrabbing; self.pointerLockPosition = {x: 0, y: 0}; Object.keys(self.style).forEach(self.parseStyleValue); self.intf.moveSelection = self.moveSelection; self.intf.moveTo = self.moveTo; self.intf.type = self.type; self.intf.addEventListener = self.addEventListener; self.intf.removeEventListener = self.removeEventListener; self.intf.dispatchEvent = self.dispatchEvent; /** * Releases grid resources and removes grid elements. * @memberof canvasDatagrid * @name dispose * @method */ self.intf.dispose = self.dispose; /** * Appends the grid to another element later. Not implemented. * @memberof canvasDatagrid * @name appendTo * @method * @param {number} el The element to append the grid to. */ self.intf.appendTo = self.appendTo; self.intf.filters = self.filters; self.intf.sorters = self.sorters; self.intf.autosize = self.autosize; self.intf.beginEditAt = self.beginEditAt; self.intf.endEdit = self.endEdit; self.intf.setActiveCell = self.setActiveCell; self.intf.forEachSelectedCell = self.forEachSelectedCell; self.intf.scrollIntoView = self.scrollIntoView; self.intf.clearChangeLog = self.clearChangeLog; self.intf.gotoCell = self.gotoCell; self.intf.gotoRow = self.gotoRow; self.intf.getHeaderByName = self.getHeaderByName; self.intf.findColumnScrollLeft = self.findColumnScrollLeft; self.intf.findRowScrollTop = self.findRowScrollTop; self.intf.fitColumnToValues = self.fitColumnToValues; self.intf.findColumnMaxTextLength = self.findColumnMaxTextLength; self.intf.disposeContextMenu = self.disposeContextMenu; self.intf.getCellAt = self.getCellAt; self.intf.isCellVisible = self.isCellVisible; self.intf.order = self.order; self.intf.draw = self.draw; self.intf.selectArea = self.selectArea; self.intf.clipElement = self.clipElement; self.intf.getSchemaFromData = self.getSchemaFromData; self.intf.setFilter = self.setFilter; self.intf.selectRow = self.selectRow; self.intf.parentGrid = self.parentGrid; self.intf.toggleTree = self.toggleTree; self.intf.expandTree = self.expandTree; self.intf.collapseTree = self.collapseTree; self.intf.canvas = self.canvas; self.intf.context = self.ctx; self.intf.insertRow = self.insertRow; self.intf.deleteRow = self.deleteRow; self.intf.addRow = self.addRow; self.intf.insertColumn = self.insertColumn; self.intf.deleteColumn = self.deleteColumn; self.intf.addColumn = self.addColumn; self.intf.getClippingRect = self.getClippingRect; self.intf.setRowHeight = self.setRowHeight; self.intf.setColumnWidth = self.setColumnWidth; self.intf.resetColumnWidths = self.resetColumnWidths; self.intf.resetRowHeights = self.resetRowHeights; self.intf.resize = self.resize; self.intf.selectColumn = self.selectColumn; self.intf.selectRow = self.selectRow; self.intf.selectAll = self.selectAll; self.intf.selectNone = self.selectNone; self.intf.drawChildGrids = self.drawChildGrids; self.intf.assertPxColor = self.assertPxColor; self.intf.clearPxColorAssertions = self.clearPxColorAssertions; self.intf.integerToAlpha = self.integerToAlpha; self.intf.copy = self.copy; self.intf.setStyleProperty = self.setStyleProperty; Object.defineProperty(self.intf, 'defaults', { get: function () { return { styles: self.defaults.styles.reduce(function (a, i) { a[i[0]] = i[1]; return a; }, {}), attributes: self.defaults.attributes.reduce(function (a, i) { a[i[0]] = i[1]; return a; }, {}) }; } }); self.styleKeys = Object.keys(self.intf.defaults.styles); self.DOMStyles = window.getComputedStyle(document.body, null); Object.keys(self.DOMStyles).concat(Object.keys(self.style)).forEach(function (key) { // unless this line is here, Object.keys() will not work on <instance>.style publicStyleKeyIntf[key] = undefined; Object.defineProperty(publicStyleKeyIntf, key, { get: function () { return self.getStyleProperty(key); }, set: function (value) { self.setStyleProperty(key, value); } }); }); Object.defineProperty(self.intf, 'shadowRoot', { get: function () { return self.shadowRoot; } }); Object.defineProperty(self.intf, 'activeCell', { get: function () { return self.activeCell; } }); Object.defineProperty(self.intf, 'hasFocus', { get: function () { return self.hasFocus; } }); Object.defineProperty(self.intf, 'style', { get: function () { return publicStyleKeyIntf; }, set: function (valueObject) { Object.keys(valueObject).forEach(function (key) { self.setStyleProperty(key, valueObject[key], true); }); self.draw(true); self.dispatchEvent('stylechanged', {name: 'style', value: valueObject}); } }); Object.defineProperty(self.intf, 'attributes', { value: {}}); Object.keys(self.attributes).forEach(function (key) { Object.defineProperty(self.intf.attributes, key, { get: function () { return self.attributes[key]; }, set: function (value) { self.attributes[key] = value; if (key === 'name') { self.reloadStoredValues(); self.tryLoadStoredOrders(); } self.draw(true); self.dispatchEvent('attributechanged', {name: key, value: value[key]}); } }); }); self.filters.string = function (value, filterFor) { value = String(value); var filterRegExp, regEnd = /\/(i|g|m)*$/, pattern = regEnd.exec(filterFor), flags = pattern ? pattern[0].substring(1) : '', flagLength = flags.length; self.invalidFilterRegEx = undefined; if (filterFor.substring(0, 1) === '/' && pattern) { try { filterRegExp = new RegExp(filterFor.substring(1, filterFor.length - (flagLength + 1)), flags); } catch (e) { self.invalidFilterRegEx = e; return; } return filterRegExp.test(value); } return value.toString ? value.toString().toLocaleUpperCase() .indexOf(filterFor.toLocaleUpperCase()) !== -1 : false; }; self.filters.number = function (value, filterFor) { if (!filterFor) { return true; } return value === filterFor; }; ['formatters', 'filters', 'sorters'].forEach(self.initProp); self.reloadStoredValues(); if (self.args.data) { self.intf.data = self.args.data; } if (!self.data) { self.intf.data = []; } if (self.args.schema) { self.intf.schema = self.args.schema; } if (self.isChildGrid) { requestAnimationFrame(function () { self.resize(true); }); } else { self.resize(true); } return self; }; /** * Removes focus from the grid. * @memberof canvasDatagrid * @name blur * @method */ self.intf.blur = function (e) { self.hasFocus = false; }; /** * Focuses on the grid. * @memberof canvasDatagrid * @name focus * @method */ self.intf.focus = function () { self.hasFocus = true; self.controlInput.focus(); }; if (self.shadowRoot || self.isChildGrid) { Object.defineProperty(self.intf, 'height', { get: function () { if (self.shadowRoot) { return self.shadowRoot.height; } return self.parentNode.height; }, set: function (value) { if (self.shadowRoot) { self.shadowRoot.height = value; } else { self.parentNode.height = value; } self.resize(true); } }); Object.defineProperty(self.intf, 'width', { get: function () { if (self.shadowRoot) { return self.shadowRoot.width; } return self.parentNode.width; }, set: function (value) { if (self.shadowRoot) { self.shadowRoot.width = value; } else { self.parentNode.width = value; } self.resize(true); } }); Object.defineProperty(self.intf, 'parentNode', { get: function () { return self.parentNode; }, set: function (value) { if (!self.isChildGrid) { throw new TypeError('Cannot set property parentNode which has only a getter'); } self.parentNode = value; } }); } Object.defineProperty(self.intf, 'visibleRowHeights', { get: function () { return self.visibleRowHeights; } }); Object.defineProperty(self.intf, 'openChildren', { get: function () { return self.openChildren; } }); Object.defineProperty(self.intf, 'childGrids', { get: function () { return Object.keys(self.childGrids).map(function (gridId) { return self.childGrids[gridId]; }); } }); Object.defineProperty(self.intf, 'isChildGrid', { get: function () { return self.isChildGrid; } }); Object.defineProperty(self, 'cursor', { get: function () { return self.parentNodeStyle.cursor; }, set: function (value) { if (value === 'cell') { value = 'default'; } if (self.currentCursor !== value) { self.parentNodeStyle.cursor = value; self.currentCursor = value; } } }); Object.defineProperty(self.intf, 'scrollHeight', { get: function () { return self.scrollBox.scrollHeight; } }); Object.defineProperty(self.intf, 'scrollWidth', { get: function () { return self.scrollBox.scrollWidth; } }); Object.defineProperty(self.intf, 'scrollTop', { get: function () { return self.scrollBox.scrollTop; }, set: function (value) { self.scrollBox.scrollTop = value; } }); Object.defineProperty(self.intf, 'scrollLeft', { get: function () { return self.scrollBox.scrollLeft; }, set: function (value) { self.scrollBox.scrollLeft = value; } }); Object.defineProperty(self.intf, 'sizes', { get: function () { return self.sizes; } }); Object.defineProperty(self.intf, 'input', { get: function () { return self.input; } }); Object.defineProperty(self.intf, 'controlInput', { get: function () { return self.controlInput; } }); Object.defineProperty(self.intf, 'currentCell', { get: function () { return self.currentCell; } }); Object.defineProperty(self.intf, 'visibleCells', { get: function () { return self.visibleCells; } }); Object.defineProperty(self.intf, 'visibleRows', { get: function () { return self.visibleRows; } }); Object.defineProperty(self.intf, 'selections', { get: function () { return self.selections; } }); Object.defineProperty(self.intf, 'dragMode', { get: function () { return self.dragMode; } }); Object.defineProperty(self.intf, 'changes', { get: function () { return self.changes; } }); self.intf.formatters = self.formatters; self.normalizeDataset = function (data, callback) { var i, d, max, syncFnInvoked; if (data === null || data === '' || data === undefined) { return callback([]); } if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') { data = [{'0': data}]; } if (!Array.isArray(data) && typeof data === 'object') { data = [data]; } if ((!Array.isArray(data[0]) && typeof data[0] === 'object' && data[0] !== null) || (Array.isArray(data) && data.length === 0)) { return callback(data); } if (typeof data === 'function') { i = data.apply(self.intf, [function (d) { if (syncFnInvoked) { console.warn('Detected a callback to the data setter function after the same function already returned a value synchronously.'); } self.normalizeDataset(d, callback); }]); if (i) { syncFnInvoked = true; self.normalizeDataset(i, callback); } return; } if (!Array.isArray(data) && typeof data === 'object') { data = [data]; } if (Array.isArray(data)) { if (!Array.isArray(data[0])) { //array of something? throw it all into 1 row! data = [data]; } // find the longest length max = 0; d = []; data.forEach(function (row) { max = Math.max(max, row.length); }); // map against length indexes data.forEach(function (row, index) { var x; d[index] = {}; for (x = 0; x < max; x += 1) { d[index][x] = row[x]; } }); return callback(d); } throw new Error('Unsupported data type. Must be an array of arrays or an array of objects, function or string.'); }; self.eventNames.forEach(function (eventName) { Object.defineProperty(self.intf, 'on' + eventName, { get: function () { return self.componentL1Events[eventName]; }, set: function (value) { self.events[eventName] = []; self.componentL1Events[eventName] = value; if (!value) { return; } self.addEventListener(eventName, value); } }); }); Object.defineProperty(self.intf, 'frozenRow', { get: function () { return self.frozenRow; }, set: function (val) { if (isNaN(val)) { throw new TypeError('Expected value for frozenRow to be a number.'); } if (self.visibleRows.length > val) { throw new RangeError('Cannot set a value larger than the number of visible rows.'); } self.frozenRow = val; } }); Object.defineProperty(self.intf, 'frozenColumn', { get: function () { return self.frozenColumn; }, set: function (val) { if (isNaN(val)) { throw new TypeError('Expected value for frozenRow to be a number.'); } if (self.getVisibleSchema().length > val) { throw new RangeError('Cannot set a value larger than the number of visible columns.'); } self.frozenColumn = val; } }); Object.defineProperty(self.intf, 'scrollIndexRect', { get: function () { return { top: self.scrollIndexTop, right: self.scrollIndexRight, bottom: self.scrollIndexBottom, left: self.scrollIndexLeft }; } }); Object.defineProperty(self.intf, 'scrollPixelRect', { get: function () { return { top: self.scrollPixelTop, right: self.scrollPixelRight, bottom: self.scrollPixelBottom, left: self.scrollPixelLeft }; } }); Object.defineProperty(self.intf, 'selectionBounds', { get: function () { return self.getSelectionBounds(); } }); Object.defineProperty(self.intf, 'selectedRows', { get: function () { return self.getSelectedData(true); } }); Object.defineProperty(self.intf, 'selectedCells', { get: function () { return self.getSelectedData(); } }); Object.defineProperty(self.intf, 'visibleSchema', { get: function () { return self.getVisibleSchema().map(function eachDataRow(col) { return col; }); } }); Object.defineProperty(self.intf, 'ctx', { get: function () { return self.ctx; } }); Object.defineProperty(self.intf, 'schema', { get: function schemaGetter() { return self.getSchema(); }, set: function schemaSetter(value) { if (!Array.isArray(value) || typeof value[0] !== 'object') { throw new Error('Schema must be an array of objects.'); } if (value[0].name === undefined) { throw new Error('Expected schema to contain an object with at least a name property.'); } self.schema = value.map(function eachSchemaColumn(column, index) { column.width = column.width || self.style.cellWidth; column[self.uniqueId] = self.getSchemaNameHash(column.name); column.filter = column.filter || self.filter(column.type); column.type = column.type || 'string'; column.index = index; column.columnIndex = index; column.rowIndex = -1; if (self.storedSettings && self.storedSettings.visibility[column.name] !== undefined) { column.hidden = !self.storedSettings.visibility[column.name]; } return column; }); self.tempSchema = undefined; self.createNewRowData(); self.createColumnOrders(); self.tryLoadStoredOrders(); self.resize(true); self.dispatchEvent('schemachanged', {schema: self.schema}); } }); Object.defineProperty(self.intf, 'data', { get: function dataGetter() { return self.data.map(function (row) { delete row[self.uniqueId]; return row; }); }, set: function dataSetter(value) { self.normalizeDataset(value, function (d) { self.originalData = d.map(function eachDataRow(row) { row[self.uniqueId] = self.uId; self.uId += 1; return row; }); self.changes = []; //TODO apply filter to incoming dataset self.data = self.originalData; if (!self.schema) { self.tempSchema = self.getSchemaFromData(); } if (!self.schema && self.data.length === 0) { self.tempSchema = [{name: ''}]; self.tempSchema[0][self.uniqueId] = self.getSchemaNameHash(''); } if (self.tempSchema && !self.schema) { self.createColumnOrders(); self.tryLoadStoredOrders(); self.dispatchEvent('schemachanged', {schema: self.tempSchema}); } self.createNewRowData(); if (self.attributes.autoResizeColumns && self.data.length > 0 && self.storedSettings === undefined) { self.autosize(); } self.fitColumnToValues('cornerCell', true); self.resize(true); self.createRowOrders(); self.tryLoadStoredOrders(); self.dispatchEvent('datachanged', {data: self.data}); }); } }); self.initScrollBox = function () { var sHeight = 0, sWidth = 0, scrollTop = 0, scrollLeft = 0, scrollHeight = 0, scrollWidth = 0, scrollBoxHeight = 20, scrollBoxWidth = 20; function setScrollTop(value, preventScrollEvent) { if (isNaN(value)) { throw new Error('ScrollTop value must be a number'); } if (value < 0) { value = 0; } if (value > scrollHeight) { value = scrollHeight; } if (scrollHeight < 0) { value = 0; } scrollTop = value; if (!preventScrollEvent) { self.scroll(); } } function setScrollLeft(value, preventScrollEvent) { if (isNaN(value)) { throw new Error('ScrollLeft value must be a number'); } if (value < 0) { value = 0; } if (value > scrollWidth) { value = scrollWidth; } if (scrollWidth < 0) { value = 0; } scrollLeft = value; if (!preventScrollEvent) { self.scroll(); } } self.scrollBox.toString = function () { return '{"width": ' + scrollWidth + ', "height": ' + scrollHeight + ', "left": ' + scrollLeft + ', "top": ' + scrollTop + '}'; }; self.scrollBox.scrollTo = function (x, y, supressDrawEvent) { setScrollLeft(x, true); setScrollTop(y, supressDrawEvent); }; Object.defineProperty(self.scrollBox, 'scrollBoxHeight', { get: function () { return scrollBoxHeight; }, set: function (value) { scrollBoxHeight = value; } }); Object.defineProperty(self.scrollBox, 'scrollBoxWidth', { get: function () { return scrollBoxWidth; }, set: function (value) { scrollBoxWidth = value; } }); Object.defineProperty(self.scrollBox, 'height', { get: function () { return sHeight; }, set: function (value) { if (scrollHeight < value) { scrollTop = 0; } sHeight = value; } }); Object.defineProperty(self.scrollBox, 'width', { get: function () { return sWidth; }, set: function (value) { sWidth = value; } }); Object.defineProperty(self.scrollBox, 'scrollTop', { get: function () { return scrollTop; }, set: setScrollTop }); Object.defineProperty(self.scrollBox, 'scrollLeft', { get: function () { return scrollLeft; }, set: setScrollLeft }); Object.defineProperty(self.scrollBox, 'scrollHeight', { get: function () { return scrollHeight; }, set: function (value) { if (scrollTop > value) { scrollTop = Math.max(value, 0); } if (scrollHeight < sHeight) { scrollTop = 0; } scrollHeight = value; } }); Object.defineProperty(self.scrollBox, 'scrollWidth', { get: function () { return scrollWidth; }, set: function (value) { if (scrollLeft > value) { scrollLeft = Math.max(value, 0); } scrollWidth = value; } }); }; return; }; });