Skip to content
4 changes: 2 additions & 2 deletions packages/grid/src/GridMetricCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ export class GridMetricCalculator {
visibleRows,
visibleColumns,

// Map of the height/width of visible rows/columns
// Map of the height/width of columns in the viewport (excluding floating columns)
visibleRowHeights,
visibleColumnWidths,

Expand All @@ -632,7 +632,7 @@ export class GridMetricCalculator {
allRows,
allColumns,

// Map of the height/width of visible rows/columns
// Map of the height/width of all rendered columns (visible + floating + dragging)
allRowHeights,
allColumnWidths,

Expand Down
38 changes: 23 additions & 15 deletions packages/iris-grid/src/FilterInputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface FilterInputFieldProps {
className: string;
style: React.CSSProperties;
value: string;
showAdvancedFilterButton: boolean;
isAdvancedFilterSet: boolean;
onAdvancedFiltersTriggered: React.MouseEventHandler<HTMLButtonElement>;
onChange: (value: string) => void;
Expand All @@ -39,6 +40,7 @@ class FilterInputField extends PureComponent<
style: {},
className: '',
value: '',
showAdvancedFilterButton: false,
isAdvancedFilterSet: false,
onAdvancedFiltersTriggered: (): void => undefined,
onChange: (): void => undefined,
Expand Down Expand Up @@ -207,6 +209,7 @@ class FilterInputField extends PureComponent<
const {
className,
style,
showAdvancedFilterButton,
isAdvancedFilterSet,
onAdvancedFiltersTriggered,
} = this.props;
Expand Down Expand Up @@ -234,21 +237,26 @@ class FilterInputField extends PureComponent<
autoCapitalize="off"
spellCheck="false"
/>
<div className="advanced-filter-button-container">
<Button
kind="ghost"
className={classNames('btn-link-icon advanced-filter-button', {
'filter-set': isAdvancedFilterSet,
})}
onClick={onAdvancedFiltersTriggered}
onContextMenu={this.handleContextMenu}
>
<div className="fa-layers ">
<FontAwesomeIcon icon={dhFilterFilled} className="filter-solid" />
<FontAwesomeIcon icon={vsFilter} className="filter-light" />
</div>
</Button>
</div>
{showAdvancedFilterButton && (
<div className="advanced-filter-button-container">
<Button
kind="ghost"
className={classNames('btn-link-icon advanced-filter-button', {
'filter-set': isAdvancedFilterSet,
})}
onClick={onAdvancedFiltersTriggered}
onContextMenu={this.handleContextMenu}
>
<div className="fa-layers ">
<FontAwesomeIcon
icon={dhFilterFilled}
className="filter-solid"
/>
<FontAwesomeIcon icon={vsFilter} className="filter-light" />
</div>
</Button>
</div>
)}
</div>
);
}
Expand Down
38 changes: 36 additions & 2 deletions packages/iris-grid/src/IrisGrid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,18 @@ describe('handleResizeAllColumns', () => {
});
jest.spyOn(component, 'setState');
expect(component.setState).not.toBeCalled();
component.rebuildFilters();
act(() => {
component.rebuildFilters();
});
expect(component.setState).toBeCalled();
});

it('does not update state for empty filters', () => {
const component = makeComponent();
jest.spyOn(component, 'setState');
component.rebuildFilters();
act(() => {
component.rebuildFilters();
});
expect(component.setState).not.toBeCalled();
});
});
Expand Down Expand Up @@ -430,3 +434,33 @@ describe('handleResizeAllColumns', () => {
});
});
});

