Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Add sections to some of the feature hooks #3391

Merged
merged 4 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
useGridApiEventHandler,
} from '../../utils';
import { gridColumnMenuSelector } from './columnMenuSelector';
import { GridColumnMenuApi } from '../../../models';

/**
* @requires useGridColumnResize (event)
Expand All @@ -22,8 +23,11 @@ export const useGridColumnMenu = (apiRef: GridApiRef): void => {
const [, setGridState, forceUpdate] = useGridState(apiRef);
const columnMenu = useGridSelector(apiRef, gridColumnMenuSelector);

const showColumnMenu = React.useCallback(
(field: string) => {
/**
* API METHODS
*/
const showColumnMenu = React.useCallback<GridColumnMenuApi['showColumnMenu']>(
(field) => {
const shouldUpdate = setGridState((state) => {
if (state.columnMenu.open && state.columnMenu.field === field) {
return state;
Expand All @@ -44,7 +48,7 @@ export const useGridColumnMenu = (apiRef: GridApiRef): void => {
[apiRef, forceUpdate, logger, setGridState],
);

const hideColumnMenu = React.useCallback(() => {
const hideColumnMenu = React.useCallback<GridColumnMenuApi['hideColumnMenu']>(() => {
const shouldUpdate = setGridState((state) => {
if (!state.columnMenu.open && state.columnMenu.field === undefined) {
return state;
Expand All @@ -62,8 +66,8 @@ export const useGridColumnMenu = (apiRef: GridApiRef): void => {
}
}, [forceUpdate, logger, setGridState]);

const toggleColumnMenu = React.useCallback(
(field: string) => {
const toggleColumnMenu = React.useCallback<GridColumnMenuApi['toggleColumnMenu']>(
(field) => {
logger.debug('Toggle Column Menu');
if (!columnMenu.open || columnMenu.field !== field) {
showColumnMenu(field);
Expand All @@ -74,16 +78,17 @@ export const useGridColumnMenu = (apiRef: GridApiRef): void => {
[logger, showColumnMenu, hideColumnMenu, columnMenu],
);

useGridApiMethod(
apiRef,
{
showColumnMenu,
hideColumnMenu,
toggleColumnMenu,
},
'ColumnMenuApi',
);
const columnMenuApi: GridColumnMenuApi = {
showColumnMenu,
hideColumnMenu,
toggleColumnMenu,
};

useGridApiMethod(apiRef, columnMenuApi, 'GridColumnMenuApi');

/**
* EVENTS
*/
useGridApiEventHandler(apiRef, GridEvents.columnResizeStart, hideColumnMenu);
useGridApiEventHandler(apiRef, GridEvents.rowsScroll, hideColumnMenu);
};
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export function useGridColumns(
[logger, setGridState, forceUpdate, apiRef],
);

/**
* API METHODS
*/
const getColumn = React.useCallback<GridColumnApi['getColumn']>(
(field) => gridColumnLookupSelector(apiRef.current.state)[field],
[apiRef],
Expand Down Expand Up @@ -189,7 +192,7 @@ export function useGridColumns(
[apiRef, logger],
);

const colApi: GridColumnApi = {
const columnApi: GridColumnApi = {
getColumn,
getAllColumns,
getColumnIndex,
Expand All @@ -203,28 +206,11 @@ export function useGridColumns(
setColumnWidth,
};

useGridApiMethod(apiRef, colApi, 'ColApi');

// The effect do not track any value defined synchronously during the 1st render by hooks called after `useGridColumns`
// As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}

logger.info(`GridColumns have changed, new length ${props.columns.length}`);

const columnsState = createColumnsState({
apiRef,
columnsTypes,
columnsToUpsert: props.columns,
reset: true,
});
setGridColumnsState(columnsState);
}, [logger, apiRef, setGridColumnsState, props.columns, columnsTypes]);
useGridApiMethod(apiRef, columnApi, 'GridColumnApi');

/**
* EVENTS
*/
const handlePreProcessorRegister = React.useCallback<
GridEventListener<GridEvents.preProcessorRegister>
>(
Expand Down Expand Up @@ -259,10 +245,32 @@ export function useGridColumns(
useGridApiEventHandler(apiRef, GridEvents.preProcessorRegister, handlePreProcessorRegister);
useGridApiEventHandler(apiRef, GridEvents.viewportInnerSizeChange, handleGridSizeChange);

// Grid Option Handlers
useGridApiOptionHandler(
apiRef,
GridEvents.columnVisibilityChange,
props.onColumnVisibilityChange,
);

/**
* EFFECTS
*/
// The effect do not track any value defined synchronously during the 1st render by hooks called after `useGridColumns`
// As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}

logger.info(`GridColumns have changed, new length ${props.columns.length}`);

const columnsState = createColumnsState({
apiRef,
columnsTypes,
columnsToUpsert: props.columns,
reset: true,
});
setGridColumnsState(columnsState);
}, [logger, apiRef, setGridColumnsState, props.columns, columnsTypes]);
}
104 changes: 104 additions & 0 deletions packages/grid/_modules_/grid/hooks/features/filter/gridFilterUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
GridApiRef,
GridFilterItem,
GridFilterModel,
GridLinkOperator,
GridRowId,
} from '../../../models';

type GridFilterItemApplier = (rowId: GridRowId) => boolean;

/**
* Adds default values to the optional fields of a filter items.
* @param {GridFilterItem} item The raw filter item.
* @param {GridApiRef} apiRef The API of the grid.
* @return {GridFilterItem} The clean filter item with an uniq ID and an always-defined operatorValue.
* TODO: Make the typing reflect the different between GridFilterInputItem and GridFilterItem.
*/
export const cleanFilterItem = (item: GridFilterItem, apiRef: GridApiRef) => {
const cleanItem: GridFilterItem = { ...item };

if (cleanItem.id == null) {
cleanItem.id = Math.round(Math.random() * 1e5);
}

if (cleanItem.operatorValue == null) {
// we select a default operator
const column = apiRef.current.getColumn(cleanItem.columnField);
cleanItem.operatorValue = column && column!.filterOperators![0].value!;
}

return cleanItem;
};

/**
* Generates a method to easily check if a row is matching the current filter model.
* @param {GridFilterModel} filterModel The model with which we want to filter the rows.
* @param {GridApiRef} apiRef The API of the grid.
* @returns {GridFilterItemApplier | null} A method that checks if a row is matching the current filter model. If `null`, we consider that all the rows are matching the filters.
*/
export const buildAggregatedFilterApplier = (
filterModel: GridFilterModel,
apiRef: GridApiRef,
): GridFilterItemApplier | null => {
const { items, linkOperator = GridLinkOperator.And } = filterModel;

const getFilterCallbackFromItem = (filterItem: GridFilterItem): GridFilterItemApplier | null => {
if (!filterItem.columnField || !filterItem.operatorValue) {
return null;
}

const column = apiRef.current.getColumn(filterItem.columnField);
if (!column) {
return null;
}

const parsedValue = column.valueParser
? column.valueParser(filterItem.value)
: filterItem.value;
const newFilterItem: GridFilterItem = { ...filterItem, value: parsedValue };

const filterOperators = column.filterOperators;
if (!filterOperators?.length) {
throw new Error(`MUI: No filter operators found for column '${column.field}'.`);
}

const filterOperator = filterOperators.find(
(operator) => operator.value === newFilterItem.operatorValue,
)!;
if (!filterOperator) {
throw new Error(
`MUI: No filter operator found for column '${column.field}' and operator value '${newFilterItem.operatorValue}'.`,
);
}

const applyFilterOnRow = filterOperator.getApplyFilterFn(newFilterItem, column)!;
if (typeof applyFilterOnRow !== 'function') {
return null;
}

return (rowId: GridRowId) => {
const cellParams = apiRef.current.getCellParams(rowId, newFilterItem.columnField!);

return applyFilterOnRow(cellParams);
};
};

const appliers = items
.map(getFilterCallbackFromItem)
.filter((callback): callback is GridFilterItemApplier => !!callback);

if (appliers.length === 0) {
return null;
}

return (rowId: GridRowId) => {
// Return `false` as soon as we have a failing filter
if (linkOperator === GridLinkOperator.And) {
return appliers.every((applier) => applier(rowId));
}

// Return `true` as soon as we have a passing filter
return appliers.some((applier) => applier(rowId));
};
};
Loading