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

Multi Filtering improvement to current filter plugin #302

Merged
merged 34 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a3c3cee
fix issue with undefined localization for filterCaptions
islarky88 Mar 15, 2022
4e39bbb
prepare onMultiFilterChange and multi filtering
islarky88 Mar 18, 2022
6968096
add trash button
islarky88 Mar 19, 2022
89ec6e1
list multiple filter on prop popup
islarky88 Mar 19, 2022
62624b4
remove filter key prop when no more filters
islarky88 Mar 19, 2022
a97b4fe
add toggle filter andOr
islarky88 Mar 19, 2022
3c5a978
button styling improvements
islarky88 Mar 19, 2022
bd28c0a
filter items list styling improvements
islarky88 Mar 19, 2022
88082ed
hide andOr toggle button if only one or last item
islarky88 Mar 20, 2022
ff34207
working initial and/or filter
islarky88 Mar 20, 2022
8fe9939
improve logic
islarky88 Mar 20, 2022
d086b76
comment out console logs for now
islarky88 Mar 20, 2022
b9f6b3b
Merge branch 'revolist:master' into master
islarky88 Mar 20, 2022
38b54f1
cleanup and logic improvement
islarky88 Mar 20, 2022
2e76015
remove console.log
islarky88 Mar 21, 2022
b603843
decluterring
islarky88 Mar 21, 2022
45dbe5c
include MultiFilterItem to filter emits
islarky88 Mar 21, 2022
b491c66
run filtering aftersourceset using multifilteritems
islarky88 Mar 21, 2022
cc064b6
handle old FilterCollection prop to new MultiFilterItem
islarky88 Mar 21, 2022
6c7bd58
remove redundant condition for hiding filter popup
islarky88 Mar 21, 2022
0a1b4e0
add jsdoc comments
islarky88 Mar 21, 2022
f9ec2e6
cleanup
islarky88 Mar 22, 2022
f86241f
improve filterId incrementing logic
islarky88 Mar 22, 2022
67ecee3
only apply filter when pressing save/reset
islarky88 Mar 22, 2022
c45f1f6
bring back details
islarky88 Mar 22, 2022
f9cdf52
add filterCollection to emit events based on multiFilterItems but wit…
islarky88 Mar 22, 2022
c47a78c
UI/UX improvement to show input for each filter and ability to edit
islarky88 Mar 23, 2022
2f64d7d
focus on input when filter type is changed
islarky88 Mar 23, 2022
e5bb45e
fix issue with add filter dropdown still set when filter popup is closed
islarky88 Mar 23, 2022
d43b8c1
remove save button and do debounced filtering for any edit action
islarky88 Mar 24, 2022
a34f73a
fix add-filter dropdown not resetting to defaultType on re opening fi…
islarky88 Mar 24, 2022
32826a7
show data if filter value is not yet set
islarky88 Mar 24, 2022
1af27d9
fix issue with not applying filter on filter change
islarky88 Mar 24, 2022
65d6ae5
improve aftersourceset
islarky88 Mar 24, 2022
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
3 changes: 2 additions & 1 deletion src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { DataInput } from "./plugins/export/types";
import { VNode } from "@stencil/core";
import { ColumnSource, RowSource } from "./components/data/columnService";
import { LogicFunction } from "./plugins/filter/filter.types";
import { FilterItem, ShowData } from "./plugins/filter/filter.pop";
import { FilterItem, MultiFilterItem, ShowData } from "./plugins/filter/filter.pop";
import { DataSourceState, Groups } from "./store/dataSource/data.store";
import { ViewportData } from "./components/revo-grid/viewport.interfaces";
import { ElementScroll } from "./components/revo-grid/viewport.scrolling.service";
Expand Down Expand Up @@ -699,6 +699,7 @@ declare namespace LocalJSX {
"filterNames"?: Record<string, string>;
"filterTypes"?: Record<string, string[]>;
"onFilterChange"?: (event: CustomEvent<FilterItem>) => void;
"onMultiFilterChange"?: (event: CustomEvent<MultiFilterItem>) => void;
"uuid"?: string;
}
interface RevogrFocus {
Expand Down
15 changes: 15 additions & 0 deletions src/plugins/filter/filter.button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { RevoGrid } from '../../interfaces';
export const FILTER_BUTTON_CLASS = 'rv-filter';
export const FILTER_BUTTON_ACTIVE = 'active';
export const FILTER_PROP = 'hasFilter';
export const AND_OR_BUTTON = 'and-or-button';
export const TRASH_BUTTON = 'trash-button';

type Props = {
column: RevoGrid.ColumnRegular;
Expand All @@ -27,6 +29,19 @@ export const FilterButton = ({ column }: Props) => {
);
};

export const TrashButton = () => {
return (
<div class={{ [TRASH_BUTTON]: true }}>
<svg style={{ width: '18px', height: '18px' }} viewBox="0 0 24 24">
<path fill="currentColor" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z" />
</svg>
</div>
);
};
export const AndOrButton = ({ isAnd }: any) => {
return <button class={{ [AND_OR_BUTTON]: true }}>{isAnd ? 'and' : 'or'}</button>;
};

export function isFilterBtn(e: HTMLElement) {
if (e.classList.contains(FILTER_BUTTON_CLASS)) {
return true;
Expand Down
114 changes: 108 additions & 6 deletions src/plugins/filter/filter.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { h } from '@stencil/core';
import BasePlugin from '../basePlugin';
import { RevoGrid } from '../../interfaces';
import { FILTER_PROP, isFilterBtn } from './filter.button';
import { FilterItem } from './filter.pop';
import { FilterItem, MultiFilterItem } from './filter.pop';
import { filterEntities, filterNames, FilterType, filterTypes } from './filter.service';
import { LogicFunction } from './filter.types';

Expand All @@ -17,12 +17,12 @@ export type FilterCaptions = {
save: string;
reset: string;
cancel: string;
}
};

export type FilterLocalization = {
captions: FilterCaptions;
filterNames: Record<FilterType, string>;
}
};

/**
* @typedef ColumnFilterConfig
Expand Down Expand Up @@ -76,8 +76,9 @@ export default class FilterPlugin extends BasePlugin {
uuid={`filter-${uiid}`}
filterNames={this.possibleFilterNames}
filterEntities={this.possibleFilterEntities}
filterCaptions={config.localization?.captions}
filterCaptions={config?.localization?.captions}
onFilterChange={e => this.onFilterChange(e.detail)}
onMultiFilterChange={e => this.onMultiFilterChange(e.detail)}
ref={e => (this.pop = e)}
/>,
]);
Expand Down Expand Up @@ -185,7 +186,18 @@ export default class FilterPlugin extends BasePlugin {

// called on internal component change
private async onFilterChange(filterItem: FilterItem) {
this.filterByProps({ [filterItem.prop]: filterItem });
console.log('onFilterChange', filterItem);
islarky88 marked this conversation as resolved.
Show resolved Hide resolved
// this.filterByProps({ [filterItem.prop]: filterItem });
}

private async onMultiFilterChange(filterItems: MultiFilterItem) {
console.log('onMultiFilterChange', filterItems);
islarky88 marked this conversation as resolved.
Show resolved Hide resolved
const { source, columns } = await this.getData();
const { defaultPrevented, detail } = this.emit('beforefilterapply', { collection: this.filterCollection, source, columns });
if (defaultPrevented) {
return;
}
this.doMultiFiltering(detail.collection, detail.source, detail.columns, filterItems);
}

/**
Expand Down Expand Up @@ -246,6 +258,40 @@ export default class FilterPlugin extends BasePlugin {
this.emit('afterFilterApply');
}

async doMultiFiltering(collection: FilterCollection, items: RevoGrid.DataType[], columns: RevoGrid.ColumnRegular[], filterItems: MultiFilterItem) {
const columnsToUpdate: RevoGrid.ColumnRegular[] = [];
// todo improvement: loop through collection of props
columns.forEach(rgCol => {
const column = { ...rgCol };
const hasFilter = filterItems[column.prop];
if (column[FILTER_PROP] && !hasFilter) {
delete column[FILTER_PROP];
columnsToUpdate.push(column);
}
if (!column[FILTER_PROP] && hasFilter) {
columnsToUpdate.push(column);
column[FILTER_PROP] = true;
}
});
const itemsToFilter = this.getRowMultiFilter(items, filterItems);
// check is filter event prevented
const { defaultPrevented } = this.emit('beforefiltertrimmed', { collection, itemsToFilter, source: items });
if (defaultPrevented) {
return;
}

// const test = { 1: true };
// check is trimmed event prevented
const isAddedEvent = await this.revogrid.addTrimmed(itemsToFilter, FILTER_TRIMMED_TYPE);
if (isAddedEvent.defaultPrevented) {
return;
}

// applies the hasFilter to the columns to show filter icon
await this.revogrid.updateColumns(columnsToUpdate);
this.emit('afterFilterApply');
}

async clearFiltering() {
this.filterCollection = {};
await this.runFiltering();
Expand All @@ -265,7 +311,7 @@ export default class FilterPlugin extends BasePlugin {
const columns = await this.revogrid.getColumns();
return {
source,
columns
columns,
};
}

Expand All @@ -282,4 +328,60 @@ export default class FilterPlugin extends BasePlugin {
});
return trimmed;
}
private getRowMultiFilter(rows: RevoGrid.DataType[], filterItems: MultiFilterItem) {
const propKeys = Object.keys(filterItems);

const trimmed: Record<number, boolean> = {};
let propFilterSatisfiedCount: number = 0;
let lastFilterResults: boolean[] = [];

// each rows
rows.forEach((model, rowIndex) => {
// working on all props
for (const prop of propKeys) {
const propFilters = filterItems[prop];

propFilterSatisfiedCount = 0;
lastFilterResults = [];

// testing each filter for a prop
for (const [filterIndex, filterData] of propFilters.entries()) {
// the filter LogicFunction based on the type
const filter = filterEntities[filterData.type];

// THE MAGIC OF FILTERING IS HERE
if (filterData.relation === 'or') {
lastFilterResults = [];
if (filter(model[prop], filterData.value)) {
continue;
}
propFilterSatisfiedCount++;
} else if (filterData.relation === 'and') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need else if here? can't it be just else and so if no relation it'll be treated as and?

// 'and' relation will need to know the next filter
// so we save this current filter to include it in the next filter
lastFilterResults.push(!filter(model[prop], filterData.value));

// check first if we have a filter on the next index to pair it with this current filter
const nextFilterData = propFilters[filterIndex + 1];
// stop the sequence if there is no next filter or if the next filter is not an 'and' relation
if (!nextFilterData || nextFilterData.relation !== 'and') {
// let's just continue since for sure propFilterSatisfiedCount cannot be satisfied
if (lastFilterResults.indexOf(true) === -1) {
lastFilterResults = [];
continue;
}

// we need to add all of the lastFilterResults since we need to satisfy all
propFilterSatisfiedCount += lastFilterResults.length;
lastFilterResults = [];
}
}
} // end of propFilters forEach

// add to the list of removed/trimmed rows of filter condition is satisfied
if (propFilterSatisfiedCount === propFilters.length) trimmed[rowIndex] = true;
} // end of for-of propKeys
});
return trimmed;
}
}
Loading