describe('Advanced Filter', () => {
it.each([
{ columnIndex: -1, expectedVisibility: false },
{ columnIndex: 0, expectedVisibility: true },
{ columnIndex: 1, expectedVisibility: true },
])(
'advanced filter button visibility is $expectedVisibility for column index $columnIndex',
({ columnIndex, expectedVisibility }) => {
const model = irisGridTestUtils.makeModel();
const ref = React.createRef<IrisGrid>();
const { container } = render(
<IrisGrid ref={ref} model={model} settings={DEFAULT_SETTINGS} />
);

act(() => {
ref.current?.setState({
focusedFilterBarColumn: columnIndex,
isFilterBarShown: true,
});
});

const advancedFilterButtons = container.querySelectorAll(
'.advanced-filter-button'
);

expect(advancedFilterButtons.length > 0).toBe(expectedVisibility);
}
);
});
217 changes: 128 additions & 89 deletions packages/iris-grid/src/IrisGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2513,7 +2513,6 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {

if (
column == null ||
column < 0 ||
columnCount <= column ||
!model.isFilterable(modelColumn)
) {
Expand All @@ -2523,22 +2522,19 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {

const { metricCalculator, metrics } = this.state;
assertNotNull(metrics);
const { left, rightVisible, lastLeft } = metrics;
if (column < left) {
this.grid?.setViewState({ left: column }, true);
} else if (rightVisible < column) {
const metricState = this.getMetricState();
assertNotNull(metricState);
const newLeft = metricCalculator.getLastLeft(
metricState,
column,
metricCalculator.getVisibleWidth(metricState)
);
this.grid?.setViewState(
{ left: Math.min(newLeft, lastLeft), leftOffset: 0 },
true
);
const metricState = this.getMetricState();
assertNotNull(metricState);

const scrollColumn = metricCalculator.getScrollLeftForColumn(
column,
metricState,
metrics
);

if (scrollColumn != null) {
this.grid?.setViewState({ left: scrollColumn, leftOffset: 0 }, true);
}

this.lastFocusedFilterBarColumn = column;
this.setState({ focusedFilterBarColumn: column, isFilterBarShown: true });
}
Expand Down Expand Up @@ -4515,6 +4511,99 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
this.seekRow(gotoValue, isBackwards);
}

/**
* Render the input field for the focused filter
* @param metrics Grid metrics
* @param metricCalculator Metric calculator
* @param focusedFilterBarColumn Column index for the focused filter
* @param quickFilters Quick filters map
* @param advancedFilters Advanced filters map
* @returns The filter input field element or null if not applicable
*/
getFilterBarInput(
metrics: GridMetrics | undefined,
metricCalculator: IrisGridMetricCalculator,
focusedFilterBarColumn: VisibleIndex | null,
quickFilters: ReadonlyQuickFilterMap,
advancedFilters: ReadonlyAdvancedFilterMap
): ReactElement | null {
if (metrics == null || focusedFilterBarColumn == null) {
return null;
}

const metricState = this.getMetricState();
if (metricState == null) {
return null;
}

const filterBoxCoordinates = metricCalculator.getFilterInputCoordinates(
focusedFilterBarColumn,
metricState,
metrics
);
if (filterBoxCoordinates == null) {
return null;
}

const debounceMs = Math.min(
Math.max(IrisGrid.minDebounce, Math.round(metrics.rowCount / 200)),
IrisGrid.maxDebounce
);
const {
x,
y,
width: fieldWidth,
height: fieldHeight,
} = filterBoxCoordinates;
const { width } = metrics;
const style = {
top: y,
left: x,
minWidth: Math.min(fieldWidth, width - x), // Don't cause overflow
height: fieldHeight,
};
let value = '';
let isValid = true;
const modelColumn = this.getModelColumn(focusedFilterBarColumn);
assertNotNull(modelColumn);
const quickFilter = quickFilters.get(modelColumn);
const advancedFilter = advancedFilters.get(modelColumn);
if (quickFilter != null) {
value = quickFilter.text;
isValid = quickFilter.filter != null;
}
const isBarFiltered = quickFilters.size !== 0 || advancedFilters.size !== 0;
const showAdvancedFilterButton =
metricCalculator.getAdvancedFilterButtonCoordinates(
focusedFilterBarColumn,
metricState,
metrics
) != null;
return (
<FilterInputField
ref={this.filterInputRef}
style={style}
className={classNames({
error: !isValid,
active: value !== '' || advancedFilter != null,
'iris-grid-has-filter': isBarFiltered,
})}
showAdvancedFilterButton={showAdvancedFilterButton}
isAdvancedFilterSet={advancedFilter != null}
onAdvancedFiltersTriggered={() => {
this.setState({ shownAdvancedFilter: focusedFilterBarColumn });
}}
key={focusedFilterBarColumn}
onChange={this.handleFilterBarChange}
onDone={this.handleFilterBarDone}
onTab={this.handleFilterBarTab}
onContextMenu={this.grid?.handleContextMenu}
debounceMs={debounceMs}
value={value}
/>
);
}

render(): ReactElement | null {
const {
children,
Expand Down Expand Up @@ -4649,66 +4738,15 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
metrics != null && metrics.width > 0 && metrics.height > 0;
const isRollup = (rollupConfig?.columns?.length ?? 0) > 0;

let focusField = null;

const debounceMs = metrics
? Math.min(
Math.max(IrisGrid.minDebounce, Math.round(metrics.rowCount / 200)),
IrisGrid.maxDebounce
const focusField = isFilterBarShown
? this.getFilterBarInput(
metrics,
metricCalculator,
focusedFilterBarColumn,
quickFilters,
advancedFilters
)
: IrisGrid.maxDebounce;

if (isFilterBarShown && focusedFilterBarColumn != null && metrics != null) {
const { gridX, gridY, allColumnXs, allColumnWidths, width } = metrics;
const columnX = allColumnXs.get(focusedFilterBarColumn);
const columnWidth = allColumnWidths.get(focusedFilterBarColumn);
if (columnX != null && columnWidth != null) {
const x = gridX + columnX;
const y = gridY - (theme.filterBarHeight ?? 0);
const fieldWidth = columnWidth + 1; // cover right border
const fieldHeight = (theme.filterBarHeight ?? 0) - 1; // remove bottom border
const style = {
top: y,
left: x,
minWidth: Math.min(fieldWidth, width - x), // Don't cause overflow
height: fieldHeight,
};
let value = '';
let isValid = true;
const modelColumn = this.getModelColumn(focusedFilterBarColumn);
assertNotNull(modelColumn);
const quickFilter = quickFilters.get(modelColumn);
const advancedFilter = advancedFilters.get(modelColumn);
if (quickFilter != null) {
value = quickFilter.text;
isValid = quickFilter.filter != null;
}
const isBarFiltered =
quickFilters.size !== 0 || advancedFilters.size !== 0;
focusField = (
<FilterInputField
ref={this.filterInputRef}
style={style}
className={classNames({
error: !isValid,
active: value !== '' || advancedFilter != null,
'iris-grid-has-filter': isBarFiltered,
})}
isAdvancedFilterSet={advancedFilter != null}
onAdvancedFiltersTriggered={() => {
this.setState({ shownAdvancedFilter: focusedFilterBarColumn });
}}
key={focusedFilterBarColumn}
onChange={this.handleFilterBarChange}
onDone={this.handleFilterBarDone}
onTab={this.handleFilterBarTab}
onContextMenu={this.grid?.handleContextMenu}
debounceMs={debounceMs}
value={value}
/>
);
}
}
: null;

let loadingElement = null;
if (loadingText != null) {
Expand Down Expand Up @@ -4753,26 +4791,27 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {

const filterBar = [];
if (metrics && isFilterBarShown) {
const { gridX, gridY, visibleColumns, allColumnXs, allColumnWidths } =
metrics;
const { filterBarHeight } = theme;
const metricState = this.getMetricState();

// Advanced Filter buttons
const { visibleColumns } = metrics;

for (let i = 0; i < visibleColumns.length; i += 1) {
const columnIndex = visibleColumns[i];

const columnX = allColumnXs.get(columnIndex);
const columnWidth = allColumnWidths.get(columnIndex);
const modelColumn = this.getModelColumn(columnIndex);

if (modelColumn != null) {
const isFilterable = model.isFilterable(modelColumn);
if (
isFilterable &&
columnX != null &&
columnWidth != null &&
columnWidth > 0
) {
const x = gridX + columnX + columnWidth - 24;
const y = gridY - (filterBarHeight ?? 0) + 2; // 2 acts as top margin for the button
const buttonCoordinates =
isFilterable && metricState
? metricCalculator.getAdvancedFilterButtonCoordinates(
columnIndex,
metricState,
metrics
)
: null;
if (buttonCoordinates != null) {
const { x, y } = buttonCoordinates;
const style: CSSProperties = {
position: 'absolute',
top: y,
Expand Down
Loading
Loading