Skip to content

Commit

Permalink
fix: post sort rows hook doesnt sort on swap
Browse files Browse the repository at this point in the history
* fix: postSortRowsHook doesnt sort on swap

* fix: action button wont spin if not passed a promise

* Fix grid height in popout

* fix: error on update of destroyed grid

* feat: add aria label to action button, fix post sort rows exception on zombie grid

* feat: add level to ActionButton

* fix: centering spinner
  • Loading branch information
matttdawson authored Nov 9, 2022
1 parent c60cb59 commit 0f9ca0e
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 86 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@linzjs/step-ag-grid",
"repository": "github:linz/step-ag-grid.git",
"license": "MIT",
"version": "2.4.1",
"version": "2.4.2",
"keywords": [
"aggrid",
"ag-grid",
Expand Down Expand Up @@ -46,6 +46,7 @@
},
"scripts": {
"build": "run-s clean stylelint lint css bundle",
"yalc": "run-s clean css bundle && yalc publish",
"clean": "rimraf dist && mkdirp ./dist",
"bundle": "rollup -c",
"stylelint": "stylelint src/**/*.scss src/**/*.css --fix",
Expand Down
103 changes: 78 additions & 25 deletions src/components/PostSortRowsHook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useCallback, useRef } from "react";
import { useCallback, useContext, useRef } from "react";
import { PostSortRowsParams } from "ag-grid-community/dist/lib/entities/iCallbackParams";
import { ColumnState } from "ag-grid-community/dist/lib/columns/columnModel";
import { isEmpty } from "lodash-es";
import { RowNode } from "ag-grid-community";
import { GridContext } from "../contexts/GridContext";

