Skip to content

Commit

Permalink
feat: add new optional filterShortcuts to Column Filter (#365)
Browse files Browse the repository at this point in the history
* feat: add new optional `filterShortcuts` to Column Filter
  • Loading branch information
ghiscoding committed Jun 18, 2024
1 parent d415a51 commit 88b8e93
Show file tree
Hide file tree
Showing 14 changed files with 677 additions and 380 deletions.
1 change: 1 addition & 0 deletions docs/column-functionalities/filters/compound-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Update Filters Dynamically](input-filter.md#update-filters-dynamically)
- [How to avoid filtering when only Operator dropdown is changed?](#how-to-avoid-filtering-when-only-operator-dropdown-is-changed)
- [Custom Filter Predicate](input-filter.md#custom-filter-predicate)
- [Filter Shortcuts](input-filter.md#filter-shortcuts)

### Description
Compound filters are a combination of 2 elements (Operator Select + Input Filter) used as a filter on a column. This is very useful to make it obvious to the user that there are Operator available and even more useful with a date picker (`Vanilla-Calendar`).
Expand Down
36 changes: 36 additions & 0 deletions docs/column-functionalities/filters/input-filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [Debounce/Throttle Text Search (wait for user to stop typing before filtering)](#debouncethrottle-text-search-wait-for-user-to-stop-typing-before-filtering)
- [Ignore Locale Accent in Text Filter/Sorting](#ignore-locale-accent-in-text-filtersorting)
- [Custom Filter Predicate](#custom-filter-predicate)
- [Filter Shortcuts](#filter-shortcuts)

### Description
Input filter is the default filter when enabling filters.
Expand Down Expand Up @@ -226,3 +227,38 @@ this.columnDefinitions = [
The custom filter predicate above was to answer a Stack Overflow question and will work similarly to an SQL LIKE matcher (it's not perfect and probably requires more work but is enough to demo the usage of a custom filter predicate)
![image](https://github.com/ghiscoding/slickgrid-universal/assets/643976/3e77774e-3a9f-4ca4-bca7-50a033a4b48d)
### Filter Shortcuts
User can declare some Filter Shortcuts, that will be added to the Header Menu of the Column it was assigned. These shortcuts are simply a list of filter search values (e.g. Filter the Blank/Non-Blanks Values), the end user can type the same search values themselves but the shortcuts are simply meant to be quicker without having to know what to type (e.g. Filter Current Year).
The shortcuts can be declared via an array that must include at least a `title` (or `titleKey`) a `searchTerms` array and lastly an optional `operator` can also be provided. The available properties of these shortcut is a merge of Header Menu Item interface (except `command` and `action` which are reserved and assigned internally) and of course the 3 properties mentioned above. The declaration is very similar to how we use it when declaring Grid Presets as shown below
```ts
this.columnDefinitions = [
{
id: 'country', name: 'Country', field: 'country',
filter: {
model: Filters.inputText,
filterShortcuts: [
{ title: 'Blank Values', searchTerms: ['A'], operator: '<', iconCssClass: 'mdi mdi-filter-minus-outline', },
{ title: 'Non-Blank Values', searchTerms: ['A'], operator: '>', iconCssClass: 'mdi mdi-filter-plus-outline', },
]
},
},
{
id: 'finish', name: 'Finish', field: 'finish',
filter: {
model: Filters.dateRange,
filterShortcuts: [
{
// using Locale translations & Tempo to calculate next 30 days
titleKey: 'NEXT_30_DAYS',
iconCssClass: 'mdi mdi-calendar',
searchTerms: [tempoFormat(new Date(), 'YYYY-MM-DD'), tempoFormat(addDay(new Date(), 30), 'YYYY-MM-DD')],
},
]
},
},
];
```
1 change: 1 addition & 0 deletions docs/column-functionalities/filters/range-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Using a Date Range](#using-a-date-range-filter)
- [Update Filters Dynamically](input-filter.md#update-filters-dynamically)
- [Custom Filter Predicate](input-filter.md#custom-filter-predicate)
- [Filter Shortcuts](input-filter.md#filter-shortcuts)

### Introduction
Range filters allows you to search for a value between 2 min/max values, the 2 most common use case would be to filter between 2 numbers or dates, you can do that with the Slider & Date Range Filters. The range can also be defined as inclusive (`>= 0 and <= 10`) or exclusive (`> 0 and < 10`), the default is exclusive but you can change that, see below for more info.
Expand Down
1 change: 1 addition & 0 deletions docs/column-functionalities/filters/select-filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [Query against a different field](#query-against-another-field-property)
- [Update Filters Dynamically](input-filter.md#update-filters-dynamically)
- [Custom Filter Predicate](input-filter.md#custom-filter-predicate)
- [Filter Shortcuts](input-filter.md#filter-shortcuts)

### Demo
[Demo Page](https://ghiscoding.github.io/slickgrid-react/#/slickgrid/Example4) / [Demo Component](https://github.com/ghiscoding/slickgrid-react/blob/master/src/examples/slickgrid/Example4.tsx)
Expand Down
46 changes: 23 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@
"/src/slickgrid-react"
],
"dependencies": {
"@slickgrid-universal/common": "~5.1.0",
"@slickgrid-universal/custom-footer-component": "~5.1.0",
"@slickgrid-universal/empty-warning-component": "~5.1.0",
"@slickgrid-universal/event-pub-sub": "~5.0.0",
"@slickgrid-universal/pagination-component": "~5.1.0",
"@slickgrid-universal/common": "~5.2.0",
"@slickgrid-universal/custom-footer-component": "~5.2.0",
"@slickgrid-universal/empty-warning-component": "~5.2.0",
"@slickgrid-universal/event-pub-sub": "~5.2.0",
"@slickgrid-universal/pagination-component": "~5.2.0",
"dequal": "^2.0.3",
"i18next": "^23.11.5",
"sortablejs": "^1.15.2"
Expand All @@ -105,18 +105,18 @@
"@formkit/tempo": "^0.1.1",
"@popperjs/core": "^2.11.8",
"@release-it/conventional-changelog": "^8.0.1",
"@slickgrid-universal/composite-editor-component": "~5.1.0",
"@slickgrid-universal/custom-tooltip-plugin": "~5.1.0",
"@slickgrid-universal/excel-export": "~5.1.0",
"@slickgrid-universal/graphql": "~5.1.0",
"@slickgrid-universal/odata": "~5.1.0",
"@slickgrid-universal/rxjs-observable": "~5.1.0",
"@slickgrid-universal/text-export": "~5.1.0",
"@slickgrid-universal/composite-editor-component": "~5.2.0",
"@slickgrid-universal/custom-tooltip-plugin": "~5.2.0",
"@slickgrid-universal/excel-export": "~5.2.0",
"@slickgrid-universal/graphql": "~5.2.0",
"@slickgrid-universal/odata": "~5.2.0",
"@slickgrid-universal/rxjs-observable": "~5.2.0",
"@slickgrid-universal/text-export": "~5.2.0",
"@types/dompurify": "^3.0.5",
"@types/fnando__sparkline": "^0.3.7",
"@types/i18next-xhr-backend": "^1.4.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.2",
"@types/node": "^20.14.4",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/sortablejs": "^1.15.8",
Expand All @@ -131,13 +131,13 @@
"css-loader": "^7.1.2",
"custom-event-polyfill": "^1.0.7",
"cypress": "^13.11.0",
"cypress-real-events": "^1.12.0",
"cypress-real-events": "^1.13.0",
"dompurify": "^3.1.5",
"esbuild-loader": "^4.1.0",
"eslint": "^9.4.0",
"esbuild-loader": "^4.2.0",
"eslint": "^9.5.0",
"eslint-plugin-cypress": "^3.3.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^17.8.1",
"eslint-plugin-n": "^17.9.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"fetch-jsonp": "^1.3.0",
"html-loader": "5.0.0",
Expand All @@ -154,21 +154,21 @@
"promise-polyfill": "^8.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.1",
"react-i18next": "^14.1.2",
"react-router-dom": "^6.23.1",
"regenerator-runtime": "^0.14.1",
"release-it": "^17.3.0",
"rimraf": "^5.0.7",
"rxjs": "^7.8.1",
"sass": "^1.77.4",
"sass": "^1.77.6",
"sass-loader": "^14.2.1",
"serve": "^14.2.3",
"style-loader": "4.0.0",
"ts-jest": "^29.1.4",
"ts-jest": "^29.1.5",
"ts-loader": "^9.5.1",
"typescript": "^5.4.5",
"typescript-eslint": "^7.12.0",
"webpack": "^5.91.0",
"typescript-eslint": "^7.13.1",
"webpack": "^5.92.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4"
},
Expand All @@ -179,4 +179,4 @@
"resolutions": {
"caniuse-lite": "1.0.30001629"
}
}
}
9 changes: 8 additions & 1 deletion src/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"EXPORT_TO_EXCEL": "Export to Excel",
"EXPORT_TO_TAB_DELIMITED": "Export in Text format (Tab delimited)",
"EXPORT_TO_TEXT_FORMAT": "Export in Text format",
"FILTER_SHORTCUTS": "Filter Shortcuts",
"FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} of {{totalItems}} items",
"FORCE_FIT_COLUMNS": "Force fit columns",
"FREEZE_COLUMNS": "Freeze Columns",
Expand Down Expand Up @@ -69,6 +70,8 @@
},
"INFORMATION": "Billing Information"
},
"BLANK_VALUES": "Blank Values",
"NON_BLANK_VALUES": "Non-Blank Values",
"CUSTOM_COMMANDS": "Custom Commands",
"DURATION": "Duration",
"COMPANY": "Company",
Expand All @@ -81,21 +84,25 @@
"FALSE": "False",
"FEMALE": "Female",
"FINISH": "Finish",
"FUTURE": "Future",
"GENDER": "Gender",
"HELP": "Help",
"HIGH": "High",
"LOW": "Low",
"MEDIUM": "Medium",
"MALE": "Male",
"NAME": "Name",
"NEXT_20_DAYS": "Next 20 days",
"NONE": "None",
"PAST": "Past",
"PERCENT_COMPLETE": "% Complete",
"PRIORITY": "Priority",
"START": "Start",
"TASK_X": "Task {{x}}",
"TITLE": "Title",
"TODAY": "Today",
"TRUE": "True",
"X_DAY_PLURAL": "{{x}} day{{plural}}",
"RBE_BTN_UPDATE": "Update the current row",
"RBE_BTN_CANCEL": "Cancel changes of the current row"
}
}
9 changes: 8 additions & 1 deletion src/assets/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"EXPORT_TO_EXCEL": "Exporter vers Excel",
"EXPORT_TO_TAB_DELIMITED": "Exporter en format texte (délimité par tabulation)",
"EXPORT_TO_TEXT_FORMAT": "Exporter en format texte",
"FILTER_SHORTCUTS": "Raccourcis de filtre",
"FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} de {{totalItems}} éléments",
"FORCE_FIT_COLUMNS": "Ajustement forcé des colonnes",
"FREEZE_COLUMNS": "Geler les colonnes",
Expand Down Expand Up @@ -69,6 +70,8 @@
},
"INFORMATION": "Information de Facturation"
},
"BLANK_VALUES": "Valeurs nulles",
"NON_BLANK_VALUES": "Valeurs non-nulles",
"DURATION": "Durée",
"COMPANY": "Compagnie",
"COMPLETED": "Terminé",
Expand All @@ -81,22 +84,26 @@
"FALSE": "Faux",
"FEMALE": "Féminin",
"FINISH": "Fin",
"FUTURE": "Future",
"GENDER": "Sexe",
"HELP": "Aide",
"HIGH": "Haut",
"LOW": "Bas",
"MEDIUM": "Moyen",
"MALE": "Masculin",
"NAME": "Nom",
"NEXT_20_DAYS": "20 prochain jours",
"NONE": "Aucun",
"PAST": "Passé",
"PERCENT_COMPLETE": "% Achevée",
"PRIORITY": "Priorité",
"START": "Début",
"TASK_X": "Tâche {{x}}",
"TITLE": "Titre",
"TITLE.NAME": "Nom du Titre",
"TODAY": "Aujourd'hui",
"TRUE": "Vrai",
"X_DAY_PLURAL": "{{x}} journée{{plural}}",
"RBE_BTN_UPDATE": "Mettre à jour la ligne actuelle",
"RBE_BTN_CANCEL": "Annuler la ligne actuelle"
}
}
19 changes: 16 additions & 3 deletions src/examples/slickgrid/Example15.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { format as tempoFormat } from '@formkit/tempo';
import i18next, { TFunction } from 'i18next';
import React from 'react';
import { withTranslation } from 'react-i18next';
Expand Down Expand Up @@ -154,7 +155,11 @@ class Example15 extends React.Component<Props, State> {
id: 'description', name: 'Description', field: 'description', filterable: true, sortable: true, minWidth: 80, width: 100,
type: FieldType.string,
filter: {
model: Filters.input
model: Filters.input,
filterShortcuts: [
{ titleKey: 'BLANK_VALUES', searchTerms: ['< A'], iconCssClass: 'mdi mdi-filter-minus-outline', },
{ titleKey: 'NON_BLANK_VALUES', searchTerms: ['> A'], iconCssClass: 'mdi mdi-filter-plus-outline', },
]
}
},
{
Expand All @@ -178,7 +183,14 @@ class Example15 extends React.Component<Props, State> {
},
{
id: 'start', name: 'Start', field: 'start', nameKey: 'START', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: true, width: 100,
type: FieldType.date, filterable: true, filter: { model: Filters.compoundDate }
type: FieldType.date, filterable: true,
filter: {
model: Filters.compoundDate,
filterShortcuts: [
{ titleKey: 'PAST', searchTerms: [tempoFormat(new Date(), 'YYYY-MM-DD')], operator: '<', iconCssClass: 'mdi mdi-calendar', },
{ titleKey: 'FUTURE', searchTerms: [tempoFormat(new Date(), 'YYYY-MM-DD')], operator: '>', iconCssClass: 'mdi mdi-calendar-clock', },
]
}
},
{
id: 'completed', field: 'completed', nameKey: 'COMPLETED', minWidth: 85, maxWidth: 85, formatter: Formatters.checkmarkMaterial, width: 100,
Expand All @@ -195,10 +207,11 @@ class Example15 extends React.Component<Props, State> {

getData(count: number) {
// mock a dataset
const currentYear = new Date().getFullYear();
const tmpData: any[] = [];
for (let i = 0; i < count; i++) {
const randomDuration = Math.round(Math.random() * 100);
const randomYear = randomBetween(2000, 2025);
const randomYear = randomBetween(currentYear - 15, currentYear + 8);
const randomYearShort = randomBetween(10, 25);
const randomMonth = randomBetween(1, 12);
const randomMonthStr = (randomMonth < 10) ? `0${randomMonth}` : randomMonth;
Expand Down
23 changes: 15 additions & 8 deletions src/examples/slickgrid/Example6.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addDay, format } from '@formkit/tempo';
import { addDay, format as tempoFormat } from '@formkit/tempo';
import { GraphqlService, GraphqlPaginatedResult, GraphqlServiceApi, GraphqlServiceOption, } from '@slickgrid-universal/graphql';
import i18next, { TFunction } from 'i18next';
import {
Expand Down Expand Up @@ -151,6 +151,13 @@ class Example6 extends React.Component<Props, State> {
filterable: true,
filter: {
model: Filters.dateRange,
filterShortcuts: [
{
titleKey: 'NEXT_20_DAYS',
iconCssClass: 'mdi mdi-calendar',
searchTerms: [tempoFormat(new Date(), 'YYYY-MM-DD'), tempoFormat(addDay(new Date(), 20), 'YYYY-MM-DD')],
},
]
}
},
];
Expand All @@ -170,8 +177,8 @@ class Example6 extends React.Component<Props, State> {
}

getGridOptions() {
const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD');
const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD');
const presetLowestDay = tempoFormat(addDay(new Date(), -2), 'YYYY-MM-DD');
const presetHighestDay = tempoFormat(addDay(new Date(), 20), 'YYYY-MM-DD');

return {
enableFiltering: true,
Expand Down Expand Up @@ -369,8 +376,8 @@ class Example6 extends React.Component<Props, State> {
}

setFiltersDynamically() {
const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD');
const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD');
const presetLowestDay = tempoFormat(addDay(new Date(), -2), 'YYYY-MM-DD');
const presetHighestDay = tempoFormat(addDay(new Date(), 20), 'YYYY-MM-DD');

// we can Set Filters Dynamically (or different filters) afterward through the FilterService
this.reactGrid?.filterService.updateFilters([
Expand All @@ -391,8 +398,8 @@ class Example6 extends React.Component<Props, State> {
}

resetToOriginalPresets() {
const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD');
const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD');
const presetLowestDay = tempoFormat(addDay(new Date(), -2), 'YYYY-MM-DD');
const presetHighestDay = tempoFormat(addDay(new Date(), 20), 'YYYY-MM-DD');

this.reactGrid.filterService.updateFilters([
// you can use OperatorType or type them as string, e.g.: operator: 'EQ'
Expand Down Expand Up @@ -527,7 +534,7 @@ class Example6 extends React.Component<Props, State> {
</div>
<br />
{this.state.metrics && <span><><b>Metrics: </b>
{this.state.metrics.endTime ? format(this.state.metrics.endTime, 'YYYY-MM-DD HH:mm:ss') : ''}
{this.state.metrics.endTime ? tempoFormat(this.state.metrics.endTime, 'YYYY-MM-DD HH:mm:ss') : ''}
| {this.state.metrics.executionTime}ms
| {this.state.metrics.totalItemCount} items </>
</span>}
Expand Down
2 changes: 1 addition & 1 deletion test/cypress/e2e/example05.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ describe('Example 5 - OData Grid', () => {

cy.window().then((win) => {
expect(win.console.log).to.have.callCount(2);
expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', operator: 'Contains', searchTerms: ['x'], targetSelector: 'input.form-control.filter-name.compound-input' }], type: 'filter' });
expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', operator: 'Contains', searchTerms: ['x'], targetSelector: 'input.form-control.filter-name.compound-input.filled' }], type: 'filter' });
expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: { pageNumber: 1, pageSize: 10 }, type: 'pagination' });
});
});
Expand Down
Loading

0 comments on commit 88b8e93

Please sign in to comment.