@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
331 lines (295 loc) • 6.66 kB
JavaScript
function generateHeader(index) {
const asciiFirstLetter = 65;
const lettersCount = 26;
let div = index + 1;
let label = '';
let pos;
while (div > 0) {
pos = (div - 1) % lettersCount;
label = String.fromCharCode(asciiFirstLetter + pos) + label;
div = parseInt(((div - pos) / lettersCount).toString(), 10);
}
return label.toLowerCase();
}
/**
* Custom sorting apply
*/
function naturalSort(prop, a, b) {
// check if it's grouping
const aValue = a['__rvgr-value'] || a[prop];
const bValue = b['__rvgr-value'] || b[prop];
return aValue.localeCompare(bValue, 'en', { numeric: true });
}
const DEFAULT_MAX_CELLS_PER_CHUNK = 50_000;
const DEFAULT_CONFIG = {
topPinned: [],
groupedHeader: false,
bottomPinned: [],
colPinStart: [],
colPinEnd: [],
rowDrag: 0,
rows: 0,
cols: 0,
order: undefined,
};
function getConfig(config = {}) {
return {
...DEFAULT_CONFIG,
...config,
};
}
function createColumn(rgCol, {
colPinStart,
colPinEnd,
rowDrag,
order,
}) {
const column = {
name: generateHeader(rgCol),
prop: rgCol,
sortable: true,
size: 100,
// custom sorting except of 0 row
cellCompare: !!rgCol && rgCol % 2 == 0 ? naturalSort : undefined,
cellProperties: ({ colIndex }) => ({
className: {
'first-column': colIndex === 0,
},
class: {
'first-column-class': colIndex === 0,
},
}),
// custom filter
// filter: 'myFilterType',
};
if (colPinStart.includes(rgCol)) {
column.pin = 'colPinStart';
}
if (colPinEnd.includes(rgCol)) {
column.pin = 'colPinEnd';
}
if (!rgCol) {
column.sortable = true;
column.cellProperties = ({ rowIndex }) => {
return {
'custom-row-index': rowIndex,
};
};
column.rowDrag = true;
}
if (rgCol === rowDrag) {
column.rowDrag = true;
}
if (rgCol === order) {
column.order = 'desc';
}
return column;
}
function createColumns(config) {
const columns = [];
for (let rgCol = 0; rgCol < config.cols; rgCol++) {
columns.push(createColumn(rgCol, config));
}
return columns;
}
function createRow(rgRow, cols) {
const row = {};
if (rgRow === 2) {
// highlighted
row['row-style'] = 'highlighted-row';
}
// apply different key for grouping
if (rgRow % 2) {
row.key = 'a';
} else {
row.key = 'b';
}
if (rgRow % 4) {
row.key2 = 'c';
} else if (rgRow % 3) {
row.key2 = 'd';
}
for (let rgCol = 0; rgCol < cols; rgCol++) {
row[rgCol] = `${rgRow}:${rgCol}`;
}
return row;
}
function applyGroupedHeaders(headers, groupedHeader) {
if (!groupedHeader || headers.length <= 2) {
return;
}
const grouped = headers.splice(1, Math.min(headers.length - 1, 30));
const grouped2 = grouped.splice(0, Math.min(headers.length - 1, 2));
grouped2.length && grouped.push({
name: 'Grouped2',
children: grouped2,
columnTemplate: (h, { value }) => {
return h('div', {
class: 'grouped-header',
}, 'Grouped2');
},
});
grouped.length && headers.splice(
6,
0,
...[
{
name: 'Grouped',
children: grouped,
},
],
);
const grouped4 = headers.splice(0, Math.min(headers.length - 1, 4));
grouped4.length && headers.splice(
0,
0,
...[
{
name: 'Grouped3',
children: grouped4,
},
],
);
}
function getPinnedSets(topPinned, bottomPinned) {
return {
topPinnedSet: new Set(topPinned),
bottomPinnedSet: new Set(bottomPinned),
};
}
function addRow({
row,
rgRow,
result,
pinnedTopRows,
pinnedBottomRows,
topPinnedSet,
bottomPinnedSet,
}) {
if (topPinnedSet.has(rgRow)) {
pinnedTopRows.push({ ...row });
return;
}
if (bottomPinnedSet.has(rgRow)) {
pinnedBottomRows.push({ ...row });
return;
}
result.push(row);
}
function finishData({ result, pinnedTopRows, pinnedBottomRows, headers, groupedHeader }) {
applyGroupedHeaders(headers, groupedHeader);
return {
rows: result,
pinnedTopRows,
pinnedBottomRows,
headers,
};
}
function isCanceled(isCanceledFn) {
return typeof isCanceledFn === 'function' && isCanceledFn();
}
function yieldToBrowser() {
return new Promise(resolve => {
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => resolve(), { timeout: 50 });
return;
}
setTimeout(resolve, 0);
});
}
function getChunkRows(cols, maxCellsPerChunk) {
if (!cols) {
return maxCellsPerChunk;
}
return Math.max(1, Math.floor(maxCellsPerChunk / cols));
}
export function generateFakeDataObject(config = {}) {
const {
topPinned,
bottomPinned,
groupedHeader,
rows,
cols,
} = getConfig(config);
const result = [];
const headers = createColumns(getConfig(config));
const pinnedTopRows = [];
const pinnedBottomRows = [];
const { topPinnedSet, bottomPinnedSet } = getPinnedSets(topPinned, bottomPinned);
for (let rgRow = 0; rgRow < rows; rgRow++) {
addRow({
row: createRow(rgRow, cols),
rgRow,
result,
pinnedTopRows,
pinnedBottomRows,
topPinnedSet,
bottomPinnedSet,
});
}
return finishData({
result,
pinnedTopRows,
pinnedBottomRows,
headers,
groupedHeader,
});
}
export async function generateFakeDataObjectAsync(config = {}, options = {}) {
const dataConfig = getConfig(config);
const {
rows,
cols,
topPinned,
bottomPinned,
groupedHeader,
} = dataConfig;
const {
maxCellsPerChunk = DEFAULT_MAX_CELLS_PER_CHUNK,
onProgress,
isCanceled: isCanceledFn,
} = options;
const result = [];
const pinnedTopRows = [];
const pinnedBottomRows = [];
const headers = createColumns(dataConfig);
const { topPinnedSet, bottomPinnedSet } = getPinnedSets(topPinned, bottomPinned);
const chunkRows = getChunkRows(cols, maxCellsPerChunk);
for (let rgRow = 0; rgRow < rows; rgRow++) {
if (isCanceled(isCanceledFn)) {
return null;
}
addRow({
row: createRow(rgRow, cols),
rgRow,
result,
pinnedTopRows,
pinnedBottomRows,
topPinnedSet,
bottomPinnedSet,
});
if ((rgRow + 1) % chunkRows === 0) {
onProgress?.({
rows: rgRow + 1,
totalRows: rows,
});
await yieldToBrowser();
}
}
if (isCanceled(isCanceledFn)) {
return null;
}
if (!rows || rows % chunkRows !== 0) {
onProgress?.({
rows,
totalRows: rows,
});
}
return finishData({
result,
pinnedTopRows,
pinnedBottomRows,
headers,
groupedHeader,
});
}