Skip to content

Commit

Permalink
Added JsDoc comments to grid functions; improved groupBy function; im…
Browse files Browse the repository at this point in the history
…proved quality of grid cell rendering
  • Loading branch information
st3v3y committed Mar 28, 2024
1 parent e412a40 commit 8d79cdd
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 88 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
engine-strict=true
@mediakular:registry=https://npm.pkg.github.com
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mediakular/gridcraft",
"version": "0.0.19",
"version": "0.1.0",
"scripts": {
"dev": "vite dev",
"build": "vite build && npm run package",
Expand All @@ -12,6 +12,9 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"publish": "npm publish --access public"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
Expand Down
118 changes: 82 additions & 36 deletions src/lib/GridFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ export class GridFunctions<T> {
public groupHeaders: GroupHeader<T>[] = [];
public groupHeadersUnpaged: GroupHeader<T>[] = [];

/**
* Initializes the GridFunctions instance with the provided data array.
*
* @param {T[]} data - The data array to initialize the GridFunctions with.
* @return {GridFunctions<T>} The initialized GridFunctions instance.
*/
init(data: T[]) : GridFunctions<T> {
this.data = data;
this.dataLength = this.data.length;
return this;
}

/**
* Applies filters to the grid data.
*
* @param {GridFilter[]} filters - An array of grid filters.
* @param {GridColumn<T>[]} columns - An array of grid columns.
* @return {GridFunctions<T>} - The updated grid functions object.
*/
applyFilters(filters: GridFilter[], columns: GridColumn<T>[]) : GridFunctions<T> {
if (filters.length == 0) {
return this;
Expand Down Expand Up @@ -54,6 +67,16 @@ export class GridFunctions<T> {
return this;
}

/**
* Sorts the data in the grid by the specified column and order, and then by the specified groupBy column and secondary order if provided.
*
* @param {string} column - The column to sort by
* @param {number} sortOrder - The sort order for the primary column
* @param {string} groupby - The column to group by
* @param {number} sortOrderSecondary - The sort order for the secondary column
* @param {GridColumn<T>[]} columns - The columns to be used for sorting
* @return {GridFunctions<T>} This instance of GridFunctions for chaining purposes
*/
sortBy(column: string, sortOrder: number, groupby: string, sortOrderSecondary: number, columns: GridColumn<T>[]): GridFunctions<T> {
if (groupby) { // always order by the groupBy column here, if the sortbyColumn is != groupby the sort will be done later
this.data = this.data.sort((a, b) => {
Expand Down Expand Up @@ -95,6 +118,15 @@ export class GridFunctions<T> {
return this;
}

/**
* Processes paging for the data based on the current page, items per page, grouping, and columns.
*
* @param {number} currentPage - The current page number.
* @param {number} itemsPerPage - The number of items to display per page.
* @param {string} groupBy - The column to group the data by.
* @param {GridColumn<T>[]} columns - An array of grid columns.
* @return {GridFunctions<T>} The updated GridFunctions object.
*/
processPaging(currentPage: number, itemsPerPage: number, groupBy: string, columns: GridColumn<T>[]): GridFunctions<T> {
this.dataUnpaged = [...this.data];

Expand Down Expand Up @@ -158,55 +190,69 @@ export class GridFunctions<T> {
return this;
}

groupBy(groupBy: string, expandedGroups: { [x: string]: boolean; }, groupsExpandedDefault: boolean, columns: GridColumn<T>[]): GridFunctions<T> {
if (!groupBy) {
/**
* Groups the data by the specified key and updates the group headers and data length.
*
* @param {string} groupByKey - The key to group the data by.
* @param {object} expandedGroups - An object containing the expanded groups.
* @param {boolean} groupsExpandedDefault - The default value for group expansion.
* @param {GridColumn<T>[]} columns - An array of columns.
* @return {GridFunctions<T>} The updated instance of GridFunctions.
*/
groupBy(
groupByKey: string,
expandedGroups: { [key: string]: boolean },
groupsExpandedDefault: boolean,
columns: GridColumn<T>[]
): GridFunctions<T> {
if (!groupByKey) {
return this;
}

let groupByDataLength = 0;
const groupColumn = columns.find((col) => col.key === groupByKey);
if (!groupColumn) {
return this;
}

const groupCol = columns.find((x) => x.key == groupBy);
const groupHeaders: GroupHeader<T>[] = [];
const groupDataLength = this.data.reduce((length, row) => {
const groupValue = groupColumn.accessor
? groupColumn.accessor(row) || ''
: (row as Record<string, any>)[groupByKey] || '';

const groupKey = hash(groupValue);
const isExpanded = groupsExpandedDefault
? !expandedGroups[groupKey]
: expandedGroups[groupKey];

const existingGroup = groupHeaders.find(
(header) => header.groupKey === groupKey
);
if (existingGroup) {
existingGroup.data.push(row);
return isExpanded ? length + 1 : length;
}

this.data.forEach((row: T) => {
const groupValue: object | string = groupCol?.accessor != undefined ? groupCol?.accessor(row) ?? '' : (row as { [key: string]: any })[groupBy] || '';
const existingHeader = this.groupHeaders.find((x) => x.titleData == groupValue);
groupHeaders.push({
selected: false,
groupKey,
titleData: groupValue,
expanded: isExpanded,
data: [row],
});
return isExpanded ? length + 1 : length;
}, 0);

if (existingHeader) {
existingHeader.data.push(row);
if (existingHeader.expanded) {
groupByDataLength++;
}
} else {
const groupKey = hash(groupValue);
const extistingGroup = this.groupHeaders.find(x => x.groupKey == groupKey);
const expanded = groupsExpandedDefault ? !expandedGroups[groupKey] : expandedGroups[groupKey];

if (!extistingGroup) {
this.groupHeaders.push({
selected: false,
groupKey: groupKey,
titleData: groupValue,
expanded: expanded,
data: [row],
});
} else {
extistingGroup.data.push(row);
}
if (expanded) {
groupByDataLength++;
}
}
});
this.groupHeaders = groupHeaders;

// deep clone group headers
// deep clone group headers, we need to do this because the group headers are mutable
this.groupHeaders.forEach(header => {
const newHeader = {...header};
newHeader.data = [...newHeader.data]
this.groupHeadersUnpaged.push(newHeader);
});


this.dataLength = groupByDataLength;
this.dataLength = groupDataLength;

return this;
}
Expand Down
84 changes: 33 additions & 51 deletions src/lib/components/Grid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -161,25 +161,20 @@
<svelte:component this={theme.grid.groupby.checkbox} checked={unpagedHeader?.data.every(item => selectedRows.includes(item))} onChange={() => { toggleGroupCheckbox(header); }} index={groupIndex} />
{/if}
<svelte:component this={theme.grid.groupby.cell} colspan={columns.length-1} onToggle={() => toggleGroup(header)} isExpanded={header.expanded}>
{#each columns.filter(x => x.visible != false) as col (col.key)}
{#if groupBy == col.key}
{#if col.renderComponent && col.accessor }
{#if typeof header.titleData === 'object'}
<svelte:component this={col.renderComponent} {...header.titleData} isGroupByHeader={true} />
{:else}
<svelte:component this={col.renderComponent} {...{value: header.titleData}} isGroupByHeader={true} />
{/if}
{:else if col.renderComponent}
<!-- TODO: Check if value={header.titleData} is not also working everywhere instead of {...{value: header.titleData}} -->
<svelte:component this={col.renderComponent} {...{value: header.titleData}} isGroupByHeader={true} />
{:else}
<svelte:component this={theme.grid.groupby.content} value={header.titleData} />
{/if}

{@const unpaged = groupHeadersUnpaged.find(x => x.groupKey == header.groupKey)}
<svelte:component this={theme.grid.groupby.rowsCount} showing={header.data.length} total={unpaged?.data.length} />
{@const col = columns.find(x => x.visible != false && x.key == groupBy)}
{#if col}
{@const value = header.titleData}
{@const renderComponent = col.renderComponent ? col.renderComponent : theme.grid.groupby.content}

{#if typeof value === 'object'}
<svelte:component this={renderComponent} {...value} isGroupByHeader={true} />
{:else}
<svelte:component this={renderComponent} {value} isGroupByHeader={true} />
{/if}
{/each}

{@const unpaged = groupHeadersUnpaged.find(x => x.groupKey == header.groupKey)}
<svelte:component this={theme.grid.groupby.rowsCount} showing={header.data.length} total={unpaged?.data.length} />
{/if}
</svelte:component>
</svelte:component>
{#if header.expanded}
Expand All @@ -188,23 +183,17 @@
{#if showCheckboxes}
<svelte:component this={theme.grid.body.checkbox} value={row} index={gridData.indexOf(row)} bind:group={selectedRows} />
{/if}
{#each columns.filter(x => x.visible != false) as col (col.key)}
{#if groupBy != col.key}
<svelte:component this={theme.grid.body.cell}>
{#if col.renderComponent && col.accessor}
{#if typeof col.accessor(row) === 'object'}
<svelte:component this={col.renderComponent} {...col.accessor(row)} />
{:else}
<svelte:component this={col.renderComponent} {...{value: col.accessor(row)}} />
{/if}
{:else if col.renderComponent}
<svelte:component this={col.renderComponent} {...{value: row[col.key]}} />
{:else}
{@const value = col.accessor ? col.accessor(row) : row[col.key]}
<svelte:component this={theme.grid.body.content} value={value} />
{/if}
</svelte:component>
{/if}
{#each columns.filter(x => x.visible != false && x.key != groupBy) as col (col.key)}
<svelte:component this={theme.grid.body.cell}>
{@const value = col.accessor ? col.accessor(row) : row[col.key]}
{@const renderComponent = col.renderComponent ? col.renderComponent : theme.grid.body.content}

{#if typeof value === 'object'}
<svelte:component this={renderComponent} {...value} />
{:else}
<svelte:component this={renderComponent} {value} />
{/if}
</svelte:component>
{/each}
</svelte:component>
{/each}
Expand All @@ -217,22 +206,15 @@
<svelte:component this={theme.grid.body.checkbox} value={row} bind:group={selectedRows} index={index} />
{/if}
{#each columns.filter(x => x.visible != false) as col (col.key)}
{#if groupBy != col.key}
<svelte:component this={theme.grid.body.cell}>
{#if col.renderComponent && col.accessor}
{#if typeof col.accessor(row) === 'object'}
<svelte:component this={col.renderComponent} {...col.accessor(row)} />
{:else}
<svelte:component this={col.renderComponent} {...{value: col.accessor(row)}} />
{/if}
{:else if col.renderComponent}
<svelte:component this={col.renderComponent} {...{value: row[col.key]}} />
{:else}
{@const value = col.accessor ? col.accessor(row) : row[col.key]}
<svelte:component this={theme.grid.body.content} value={value} />
{/if}
</svelte:component>
{/if}
<svelte:component this={theme.grid.body.cell}>
{@const value = col.accessor ? col.accessor(row) : row[col.key]}
{@const renderComponent = col.renderComponent ? col.renderComponent : theme.grid.body.content}
{#if typeof value === 'object'}
<svelte:component this={renderComponent} {...value} />
{:else}
<svelte:component this={renderComponent} {value} />
{/if}
</svelte:component>
{/each}
</svelte:component>
{/each}
Expand Down

0 comments on commit 8d79cdd

Please sign in to comment.