Skip to content

Commit

Permalink
greatly improved performance by improving addEllipsis function, chang…
Browse files Browse the repository at this point in the history
…e onclick to click on context menu interface, renamed style headerRowColumn to rowHeaderColumn. Updated sample to use more rows, less bs code
  • Loading branch information
Tony Germaneri committed Dec 1, 2016
1 parent c381793 commit 9d55f2d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 75 deletions.
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
Canvas Data Grid
================

Lightweight canvas based data grid.
High performance lightweight canvas based data grid.
Support for 10^6+ rows and 100's of columns.
Extensible styling, filtering, formatting, resizing, selecting, and ordering.
Rich API of events, methods and properties optimized for CRUD, reporting and work flow applications.
Zero dependencies, very small code base, a single 73k (13k gziped) file.

[Demo](https://tonygermaneri.github.io/canvas-datagrid/sample/index.html)

Expand Down Expand Up @@ -465,7 +469,7 @@ You can add items to the context menu but they must conform to this object type.
| Property | Description |
|-----|------|
| title | The title that will appear in the context menu. If title is a `string` then the string will appear. If title is a `HTMLElement` then it will be appended via `appendChild()` to the context menu row maintaining any events and references. |
| onclick | Optional function to invoke when this context menu item is clicked. Neglecting to call `e.stopPropagation();` in your function will result in the mouse event bubbling up to the canvas undesirably.|
| click | Optional function to invoke when this context menu item is clicked. Neglecting to call `e.stopPropagation();` in your function will result in the mouse event bubbling up to the canvas undesirably.|

Removing all items from the list of menu items will cause the context menu to not appear.
Calling `e.preventDefault();` will cause the context menu to not appear as well.
Expand Down Expand Up @@ -655,7 +659,7 @@ A cell on the grid and all data associated with it.
| Property | Description |
|-----|------|
| type | Data type used by this cell as dictated by the column. |
| style | Visual style of cell. Can be any one of `cell`, `activeCell`, `headerCell`, `cornerCell`, or `headerRowCell`. Prefix of each style name. |
| style | Visual style of cell. Can be any one of `cell`, `activeCell`, `headerCell`, `cornerCell`, or `rowHeaderCell`. Prefix of each style name. |
| x | The x coordinate of this cell on the canvas. |
| y | The y coordinate of this cell on the canvas. |
| hovered | When true, this cell is hovered. |
Expand Down Expand Up @@ -767,16 +771,16 @@ Changing a style will automatically call `draw`.
| headerCellHoverColor | rgba(43, 48, 153, 1) |
| headerCellHoverBackgroundColor | rgba(181, 201, 223, 1) |
| headerRowWidth | 57 |
| headerRowCellPaddingTop | 5 |
| headerRowCellPaddingLeft | 5 |
| headerRowCellPaddingRight | 7 |
| headerRowCellHeight | 25 |
| headerRowCellBorderWidth | 0.5 |
| headerRowCellBorderColor | rgba(172, 175, 179, 1) |
| headerRowCellFont | 16px sans-serif |
| headerRowCellColor | rgba(50, 50, 50, 1) |
| headerRowCellBackgroundColor | rgba(222, 227, 233, 1) |
| headerRowCellHoverColor | rgba(43, 48, 153, 1) |
| headerRowCellHoverBackgroundColor | rgba(181, 201, 223, 1) |
| headerRowCellSelectedColor | rgba(43, 48, 153, 1) |
| headerRowCellSelectedBackgroundColor', 'rgba(182, 205, 250, 1)' |
| rowHeaderCellPaddingTop | 5 |
| rowHeaderCellPaddingLeft | 5 |
| rowHeaderCellPaddingRight | 7 |
| rowHeaderCellHeight | 25 |
| rowHeaderCellBorderWidth | 0.5 |
| rowHeaderCellBorderColor | rgba(172, 175, 179, 1) |
| rowHeaderCellFont | 16px sans-serif |
| rowHeaderCellColor | rgba(50, 50, 50, 1) |
| rowHeaderCellBackgroundColor | rgba(222, 227, 233, 1) |
| rowHeaderCellHoverColor | rgba(43, 48, 153, 1) |
| rowHeaderCellHoverBackgroundColor | rgba(181, 201, 223, 1) |
| rowHeaderCellSelectedColor | rgba(43, 48, 153, 1) |
| rowHeaderCellSelectedBackgroundColor', 'rgba(182, 205, 250, 1)' |
83 changes: 45 additions & 38 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,19 @@ define([], function context() {
['headerCellHoverColor', 'rgba(43, 48, 153, 1)'],
['headerCellHoverBackgroundColor', 'rgba(181, 201, 223, 1)'],
['headerRowWidth', 57],
['headerRowCellPaddingTop', 5],
['headerRowCellPaddingLeft', 5],
['headerRowCellPaddingRight', 7],
['headerRowCellHeight', 25],
['headerRowCellBorderWidth', 0.5],
['headerRowCellBorderColor', 'rgba(172, 175, 179, 1)'],
['headerRowCellFont', '16px sans-serif'],
['headerRowCellColor', 'rgba(50, 50, 50, 1)'],
['headerRowCellBackgroundColor', 'rgba(222, 227, 233, 1)'],
['headerRowCellHoverColor', 'rgba(43, 48, 153, 1)'],
['headerRowCellHoverBackgroundColor', 'rgba(181, 201, 223, 1)'],
['headerRowCellSelectedColor', 'rgba(43, 48, 153, 1)'],
['headerRowCellSelectedBackgroundColor', 'rgba(182, 205, 250, 1)']
['rowHeaderCellPaddingTop', 5],
['rowHeaderCellPaddingLeft', 5],
['rowHeaderCellPaddingRight', 7],
['rowHeaderCellHeight', 25],
['rowHeaderCellBorderWidth', 0.5],
['rowHeaderCellBorderColor', 'rgba(172, 175, 179, 1)'],
['rowHeaderCellFont', '16px sans-serif'],
['rowHeaderCellColor', 'rgba(50, 50, 50, 1)'],
['rowHeaderCellBackgroundColor', 'rgba(222, 227, 233, 1)'],
['rowHeaderCellHoverColor', 'rgba(43, 48, 153, 1)'],
['rowHeaderCellHoverBackgroundColor', 'rgba(181, 201, 223, 1)'],
['rowHeaderCellSelectedColor', 'rgba(43, 48, 153, 1)'],
['rowHeaderCellSelectedBackgroundColor', 'rgba(182, 205, 250, 1)']
],
input,
contextMenu,
Expand All @@ -137,6 +137,7 @@ define([], function context() {
filterBy = '',
filterValue = '',
filterTypeMap = {},
ellipsisCache = {},
container,
canvas,
height,
Expand Down Expand Up @@ -203,12 +204,16 @@ define([], function context() {
});
}
function addEllipsis(text, width) {
if (typeof text !== 'string') { return ''; }
if (ellipsisCache[text] && ellipsisCache[text][width]) {
return ellipsisCache[text][width];
}
var o = text, i = text.length;
while (width < ctx.measureText(o).width && i > 1) {
i -= 1;
o = text.substring(0, i) + "...";
}
ellipsisCache[text] = ellipsisCache[text] || {};
ellipsisCache[text][width] = o;
return o;
}
function addEventListener(ev, fn) {
Expand Down Expand Up @@ -236,7 +241,7 @@ define([], function context() {
cellFormaters.string = function cellFormatterString(ctx, cell) {
return cell.value !== undefined ? cell.value : '';
};
cellFormaters.headerRowCell = cellFormaters.string;
cellFormaters.rowHeaderCell = cellFormaters.string;
cellFormaters.headerCell = cellFormaters.string;
cellFormaters.number = cellFormaters.string;
cellFormaters.int = cellFormaters.string;
Expand Down Expand Up @@ -355,7 +360,7 @@ define([], function context() {
val,
f = cellFormaters[header.type || 'string'],
orderByArrowSize = 0,
cellWidth = sizes.columns[cellStyle === 'headerRowCell'
cellWidth = sizes.columns[cellStyle === 'rowHeaderCell'
? 'cornerCell' : header[uniqueId]] || header.width;
if (active) {
cellStyle = 'activeCell';
Expand All @@ -371,7 +376,7 @@ define([], function context() {
if (cellStyle === 'cornerCell') {
cx = 0;
cy = 0;
} else if (cellStyle === 'headerRowCell') {
} else if (cellStyle === 'rowHeaderCell') {
cx = 0;
} else if (cellStyle === 'headerCell') {
cy = 0;
Expand Down Expand Up @@ -415,8 +420,8 @@ define([], function context() {
if (cell.width !== cellWidth) {
sizes.columns[cell.columnIndex] = cell.width;
}
if ((attributes.showRowNumbers && cellStyle === 'headerRowCell')
|| cellStyle !== 'headerRowCell') {
if ((attributes.showRowNumbers && cellStyle === 'rowHeaderCell')
|| cellStyle !== 'rowHeaderCell') {
ctx.font = style[cellStyle + 'Font'];
val = val !== undefined ? val : f
? f(ctx, cell) : '';
Expand All @@ -443,7 +448,7 @@ define([], function context() {
if (header.name === filterBy && filterValue !== '' && cellStyle === 'headerCell') {
val = style.filterTextPrefix + val;
}
cell.formattedValue = val;
cell.formattedValue = (val || '').toString();
fire('rendertext', [ctx, cell], intf);
ctx.fillText(addEllipsis(cell.formattedValue, cell.width - style[cellStyle + 'PaddingRight'] - orderByArrowSize - style.autosizePadding),
orderByArrowSize + cx + style[cellStyle + 'PaddingLeft'],
Expand All @@ -458,12 +463,12 @@ define([], function context() {
var a;
if (attributes.showRowHeaders) {
x = 0;
rowHeaderCell = {'headerRowCell': index + 1 };
rowHeaderCell = {'rowHeaderCell': index + 1 };
rowHeaderCell[uniqueId] = rowData[uniqueId];
a = {
name: 'headerRowCell',
name: 'rowHeaderCell',
width: style.headerRowWidth,
style: 'headerRowCell',
style: 'rowHeaderCell',
type: 'string',
index: -1
};
Expand Down Expand Up @@ -548,6 +553,7 @@ define([], function context() {
}
scrollIndexTop = Math.max(scrollIndexTop - 1, 0);
scrollPixelTop = Math.max(scrollPixelTop - style.cellHeight, 0);
ellipsisCache = {};
draw();
if (input) {
input.style.top = scrollEdit.inputTop
Expand All @@ -567,10 +573,10 @@ define([], function context() {
function findColumnMaxTextLength(name) {
var m = -Infinity;
if (name === 'cornerCell') {
ctx.font = style.headerRowCellFont;
ctx.font = style.rowHeaderCellFont;
return ctx.measureText((data.length + (attributes.showNewRow ? 1 : 0)).toString()).width
+ style.headerRowCellPaddingRight
+ style.headerRowCellPaddingLeft;
+ style.rowHeaderCellPaddingRight
+ style.rowHeaderCellPaddingLeft;
}
(schema || tempSchema).forEach(function (col) {
if (col.name !== name) { return; }
Expand Down Expand Up @@ -660,8 +666,8 @@ define([], function context() {
&& ((attributes.allowColumnResizeFromCell && cell.style === 'cell')
|| cell.style !== 'cell')
&& ((attributes.allowRowHeaderResize
&& ['headerRowCell', 'cornerCell'].indexOf(cell.style) !== -1)
|| ['headerRowCell', 'cornerCell'].indexOf(cell.style) === -1)) {
&& ['rowHeaderCell', 'cornerCell'].indexOf(cell.style) !== -1)
|| ['rowHeaderCell', 'cornerCell'].indexOf(cell.style) === -1)) {
cell.context = 'ew-resize';
return cell;
}
Expand Down Expand Up @@ -806,7 +812,7 @@ define([], function context() {
if (filterValue) {
menuItems.push({
title: 'Remove Filter',
onclick: function removeFilterClick() {
click: function removeFilterClick() {
e.preventDefault();
setFilter();
disposeContextMenu();
Expand All @@ -820,7 +826,7 @@ define([], function context() {
&& Object.keys(sizes.columns).length > 0) {
menuItems.push({
title: 'Reset column and row sizes',
onclick: function (e) {
click: function (e) {
e.preventDefault();
sizes = {
rows: {},
Expand All @@ -836,7 +842,7 @@ define([], function context() {
if (attributes.allowColumnReordering) {
menuItems.push({
title: 'Order by ' + contextObject.header.name + ' ascending',
onclick: function (e) {
click: function (e) {
e.preventDefault();
order(contextObject.header.name, 'asc');
disposeContextMenu();
Expand All @@ -845,7 +851,7 @@ define([], function context() {
});
menuItems.push({
title: 'Order by ' + contextObject.header.name + ' descending',
onclick: function (e) {
click: function (e) {
e.preventDefault();
order(contextObject.header.name, 'desc');
disposeContextMenu();
Expand All @@ -866,9 +872,9 @@ define([], function context() {
} else {
row.appendChild(item.title);
}
if (item.onclick) {
if (item.click) {
row.addEventListener('click', function contextClickProxy(e) {
item.onclick.apply(this, [e, contextObject, disposeContextMenu]);
item.click.apply(this, [e, contextObject, disposeContextMenu]);
e.preventDefault();
e.stopPropagation();
controlInput.focus();
Expand Down Expand Up @@ -1116,12 +1122,12 @@ define([], function context() {
checkSelectionChange();
return;
}
if (['headerRowCell', 'headerCell'].indexOf(currentCell.style) === -1) {
if (['rowHeaderCell', 'headerCell'].indexOf(currentCell.style) === -1) {
setActiveCell(i.columnIndex, i.rowIndex);
}
selections[i.rowIndex] = selections[i.rowIndex] || [];
index = i.selected ? selections[i.rowIndex].indexOf(i.header.index) : -1;
if (attributes.rowSelectionMode || currentCell.style === 'headerRowCell') {
if (attributes.rowSelectionMode || currentCell.style === 'rowHeaderCell') {
if (selections[i.rowIndex].length === data.length) {
selections[i.rowIndex] = [];
selectionChanged = true;
Expand Down Expand Up @@ -1170,14 +1176,15 @@ define([], function context() {
}
if (fire('resizecolumn', [x, y, resizingItem], intf)) { return false; }
if (resizeMode === 'ew-resize') {
sizes.columns[resizingItem.header.style === 'headerRowCell'
sizes.columns[resizingItem.header.style === 'rowHeaderCell'
? 'cornerCell' : resizingItem.header[uniqueId]] = x;
return;
}
if (resizeMode === 'ns-resize') {
sizes.rows[resizingItem.data[uniqueId]] = y;
return;
}
ellipsisCache = {};
}
function stopDragResize() {
setScrollHeight();
Expand All @@ -1199,7 +1206,7 @@ define([], function context() {
}
if (['ns-resize', 'ew-resize'].indexOf(resizeMode) !== -1) {
resizingItem = resizeItem;
resizingStartingWidth = sizes.columns[resizingItem.header.style === 'headerRowCell'
resizingStartingWidth = sizes.columns[resizingItem.header.style === 'rowHeaderCell'
? 'cornerCell' : resizingItem.header[uniqueId]] || resizingItem.header.width;
resizingStartingHeight = sizes.rows[resizingItem.data[uniqueId]] || style.cellHeight;
document.body.addEventListener('mousemove', dragResizeColumn, false);
Expand Down
31 changes: 10 additions & 21 deletions sample/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
document.addEventListener('DOMContentLoaded', function () {
'use strict';
function createRandomSampleData() {
var rows = 99999, x, data = [], d, i, c,
var rows = Math.pow(10, 6), x, data = [], d, i, c,
r = 'Elend, eam, animal omittam an, has in, explicari principes. Elit, causae eleifend mea cu. No sed adipisci accusata, ei mea everti melius periculis. Ei quot audire pericula mea, qui ubique offendit no. Sint mazim mandamus duo ei. Sumo maiestatis id has, at animal reprehendunt definitionem cum, mei ne adhuc theophrastus.';
c = r.split(' ');
r = r.split(',');
c = r.split(' ').map(function (i) { return i.trim(); });
r = r.split(',').map(function (i) { return i.trim(); });
for (x = 0; x < rows; x += 1) {
d = {};
for (i = 0; i < r.length; i += 1) {
Expand All @@ -16,26 +16,12 @@ document.addEventListener('DOMContentLoaded', function () {
}
return data;
}
function createSampleData() {
var x,
data = [],
rows = 99999,
cols = ['Alpha', 'Beta', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf', 'Hotel', 'Indigo', 'Juliet', 'Kilo', 'Lima', 'Mike', 'November', 'Oscar', 'Pappa', 'Quebec', 'Romeo', 'Sierra', 'Tango', 'Uniform', 'Victor', 'Whiskey', 'X-Ray', 'Yak', 'Zulu'];
function addRow(col, index) {
data[x][col] = x + ':' + index;
}
for (x = 0; x < rows; x += 1) {
data[x] = {};
cols.forEach(addRow);
}
return data;
}
var parentNode,
grid,
sampleData = createSampleData(),
sampleData = createRandomSampleData(),
schema = Object.keys(sampleData[0]).map(function (col) {
return {
hidden: col === 'Delta',
hidden: col === 'Elit',
name: col,
defaultValue: function (header) {
return Date.now();
Expand All @@ -62,7 +48,7 @@ document.addEventListener('DOMContentLoaded', function () {
grid.schema = schema;
grid.addEventListener('rendercell', function (ctx, cell) {
if (cell.selected || cell.active) { return; }
if (cell.header.name === 'Beta' && cell.style !== 'headerCell') {
if (cell.header.name === 'Elit' && cell.style !== 'headerCell') {
ctx.fillStyle = 'lightgreen';
}
if (cell.rowIndex === 1 && cell.columnIndex === 0) {
Expand All @@ -71,6 +57,9 @@ document.addEventListener('DOMContentLoaded', function () {
if (cell.rowIndex === 1 && cell.columnIndex === 1) {
ctx.strokeStyle = 'red';
}
if (cell.value === 'Elend') {
ctx.fillStyle = 'red';
}
});
grid.addEventListener('click', function (e, cell, menuItems, menuElement) {
grid.data[0].Alpha = 'Woah! ' + cell.value;
Expand All @@ -82,7 +71,7 @@ document.addEventListener('DOMContentLoaded', function () {
grid.addEventListener('contextmenu', function (e, cell, menuItems, contextMenu) {
menuItems.push({
title: 'Check out ' + cell.value,
onclick: function (e) {
click: function (e) {
alert('Yup, it\'s ' + cell.value);
}
});
Expand Down

0 comments on commit 9d55f2d

Please sign in to comment.