interface PostSortRowsHookProps {
setStaleGrid: (stale: boolean) => void;
Expand All @@ -12,14 +15,16 @@ interface PostSortRowsHookProps {
* Handles stale sort, when you edit a row but don't want to re-sort.
*/
export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) => {
const { redrawRows } = useContext(GridContext);

// On first run we need to init the first backed up sort order
const initialised = useRef(false);

// Used to detect sort order has changed since last run
const lastSortOrderHash = useRef<string>("");

// Used to detect if sort order was the same, has the direction changed
const previousRowSortIndexRef = useRef<Record<number, number | undefined>>({});
const previousRowSortIndexRef = useRef<Record<string, { index: number; hash: string } | undefined>>({});

// stale sort is when there's a sort and user edits a row
// this applies a class to the div wrapping the grid which in turn adds a * beside the sort arrow
Expand All @@ -33,14 +38,21 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>

return useCallback(
({ api, columnApi, nodes }: PostSortRowsParams) => {
// Grid is destroyed
if (!api || !columnApi) return;

const previousRowSortIndex = previousRowSortIndexRef.current;

const hashNode = (node: RowNode | undefined) => {
return node ? JSON.stringify(node.data) : "";
};

const copyCurrentSortSettings = (): ColumnState[] =>
columnApi.getColumnState().map((row) => ({ colId: row.colId, sort: row.sort, sortIndex: row.sortIndex }));

const backupSortOrder = () => {
for (const x in previousRowSortIndex) delete previousRowSortIndex[x];
nodes.forEach((row, index) => (previousRowSortIndex[row.data.id] = index));
nodes.forEach((node, index) => (previousRowSortIndex[`${node.data.id}`] = { index, hash: hashNode(node) }));
};

// Check if column is the first sorted column. Note: column is preconfigured to sort its sortIndex is null not 1
Expand All @@ -51,22 +63,13 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>

const restorePreviousSortColumnState = () => columnApi.applyColumnState({ state: previousColSort.current });

const hasNewRows = () => nodes.some((row) => previousRowSortIndex[row.data.id] == null);

const sortIsStale = () => {
// If there are new rows we want to them to be at the bottom of the grid, so we treat it as sort not stale
if (hasNewRows()) return false;

// Otherwise check if the stored sort index matches the new sort index
return nodes.some((node, index) => previousRowSortIndex[node.data.id] != index);
};

const sortNodesByPreviousSort = () =>
const sortNodesByPreviousSort = () => {
nodes.sort(
(a, b) =>
(previousRowSortIndex[a.data.id] ?? Number.MAX_SAFE_INTEGER) -
(previousRowSortIndex[b.data.id] ?? Number.MAX_SAFE_INTEGER),
(previousRowSortIndex[`${a.data.id}`]?.index ?? Number.MAX_SAFE_INTEGER) -
(previousRowSortIndex[`${b.data.id}`]?.index ?? Number.MAX_SAFE_INTEGER),
);
};

// On first load copy the current sort
if (!initialised.current) {
Expand All @@ -83,6 +86,10 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
sortOrderChanged = true;
}

if (isEmpty(previousRowSortIndex)) {
backupSortOrder();
}

if (sortOrderChanged) {
const thisFirstCol = copyCurrentSortSettings().find(isFirstSortColumn);
const previousFirstCol = previousColSort.current.find(isFirstSortColumn);
Expand Down Expand Up @@ -115,23 +122,69 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
lastSortOrderHash.current = newSortOrder;
}
} else {
if (sortIsStale()) {
let firstChangedNodeIndex = -1;
let lastNewNode: RowNode | undefined = undefined;
let changedRowCount = 0;
let newRowCount = 0;
let index = 0;
for (const node of nodes) {
const psr = previousRowSortIndex[`${node.data.id}`];
if (psr) {
if (psr.hash != hashNode(node)) {
if (firstChangedNodeIndex === -1) firstChangedNodeIndex = index;
changedRowCount++;
}
} else {
lastNewNode = node;
newRowCount++;
}
index++;
}

let wasStale = false;
if (changedRowCount === 0 && newRowCount === 1) {
// insert new row at end
const newIndex = index - 1;
previousRowSortIndex[`${lastNewNode?.data.id}`] = { index: newIndex, hash: hashNode(lastNewNode) };
wasStale = true;
} else if (changedRowCount === 2 && newRowCount === 0) {
// This must be a swap rows
backupSortOrder();
wasStale = false;
} else if (changedRowCount > 1 && newRowCount === 1) {
// This must be a insert so, insert new row near the row that changed
previousRowSortIndex[`${lastNewNode?.data.id}`] = {
index: firstChangedNodeIndex + 0.5,
hash: hashNode(lastNewNode),
};
wasStale = true;
// For some reason AgGrid mis-positions the inserted row.
lastNewNode && redrawRows();
} else if (changedRowCount == 1 && newRowCount === 0) {
// User edited one row so, do nothing, retain sort
wasStale = true;
} else if (changedRowCount !== 0 || newRowCount != 0) {
// too many rows changed, resort
backupSortOrder();
}

if (wasStale) {
// Check if the sort order the aggrid passed matches our stale sort order
const stillStale =
Object.keys(previousRowSortIndex).length != nodes.length ||
nodes.some((node, index) => previousRowSortIndex[`${node.data.id}`]?.index !== index);

// If we haven't already processed a stale sort then...
if (!sortWasStale.current) {
if (stillStale && !sortWasStale.current) {
// backup sort state, so we can restore it when sort is clicked on a stale column
previousColSort.current = copyCurrentSortSettings();
backupSortOrder();
sortWasStale.current = true;
setStaleGrid(true);
}

sortNodesByPreviousSort();
}
// secondary sort backup as there may be new nodes that didn't have their sort registered
// which would cause two new rows to sort out of sequence
backupSortOrder();
sortNodesByPreviousSort();
}
},
[setStaleGrid],
[redrawRows, setStaleGrid],
);
};
90 changes: 45 additions & 45 deletions src/components/gridForm/GridFormSubComponentTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
import { useState } from "react";
import { TextInputFormatted } from "../../lui/TextInputFormatted";

export interface GridFormSubComponentTextInput {
setValue: (value: string) => void;
keyDown: (key: string) => void;
placeholder?: string;
className?: string;
}

export const GridFormSubComponentTextInput = ({
keyDown,
placeholder,
setValue,
className,
}: GridFormSubComponentTextInput) => {
const placeholderText = placeholder || "Other...";
const inputClass = className || "";
const [inputValue, setInputValue] = useState("");
return (
<>
<TextInputFormatted
className={inputClass}
value={inputValue}
onChange={(e) => {
const value = e.target.value;
setValue(value);
setInputValue(value);
}}
inputProps={{
onKeyDown: (k: any) => keyDown(k.key),
placeholder: placeholderText,
onMouseEnter: (e) => {
if (document.activeElement != e.currentTarget) {
e.currentTarget.focus();
}
},
style: {
width: "100%",
},
}}
/>
</>
);
};
import { useState } from "react";
import { TextInputFormatted } from "../../lui/TextInputFormatted";

export interface GridFormSubComponentTextInput {
setValue: (value: string) => void;
keyDown: (key: string) => void;
placeholder?: string;
className?: string;
}

export const GridFormSubComponentTextInput = ({
keyDown,
placeholder,
setValue,
className,
}: GridFormSubComponentTextInput) => {
const placeholderText = placeholder || "Other...";
const inputClass = className || "";
const [inputValue, setInputValue] = useState("");
return (
<>
<TextInputFormatted
className={inputClass}
value={inputValue}
onChange={(e) => {
const value = e.target.value;
setValue(value);
setInputValue(value);
}}
inputProps={{
onKeyDown: (k: any) => keyDown(k.key),
placeholder: placeholderText,
onMouseEnter: (e) => {
if (document.activeElement != e.currentTarget) {
e.currentTarget.focus();
}
},
style: {
width: "100%",
},
}}
/>
</>
);
};
6 changes: 5 additions & 1 deletion src/contexts/GridContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext } from "react";
import { GridApi } from "ag-grid-community";
import { GridApi, RowNode } from "ag-grid-community";
import { GridBaseRow } from "../components/Grid";

export interface GridContextType {
Expand All @@ -24,6 +24,7 @@ export interface GridContextType {
fnUpdate: (selectedRows: any[]) => Promise<boolean>,
setSaving?: (saving: boolean) => void,
) => Promise<boolean>;
redrawRows: (rowNodes?: RowNode[]) => void;
}

export const GridContext = createContext<GridContextType>({
Expand Down Expand Up @@ -83,4 +84,7 @@ export const GridContext = createContext<GridContextType>({
console.error("no context provider for modifyUpdating");
return false;
},
redrawRows: () => {
console.error("no context provider for redrawRows");
},
});
7 changes: 7 additions & 0 deletions src/contexts/GridContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
});
};

const redrawRows = (rowNodes?: RowNode[]) => {
gridApiOp((gridApi) => {
gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
});
};

return (
<GridContext.Provider
value={{
Expand All @@ -293,6 +299,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
sizeColumnsToFit,
stopEditing,
updatingCells,
redrawRows,
}}
>
{props.children}
Expand Down
Loading

0 comments on commit 0f9ca0e

Please sign in to comment.