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,003 lines • 68.3 kB
JavaScript
/*jslint browser: true, unparam: true, todo: true*/
/*globals XMLSerializer: false, define: true, Blob: false, MutationObserver: false, requestAnimationFrame: false, performance: false, btoa: false*/
define([], function () {
'use strict';
return function (self) {
var perfCounters = [],
drawCount = 0,
perfWindowSize = 300,
entityCount = [],
scrollDebugCounters = [],
touchPPSCounters = [];
self.htmlImageCache = {};
function drawPerfLine(w, h, x, y, perfArr, arrIndex, max, color, useAbs) {
var i = w / perfArr.length,
r = h / max;
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.beginPath();
self.ctx.moveTo(x, y + h);
perfArr.forEach(function (n) {
var val = (arrIndex === undefined ? n : n[arrIndex]),
cx,
cy;
if (useAbs) {
val = Math.abs(val);
}
cx = x + i;
cy = y + h - (val * r);
self.ctx.lineTo(cx, cy);
x += i;
});
self.ctx.moveTo(x + w, y + h);
self.ctx.strokeStyle = color;
self.ctx.stroke();
}
function drawOnAllImagesLoaded() {
var loaded = true;
Object.keys(self.htmlImageCache).forEach(function (html) {
if (!self.htmlImageCache[html].complete) {
loaded = false;
}
});
if (loaded) {
self.draw();
}
}
function drawHtml(cell) {
var img,
v = cell.innerHTML || cell.formattedValue,
x = cell.x + self.canvasOffsetLeft,
y = cell.y + self.canvasOffsetTop;
if (self.htmlImageCache[v]) {
img = self.htmlImageCache[v];
if (img.height !== cell.height || img.width !== cell.width) {
// height and width of the cell has changed, invalidate cache
self.htmlImageCache[v] = undefined;
} else {
if (!img.complete) {
return;
}
return self.ctx.drawImage(img, x, y);
}
}
img = new Image(cell.width, cell.height);
self.htmlImageCache[v] = img;
img.onload = function () {
self.ctx.drawImage(img, x, y);
drawOnAllImagesLoaded();
};
img.src = 'data:image/svg+xml;base64,' + btoa(
'<svg xmlns="http://www.w3.org/2000/svg" width="' + cell.width + '" height="' + cell.height + '">\n' +
'<foreignObject class="node" x="0" y="0" width="100%" height="100%">\n' +
'<body xmlns="http://www.w3.org/1999/xhtml" style="margin:0;padding:0;">\n' +
v + '\n' +
'</body>' +
'</foreignObject>\n' +
'</svg>\n'
);
}
function drawOrderByArrow(x, y) {
var mt = self.style.columnHeaderOrderByArrowMarginTop * self.scale,
ml = self.style.columnHeaderOrderByArrowMarginLeft * self.scale,
mr = self.style.columnHeaderOrderByArrowMarginRight * self.scale,
aw = self.style.columnHeaderOrderByArrowWidth * self.scale,
ah = self.style.columnHeaderOrderByArrowHeight * self.scale;
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.fillStyle = self.style.columnHeaderOrderByArrowColor;
self.ctx.strokeStyle = self.style.columnHeaderOrderByArrowBorderColor;
self.ctx.beginPath();
x = x + ml;
y = y + mt;
if (self.orderDirection === 'asc') {
self.ctx.moveTo(x, y);
self.ctx.lineTo(x + aw, y);
self.ctx.lineTo(x + (aw * 0.5), y + ah);
self.ctx.moveTo(x, y);
} else {
self.ctx.lineTo(x, y + ah);
self.ctx.lineTo(x + aw, y + ah);
self.ctx.lineTo(x + (aw * 0.5), y);
self.ctx.lineTo(x, y + ah);
}
self.ctx.stroke();
self.ctx.fill();
return ml + aw + mr;
}
function drawTreeArrow(cell, x, y) {
var mt = self.style.treeArrowMarginTop * self.scale,
mr = self.style.treeArrowMarginRight * self.scale,
ml = self.style.treeArrowMarginLeft * self.scale,
aw = self.style.treeArrowWidth * self.scale,
ah = self.style.treeArrowHeight * self.scale;
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.fillStyle = self.style.treeArrowColor;
self.ctx.strokeStyle = self.style.treeArrowBorderColor;
self.ctx.beginPath();
x = x + ml;
y = y + mt;
if (self.openChildren[cell.data[self.uniqueId]]) {
self.ctx.moveTo(x, y);
self.ctx.lineTo(x + aw, y);
self.ctx.lineTo(x + (aw * 0.5), y + ah);
self.ctx.moveTo(x, y);
} else {
self.ctx.lineTo(x, y);
self.ctx.lineTo(x + ah, y + (aw * 0.5));
self.ctx.lineTo(x, y + aw);
self.ctx.lineTo(x, y);
}
self.ctx.stroke();
self.ctx.fill();
return ml + aw + mr;
}
function radiusRect(x, y, w, h, radius) {
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
var r = x + w, b = y + h;
self.ctx.beginPath();
self.ctx.moveTo(x + radius, y);
self.ctx.lineTo(r - radius, y);
self.ctx.quadraticCurveTo(r, y, r, y + radius);
self.ctx.lineTo(r, y + h - radius);
self.ctx.quadraticCurveTo(r, b, r - radius, b);
self.ctx.lineTo(x + radius, b);
self.ctx.quadraticCurveTo(x, b, x, b - radius);
self.ctx.lineTo(x, y + radius);
self.ctx.quadraticCurveTo(x, y, x + radius, y);
}
function fillRect(x, y, w, h) {
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.fillRect(x, y, w, h);
}
function strokeRect(x, y, w, h) {
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.strokeRect(x, y, w, h);
}
function fillText(text, x, y) {
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.fillText(text, x, y);
}
function fillCircle(x, y, r) {
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.beginPath();
self.ctx.arc(x, y, r, 0, 2 * Math.PI);
self.ctx.fill();
}
function strokeCircle(x, y, r) {
x += self.canvasOffsetLeft;
y += self.canvasOffsetTop;
self.ctx.beginPath();
self.ctx.arc(x, y, r, 0, 2 * Math.PI);
self.ctx.stroke();
}
function clipFrozenArea(mode) {
// 0 both, 1 rows, 2 cols
// self.lastFrozenColumnPixel;
// self.lastFrozenRowPixel;
self.ctx.beginPath();
if (mode === 0) {
self.ctx.moveTo(self.lastFrozenColumnPixel, self.lastFrozenRowPixel);
self.ctx.lineTo(self.lastFrozenColumnPixel, self.height);
self.ctx.lineTo(self.width, self.height);
self.ctx.lineTo(self.width, self.lastFrozenRowPixel);
}
if (mode === 1) {
self.ctx.moveTo(0, self.lastFrozenRowPixel);
self.ctx.lineTo(0, self.height);
self.ctx.lineTo(self.width, self.height);
self.ctx.lineTo(self.width, self.lastFrozenRowPixel);
}
if (mode === 2) {
self.ctx.moveTo(self.lastFrozenColumnPixel, 0);
self.ctx.lineTo(self.width, 0);
self.ctx.lineTo(self.width, self.height);
self.ctx.lineTo(self.lastFrozenColumnPixel, self.height);
}
self.ctx.clip();
}
function fillHandle(x, y, r) {
if (self.style.selectionHandleType === 'circle') {
return fillCircle(x, y, r * 0.5);
}
fillRect(x - r * 0.5, y - r * 0.5, r, r);
}
function strokeHandle(x, y, r) {
if (self.style.selectionHandleType === 'circle') {
return strokeCircle(x, y, r * 0.5);
}
strokeRect(x - r * 0.5, y - r * 0.5, r, r);
}
function addselectionHandle(c, pos) {
var hw = self.style.selectionHandleSize,
p = {
tr: function () {
fillHandle(c.x + c.width, c.y, hw);
strokeHandle(c.x + c.width, c.y, hw);
},
br: function () {
fillHandle(c.x + c.width, c.y + c.height, hw);
strokeHandle(c.x + c.width, c.y + c.height, hw);
},
tl: function () {
fillHandle(c.x, c.y, hw);
strokeHandle(c.x, c.y, hw);
},
bl: function () {
fillHandle(c.x, c.y + c.height, hw);
strokeHandle(c.x, c.y + c.height, hw);
}
};
p[pos]();
}
function addBorderLine(c, pos) {
self.ctx.beginPath();
var p = {
t: function () {
self.ctx.moveTo(c.x + self.canvasOffsetLeft, c.y + self.canvasOffsetTop);
self.ctx.lineTo(c.x + self.canvasOffsetLeft + c.width, c.y + self.canvasOffsetTop);
},
r: function () {
self.ctx.moveTo(c.x + self.canvasOffsetLeft + c.width, c.y + self.canvasOffsetTop);
self.ctx.lineTo(c.x + self.canvasOffsetLeft + c.width, c.y + self.canvasOffsetTop + c.height);
},
b: function () {
self.ctx.moveTo(c.x + self.canvasOffsetLeft, c.y + self.canvasOffsetTop + c.height);
self.ctx.lineTo(c.x + self.canvasOffsetLeft + c.width, c.y + self.canvasOffsetTop + c.height);
},
l: function () {
self.ctx.moveTo(c.x + self.canvasOffsetLeft, c.y + self.canvasOffsetTop);
self.ctx.lineTo(c.x + self.canvasOffsetLeft, c.y + self.canvasOffsetTop + c.height);
}
};
p[pos]();
self.ctx.stroke();
}
function addEllipsis(text, width) {
var c, w = 0;
if (self.ellipsisCache[text] && self.ellipsisCache[text][width]) {
return self.ellipsisCache[text][width];
}
//TODO Add ellipsis back when there is a fast way to do it
w = self.ctx.measureText(text).width;
self.ellipsisCache[text] = self.ellipsisCache[text] || {};
c = {value: text, width: w};
self.ellipsisCache[text][width] = c;
return c;
}
function drawText(cell) {
var paddingLeft = self.style[cell.style + 'PaddingLeft'] * self.scale,
paddingTop = self.style[cell.style + 'PaddingTop'] * self.scale,
paddingRight = self.style[cell.style + 'PaddingRight'] * self.scale,
paddingBottom = self.style[cell.style + 'PaddingBottom'] * self.scale,
vPos = paddingTop + cell.height - (cell.height * 0.5),
hPos = paddingLeft + cell.treeArrowWidth + cell.orderByArrowWidth;
cell.text = addEllipsis(cell.formattedValue, cell.width - paddingRight - paddingLeft);
cell.text.height = cell.fontHeight;
if (cell.horizontalAlignment === 'right') {
hPos = cell.width - cell.text.width - paddingRight;
} else if (cell.horizontalAlignment === 'center') {
hPos = (cell.width / 2) - (cell.text.width / 2);
}
if (cell.verticalAlignment === 'top') {
vPos = paddingTop + cell.text.height;
} else if (cell.verticalAlignment === 'bottom') {
vPos = cell.height - paddingBottom - cell.text.height;
}
cell.text.x = cell.x + hPos;
cell.text.y = cell.y + vPos;
fillText(cell.text.value, cell.text.x, cell.text.y);
}
function getFrozenColumnsWidth() {
var w = 0,
s = self.getVisibleSchema(),
x = 0,
n = Math.min(self.frozenColumn, s.length),
column;
while (x < n) {
column = s[self.orders.columns[x]];
w += ((self.sizes.columns[column[self.uniqueId]] || column.width || self.style.cellWidth) * self.scale);
x += 1;
}
return w;
}
/**
* Redraws the grid. No matter what the change, this is the only method required to refresh everything.
* @memberof canvasDatagrid
* @name draw
* @method
*/
// r = literal row index
// rd = row data array
// i = user order index
// o = literal data index
// y = y drawing cursor
// x = x drawing cursor
// s = visible schema array
// cx = current x drawing cursor sub calculation var
// cy = current y drawing cursor sub calculation var
// a = static cell (like corner cell)
// p = perf counter
// l = data length
// u = current cell
// h = current height
// w = current width
self.draw = function (internal) {
if (self.dispatchEvent('beforedraw', {})) { return; }
if (!self.isChildGrid && (!self.height || !self.width)) {
return;
}
if (self.isChildGrid && internal) {
requestAnimationFrame(self.parentGrid.draw);
return;
}
if (self.intf.visible === false) {
return;
}
// initial values
var checkScrollHeight, rowHeaderCell, p, cx, cy, treeGrid, rowOpen,
rowHeight, cornerCell, y, x, c, h, w, s, r, rd, aCell,
bc = self.style.gridBorderCollapse === 'collapse',
selectionBorders = [],
moveBorders = [],
selectionHandles = [],
rowHeaders = [],
frozenColumnWidths = getFrozenColumnsWidth(),
l = self.data.length,
u = self.currentCell || {},
columnHeaderCellHeight = self.getColumnHeaderCellHeight(),
rowHeaderCellWidth = self.getRowHeaderCellWidth(),
cellHeight = self.style.cellHeight;
drawCount += 1;
p = performance.now();
self.visibleRowHeights = [];
// if data length has changed, there is no way to know
if (self.data.length > self.orders.rows.length) {
self.createRowOrders();
}
function drawScrollBars() {
var v = {
x: 0,
y: 0,
height: 0,
width: 0,
style: 'vertical-scroll-bar'
},
n = {
x: 0,
y: 0,
height: 0,
width: 0,
style: 'horizontal-scroll-bar'
},
vb = {
x: 0,
y: 0,
height: 0,
width: 0,
style: 'vertical-scroll-box'
},
nb = {
x: 0,
y: 0,
height: 0,
width: 0,
style: 'horizontal-scroll-box'
},
co = {
x: 0,
y: 0,
height: 0,
width: 0,
isCorner: true,
isScrollBoxCorner: true,
style: 'scroll-box-corner'
},
m = (self.style.scrollBarBoxMargin * 2),
d = self.style.scrollBarBoxMargin * 0.5;
self.ctx.strokeStyle = self.style.scrollBarBorderColor;
self.ctx.lineWidth = self.style.scrollBarBorderWidth;
// vertical
v.x += w - self.style.scrollBarWidth - self.style.scrollBarBorderWidth - d;
v.y += columnHeaderCellHeight;
v.width = self.style.scrollBarWidth + self.style.scrollBarBorderWidth + d;
v.height = h - columnHeaderCellHeight - self.style.scrollBarWidth - d - m;
self.ctx.fillStyle = self.style.scrollBarBackgroundColor;
fillRect(v.x, v.y, v.width, v.height + m);
strokeRect(v.x, v.y, v.width, v.height + m);
// vertical box
vb.x = v.x + self.style.scrollBarBoxMargin;
vb.y = columnHeaderCellHeight + self.style.scrollBarBoxMargin
+ ((v.height - self.scrollBox.scrollBoxHeight)
* (self.scrollBox.scrollTop / self.scrollBox.scrollHeight));
vb.width = self.style.scrollBarBoxWidth;
vb.height = self.scrollBox.scrollBoxHeight;
self.ctx.fillStyle = self.style.scrollBarBoxColor;
if (/vertical/.test(u.context)) {
self.ctx.fillStyle = self.style.scrollBarActiveColor;
}
if (vb.height < v.height) {
radiusRect(vb.x, vb.y, vb.width, vb.height, self.style.scrollBarBoxBorderRadius);
self.ctx.stroke();
self.ctx.fill();
}
// horizontal
n.x += rowHeaderCellWidth;
n.y += h - self.style.scrollBarWidth - d;
n.width = w - self.style.scrollBarWidth - rowHeaderCellWidth - d - m;
n.height = self.style.scrollBarWidth + self.style.scrollBarBorderWidth + d;
self.ctx.fillStyle = self.style.scrollBarBackgroundColor;
fillRect(n.x, n.y, n.width + m, n.height);
strokeRect(n.x, n.y, n.width + m, n.height);
// horizontal box
nb.y = n.y + self.style.scrollBarBoxMargin;
nb.x = rowHeaderCellWidth + self.style.scrollBarBoxMargin
+ ((n.width - self.scrollBox.scrollBoxWidth)
* (self.scrollBox.scrollLeft / self.scrollBox.scrollWidth));
nb.width = self.scrollBox.scrollBoxWidth;
nb.height = self.style.scrollBarBoxWidth;
self.ctx.fillStyle = self.style.scrollBarBoxColor;
if (/horizontal/.test(u.context)) {
self.ctx.fillStyle = self.style.scrollBarActiveColor;
}
if (nb.width < n.width) {
radiusRect(nb.x, nb.y, nb.width, nb.height, self.style.scrollBarBoxBorderRadius);
self.ctx.stroke();
self.ctx.fill();
}
//corner
self.ctx.strokeStyle = self.style.scrollBarCornerBorderColor;
self.ctx.fillStyle = self.style.scrollBarCornerBackgroundColor;
co.x = n.x + n.width + m;
co.y = v.y + v.height + m;
co.width = self.style.scrollBarWidth + self.style.scrollBarBorderWidth;
co.height = self.style.scrollBarWidth + self.style.scrollBarBorderWidth;
radiusRect(co.x, co.y, co.width, co.height, 0);
self.ctx.stroke();
self.ctx.fill();
self.visibleCells.unshift(v);
self.visibleCells.unshift(vb);
self.visibleCells.unshift(n);
self.visibleCells.unshift(nb);
self.visibleCells.unshift(co);
self.scrollBox.bar = {
v: v,
h: n
};
self.scrollBox.box = {
v: vb,
h: nb
};
}
function createHandlesOverlayArray(cell) {
if (self.attributes.allowMovingSelection || self.mobile) {
if (cell.selectionBorderTop && cell.selectionBorderRight && self.mobile) {
selectionHandles.push([cell, 'tr']);
cell.selectionHandle = 'tr';
}
if (cell.selectionBorderTop && cell.selectionBorderLeft && self.mobile) {
selectionHandles.push([cell, 'tl']);
cell.selectionHandle = 'tl';
}
if (cell.selectionBorderBottom && cell.selectionBorderLeft && self.mobile) {
selectionHandles.push([cell, 'bl']);
cell.selectionHandle = 'bl';
}
if (cell.selectionBorderBottom && cell.selectionBorderRight
&& (self.attributes.selectionHandleBehavior !== 'none' || self.mobile)) {
selectionHandles.push([cell, 'br']);
cell.selectionHandle = 'br';
}
}
}
function createBorderOverlayArray(cell, drawArray, propPrefix, offsetPoint) {
offsetPoint = offsetPoint || {x: 0, y: 0};
cell.selectionBorder = '';
if (!cell.isRowHeader
&& self.selections[cell.rowIndex + -offsetPoint.y]
&& self.selections[cell.rowIndex + -offsetPoint.y].indexOf(cell.columnIndex + -offsetPoint.x) !== -1) {
if ((!self.selections[cell.rowIndex - 1 + -offsetPoint.y]
|| self.selections[cell.rowIndex - 1 + -offsetPoint.y].indexOf(cell.columnIndex + -offsetPoint.x) === -1
|| cell.rowIndex === 0)
&& !cell.isHeader) {
drawArray.push([cell, 't']);
cell[propPrefix + 'BorderTop'] = true;
cell[propPrefix + 'Border'] += 't';
}
if (!self.selections[cell.rowIndex + 1 + -offsetPoint.y]
|| self.selections[cell.rowIndex + 1 + -offsetPoint.y].indexOf(cell.columnIndex + -offsetPoint.x) === -1) {
drawArray.push([cell, 'b']);
cell[propPrefix + 'BorderBottom'] = true;
cell[propPrefix + 'Border'] += 'b';
}
if (!self.selections[cell.rowIndex + -offsetPoint.y] || cell.columnIndex === 0
|| self.selections[cell.rowIndex + -offsetPoint.y].indexOf(cell.columnIndex - 1 + -offsetPoint.x) === -1) {
drawArray.push([cell, 'l']);
cell[propPrefix + 'BorderLeft'] = true;
cell[propPrefix + 'Border'] += 'l';
}
if (!self.selections[cell.rowIndex + -offsetPoint.y] || cell.columnIndex === s.length
|| self.selections[cell.rowIndex + -offsetPoint.y].indexOf(cell.columnIndex + 1 + -offsetPoint.x) === -1) {
drawArray.push([cell, 'r']);
cell[propPrefix + 'BorderRight'] = true;
cell[propPrefix + 'Border'] += 'r';
}
}
}
function drawCell(d, rowOrderIndex, rowIndex) {
return function drawEach(header, headerIndex, columnOrderIndex) {
var cellStyle = header.style || 'cell',
cellGridAttributes,
cell,
isHeader = /HeaderCell/.test(cellStyle),
isCorner = /cornerCell/.test(cellStyle),
isRowHeader = 'rowHeaderCell' === cellStyle,
isColumnHeader = 'columnHeaderCell' === cellStyle,
selected = self.selections[rowOrderIndex] && self.selections[rowOrderIndex].indexOf(columnOrderIndex) !== -1,
hovered = self.hovers[d[self.uniqueId]] && self.hovers[d[self.uniqueId]].indexOf(columnOrderIndex) !== -1,
active = self.activeCell.rowIndex === rowOrderIndex && self.activeCell.columnIndex === columnOrderIndex,
isGrid = typeof d[header.name] === 'object' && d[header.name] !== null && d[header.name] !== undefined,
activeHeader = (self.orders.rows[self.activeCell.rowIndex] === rowOrderIndex
|| self.orders.columns[self.activeCell.columnIndex] === columnOrderIndex)
&& (columnOrderIndex === -1 || rowOrderIndex === -1)
? (isRowHeader ? 'activeRowHeaderCell' : 'activeColumnHeaderCell') : false,
val,
f = self.formatters[header.type || 'string'],
orderByArrowSize = 0,
treeArrowSize = 0,
cellWidth = self.sizes.columns[isRowHeader ? 'cornerCell' : header[self.uniqueId]] || header.width,
ev = {
value: d[header.name],
row: d,
header: header
};
if (cellStyle === 'headerCellCap') {
cellWidth = w - x;
}
// if no data or schema are defined, a width is provided to the stub column
if (cellWidth === undefined) {
cellWidth = self.style.cellWidth;
}
cellWidth = cellWidth * self.scale;
if (x + cellWidth + self.style.cellBorderWidth < 0) {
x += cellWidth + self.style.cellBorderWidth;
}
if (active && cellStyle !== 'cornerCell') {
cellStyle = 'activeCell';
}
if (self.visibleRows.indexOf(rowIndex) === -1 && !isHeader) {
self.visibleRows.push(rowIndex);
}
val = self.dispatchEvent('formatcellvalue', ev);
cx = x;
cy = y;
if (cellStyle === 'cornerCell') {
cx = 0;
cy = 0;
} else if (isRowHeader) {
cx = 0;
} else if (isHeader) {
cy = 0;
}
cell = {
type: isGrid ? 'canvas-datagrid-cell' : header.type,
style: cellStyle,
nodeType: 'canvas-datagrid-cell',
x: cx,
y: cy,
horizontalAlignment: self.style[cellStyle + 'HorizontalAlignment'],
verticalAlignment: self.style[cellStyle + 'VerticalAlignment'],
offsetTop: self.canvasOffsetTop + cy,
offsetLeft: self.canvasOffsetLeft + cx,
scrollTop: self.scrollBox.scrollTop,
scrollLeft: self.scrollBox.scrollLeft,
active: active || activeHeader,
hovered: hovered,
selected: selected,
width: cellWidth,
height: cellHeight,
offsetWidth: cellWidth,
offsetHeight: cellHeight,
parentNode: self.intf.parentNode,
offsetParent: self.intf.parentNode,
data: d,
isCorner: isCorner,
isHeader: isHeader,
isColumnHeader: isColumnHeader,
isHeaderCellCap: !!header.isHeaderCellCap,
isRowHeader: isRowHeader,
rowOpen: rowOpen,
header: header,
columnIndex: columnOrderIndex,
rowIndex: rowOrderIndex,
sortColumnIndex: headerIndex,
sortRowIndex: rowIndex,
isGrid: isGrid,
isNormal: !isGrid && !isCorner && !isHeader,
gridId: (self.attributes.name || '') + d[self.uniqueId] + ':' + header[self.uniqueId],
parentGrid: self.intf,
innerHTML: '',
activeHeader: activeHeader,
value: isHeader && !isRowHeader ? (header.title || header.name) : d[header.name]
};
ev.cell = cell;
cell.userHeight = cell.isHeader ? self.sizes.rows[-1] : rowHeight;
cell.userWidth = cell.isHeader ? self.sizes.columns.cornerCell : self.sizes.columns[header[self.uniqueId]];
cell[self.uniqueId] = d[self.uniqueId];
self.visibleCells.unshift(cell);
if (self.dispatchEvent('beforerendercell', ev)) { return; }
self.ctx.fillStyle = self.style[cellStyle + 'BackgroundColor'];
self.ctx.strokeStyle = self.style[cellStyle + 'BorderColor'];
self.ctx.lineWidth = self.style[cellStyle + 'BorderWidth'];
if (hovered) {
self.ctx.fillStyle = self.style[cellStyle + 'HoverBackgroundColor'];
self.ctx.strokeStyle = self.style[cellStyle + 'HoverBorderColor'];
}
if (selected) {
self.ctx.fillStyle = self.style[cellStyle + 'SelectedBackgroundColor'];
self.ctx.strokeStyle = self.style[cellStyle + 'SelectedBorderColor'];
}
if (activeHeader) {
self.ctx.fillStyle = self.style[activeHeader + 'BackgroundColor'];
}
self.dispatchEvent('rendercell', ev);
if (cell.isGrid) {
if (cell.height !== rowHeight) {
cell.height = rowHeight || self.style.cellHeightWithChildGrid;
checkScrollHeight = true;
}
cell.width = self.sizes.columns[header[self.uniqueId]] || self.style.cellWidthWithChildGrid;
}
if (rowOpen && !cell.isRowHeader) {
cell.height = self.sizes.rows[rd[self.uniqueId]] || self.style.cellHeight;
}
if (!cell.isGrid) {
fillRect(cx, cy, cell.width, cell.height);
strokeRect(cx, cy, cell.width, cell.height);
}
self.ctx.save();
radiusRect(cell.x, cell.y, cell.width, cell.height, 0);
self.ctx.clip();
self.dispatchEvent('afterrendercell', ev);
if (cell.height !== cellHeight && !(rowOpen && !cell.isRowHeader)) {
self.sizes.rows[isHeader ? -1 : d[self.uniqueId]] = cell.height;
checkScrollHeight = true;
}
if (cell.width !== cellWidth) {
self.sizes.columns[header[self.uniqueId]] = cell.width;
checkScrollHeight = true;
}
if (isRowHeader && self.attributes.tree) {
if (!self.dispatchEvent('rendertreearrow', ev)) {
treeArrowSize = drawTreeArrow(cell, self.style[cellStyle + 'PaddingLeft'], cy, 0);
}
}
if ((self.attributes.showRowNumbers && isRowHeader)
|| !isRowHeader) {
if (cell.isGrid && !self.dispatchEvent('beforerendercellgrid', ev)) {
if (!self.childGrids[cell.gridId]) {
cellGridAttributes = self.args.cellGridAttributes || self.args;
cellGridAttributes.name = self.attributes.saveAppearance ? cell.gridId : undefined;
cellGridAttributes.component = false;
cellGridAttributes.parentNode = cell;
cellGridAttributes.data = d[header.name];
cellGridAttributes.style = cellGridAttributes.style || self.style;
ev.cellGridAttributes = cellGridAttributes;
if (self.dispatchEvent('beforecreatecellgrid', ev)) { return; }
self.childGrids[cell.gridId] = self.createGrid(cellGridAttributes);
self.sizes.rows[rd[self.uniqueId]]
= self.sizes.rows[rd[self.uniqueId]] || self.style.cellGridHeight;
checkScrollHeight = true;
}
cell.grid = self.childGrids[cell.gridId];
cell.grid.parentNode = cell;
cell.grid.visible = true;
cell.grid.draw();
self.dispatchEvent('rendercellgrid', ev);
} else if (!cell.isGrid) {
if (self.childGrids[cell.gridId]) {
self.childGrids[cell.gridId].parentNode.offsetHeight = 0;
}
if (isHeader && self.orderBy === header.name) {
if (!self.dispatchEvent('renderorderbyarrow', ev)) {
orderByArrowSize = drawOrderByArrow(cx + self.style[cellStyle + 'PaddingLeft'], 0);
}
}
self.ctx.fillStyle = self.style[cellStyle + 'Color'];
if (hovered) {
self.ctx.fillStyle = self.style[cellStyle + 'HoverColor'];
}
if (selected) {
self.ctx.fillStyle = self.style[cellStyle + 'SelectedColor'];
}
if (activeHeader) {
self.ctx.fillStyle = self.style[activeHeader + 'Color'];
}
self.ctx.font = (self.style[cellStyle + 'FontHeight'] * self.scale) + 'px ' + self.style[cellStyle + 'FontName'];
cell.fontHeight = (self.style[cellStyle + 'FontHeight'] * self.scale);
cell.treeArrowWidth = treeArrowSize;
cell.orderByArrowWidth = orderByArrowSize;
val = val !== undefined ? val : f
? f(ev) : '';
if (val === undefined && !f) {
val = '';
console.warn('canvas-datagrid: Unknown format '
+ header.type + ' add a cellFormater');
}
cell.formattedValue = ((val !== undefined && val !== null) ? val : '').toString();
if (self.columnFilters && self.columnFilters[val] !== undefined && isHeader) {
cell.formattedValue = self.attributes.filterTextPrefix + val;
}
if (!self.dispatchEvent('rendertext', ev)) {
if (cell.innerHTML || header.type === 'html') {
drawHtml(cell);
} else {
drawText(cell);
}
}
}
}
if (active) {
aCell = cell;
}
createBorderOverlayArray(cell, selectionBorders, 'selection');
// createBorderOverlayArray calculates data for createHandlesOverlayArray so it must go 2nd
createHandlesOverlayArray(cell);
if (self.movingSelection) {
createBorderOverlayArray(cell, moveBorders, 'move', self.moveOffset);
}
self.ctx.restore();
x += cell.width + (bc ? 0 : self.style.cellBorderWidth);
return cell.width;
};
}
function drawRowHeader(rowData, index, rowOrderIndex) {
var a;
if (self.attributes.showRowHeaders) {
x = 0;
rowHeaderCell = {'rowHeaderCell': index + 1 };
rowHeaderCell[self.uniqueId] = rowData[self.uniqueId];
a = {
name: 'rowHeaderCell',
width: self.style.rowHeaderCellWidth,
style: 'rowHeaderCell',
type: 'string',
data: rowData[self.uniqueId],
index: -1
};
a[self.uniqueId] = rowData[self.uniqueId];
rowOpen = self.openChildren[rowData[self.uniqueId]];
drawCell(rowHeaderCell, index, rowOrderIndex)(a, -1, -1);
}
}
function drawHeaders() {
var d, g = s.length, i, o, columnHeaderCell, header, nonFrozenHeaderWidth;
function drawHeaderColumnRange(start, end) {
end = Math.min(end, g);
for (o = start; o < end; o += 1) {
i = self.orders.columns[o];
header = s[i];
d = {
title: header.title,
name: header.name,
width: header.width || self.style.cellWidth,
style: 'columnHeaderCell',
type: 'string',
index: o,
order: i
};
columnHeaderCell = {'columnHeaderCell': header.title || header.name};
columnHeaderCell[self.uniqueId] = 'h' + header[self.uniqueId];
d[self.uniqueId] = header[self.uniqueId];
x += drawCell(columnHeaderCell, -1, -1)(d, o, i);
if (x > self.width + self.scrollBox.scrollLeft) {
break;
}
}
}
rowHeaders.forEach(function (rArgs, rhIndex) {
y = rArgs[3];
cellHeight = rArgs[4];
if (rhIndex === self.frozenRow) {
self.ctx.save();
radiusRect(0, self.lastFrozenRowPixel, self.width, self.height - self.lastFrozenRowPixel, 0);
self.ctx.clip();
}
drawRowHeader(rArgs[0], rArgs[1], rArgs[2]);
});
self.ctx.restore();
if (self.attributes.showColumnHeaders) {
x = -self.scrollBox.scrollLeft + self.scrollPixelLeft + self.style.columnHeaderCellBorderWidth;
if (self.attributes.showRowHeaders) {
x += rowHeaderCellWidth;
}
x += frozenColumnWidths;
y = 0;
// cell height might have changed during drawing
cellHeight = self.getColumnHeaderCellHeight();
drawHeaderColumnRange(self.scrollIndexLeft + self.frozenColumn, g);
nonFrozenHeaderWidth = x;
x = self.style.columnHeaderCellBorderWidth;
if (self.attributes.showRowHeaders) {
x += rowHeaderCellWidth;
}
drawHeaderColumnRange(0, self.frozenColumn);
// fill in the space right of the headers
x = nonFrozenHeaderWidth;
if (x < w) {
c = {
name: '',
width: self.style.scrollBarWidth,
style: 'headerCellCap',
isHeaderCell: true,
isHeaderCellCap: true,
type: 'string',
index: s.length
};
c[self.uniqueId] = 'columnHeaderCell';
drawCell({endCap: ''}, -1, -1)(c, -1, -1);
}
// fill in the space right of the headers
if (self.attributes.showRowHeaders) {
cornerCell = {'cornerCell': '' };
cornerCell[self.uniqueId] = 'cornerCell';
x = 0;
c = {
name: 'cornerCell',
width: self.style.rowHeaderCellWidth,
style: 'cornerCell',
type: 'string',
index: -1
};
c[self.uniqueId] = 'cornerCell';
drawCell(cornerCell, -1, -1)(c, -1, -1);
}
}
}
function drawRow(r, d) {
var i, treeHeight, rowSansTreeHeight, o, g = s.length;
if (y - (cellHeight * 2) > h) {
return false;
}
rd = self.data[r];
rowOpen = self.openChildren[rd[self.uniqueId]];
rowSansTreeHeight = (self.sizes.rows[rd[self.uniqueId]] || self.style.cellHeight) * self.scale;
treeHeight = (rowOpen ? self.sizes.trees[rd[self.uniqueId]] : 0) * self.scale;
rowHeight = (rowSansTreeHeight + treeHeight);
if (y < -rowHeight) {
return false;
}
if (self.attributes.showRowHeaders) {
x += rowHeaderCellWidth;
}
cellHeight = rowHeight;
if (self.attributes.allowFreezingColumns) {
x += frozenColumnWidths;
}
//draw normal columns
for (o = (self.scrollIndexLeft + self.frozenColumn); o < g; o += 1) {
i = self.orders.columns[o];
x += drawCell(rd, r, d)(s[i], i, o);
if (x > self.width) {
self.scrollIndexRight = o;
self.scrollPixelRight = x;
break;
}
}
//draw frozen columns
x = 0;
if (self.attributes.showRowHeaders) {
x += rowHeaderCellWidth;
}
for (o = 0; o < self.frozenColumn; o += 1) {
i = self.orders.columns[o];
x += drawCell(rd, r, d)(s[i], i, o);
if (x > self.width) {
break;
}
}
self.lastFrozenColumnPixel = x;
// cell height might have changed during drawing
cellHeight = rowHeight;
x = -self.scrollBox.scrollLeft + self.scrollPixelLeft + self.style.cellBorderWidth;
// don't draw a tree for the new row
treeGrid = self.childGrids[rd[self.uniqueId]];
if (r !== self.data.length && rowOpen) {
treeGrid.visible = true;
treeGrid.parentNode = {
offsetTop: y + rowSansTreeHeight + self.canvasOffsetTop,
offsetLeft: rowHeaderCellWidth - 1 + self.canvasOffsetLeft,
offsetHeight: treeHeight,
offsetWidth: self.width - rowHeaderCellWidth - self.style.scrollBarWidth - 1,
offsetParent: self.intf.parentNode,
parentNode: self.intf.parentNode,
style: self.style,
nodeType: 'canvas-datagrid-tree',
scrollTop: self.scrollBox.scrollTop,
scrollLeft: self.scrollBox.scrollLeft,
rowIndex: r
};
self.visibleCells.unshift({
rowIndex: r,
columnIndex: 0,
y: treeGrid.parentNode.offsetTop,
x: treeGrid.parentNode.offsetLeft,
height: treeGrid.height,
width: treeGrid.width,
style: 'tree-grid',
type: treeGrid.parentNode.nodeType
});
treeGrid.draw();
} else if (treeGrid) {
treeGrid.parentNode.offsetHeight = 0;
delete self.sizes.trees[rd[self.uniqueId]];
}
rowHeaders.push([rd, r, d, y, rowHeight]);
self.visibleRowHeights[r] = rowHeight;
y += cellHeight + (bc ? 0 : self.style.cellBorderWidth);
return true;
}
function initDraw() {
self.visibleRows = [];
s = self.getVisibleSchema();
self.visibleCells = [];
self.canvasOffsetTop = self.isChildGrid ? self.parentNode.offsetTop : 0;
self.canvasOffsetLeft = self.isChildGrid ? self.parentNode.offsetLeft : 0;
h = self.height;
w = self.width;
}
function drawBackground() {
radiusRect(0, 0, w, h, 0);
self.ctx.clip();
self.ctx.fillStyle = self.style.gridBackgroundColor;
fillRect(0, 0, w, h);
}
function drawFrozenRows() {
var n, ln = Math.min(self.data.length, self.frozenRow);
x = -self.scrollBox.scrollLeft + self.scrollPixelLeft + self.style.cellBorderWidth;
y = columnHeaderCellHeight;
for (r = 0; r < ln; r += 1) {
n = self.orders.rows[r];
if (!drawRow(n, r)) {
break;
}
}
if (self.attributes.allowFreezingRows) {
// HACK great, another stupid magic number.
// Background will appear as a 0.5px artifact behind the row freeze bar without this hack
y += self.style.frozenMarkerBorderWidth + self.style.frozenMarkerWidth - 0.4999999999;
}
self.lastFrozenRowPixel = y;
}
function drawRows() {
self.ctx.save();
if (self.frozenRow > 0) {
radiusRect(0, self.lastFrozenRowPixel, self.width, self.height - self.lastFrozenRowPixel, 0);
self.ctx.clip();
}
var o, n, i, g = s.length;
x = -self.scrollBox.scrollLeft + self.scrollPixelLeft + self.style.cellBorderWidth;
if (!self.attributes.snapToRow) {
y += -self.scrollBox.scrollTop + self.scrollPixelTop;
}
for (r = self.frozenRow + self.scrollIndexTop; r < l; r += 1) {
n = self.orders.rows[r];
if (!drawRow(n, r)) {
self.scrollIndexBottom = r;
self.scrollPixelBottom = y;
break;
}
}
if (self.attributes.showNewRow) {
if (self.attributes.showRowHeaders) {
x += rowHeaderCellWidth;
}
rowHeight = cellHeight = self.style.cellHeight;
rowOpen = false;
for (o = self.scrollIndexLeft; o < g; o +