Skip to content

Commit

Permalink
feat(filter): add new rangeDate Filter
Browse files Browse the repository at this point in the history
  • Loading branch information
Ghislain Beaulac authored and Ghislain Beaulac committed Aug 5, 2019
1 parent 41fbb9b commit 0878569
Show file tree
Hide file tree
Showing 17 changed files with 619 additions and 24 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"@ngx-translate/http-loader": "^4.0.0",
"core-js": "^2.6.1",
"dompurify": "^1.0.9",
"flatpickr": "^4.5.0",
"flatpickr": ">=4.5.0",
"font-awesome": "^4.7.0",
"jquery": ">=3.2.1",
"jquery-ui-dist": "^1.12.1",
Expand Down
2 changes: 2 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { GridHeaderMenuComponent } from './examples/grid-headermenu.component';
import { GridLocalizationComponent } from './examples/grid-localization.component';
import { GridMenuComponent } from './examples/grid-menu.component';
import { GridOdataComponent } from './examples/grid-odata.component';
import { GridRangeComponent } from './examples/grid-range.component';
import { GridRemoteComponent } from './examples/grid-remote.component';
import { GridRowDetailComponent } from './examples/grid-rowdetail.component';
import { GridRowMoveComponent } from './examples/grid-rowmove.component';
Expand Down Expand Up @@ -49,6 +50,7 @@ const routes: Routes = [
{ path: 'localization', component: GridLocalizationComponent },
{ path: 'clientside', component: GridClientSideComponent },
{ path: 'odata', component: GridOdataComponent },
{ path: 'range', component: GridRangeComponent },
{ path: 'remote', component: GridRemoteComponent },
{ path: 'rowdetail', component: GridRowDetailComponent },
{ path: 'rowmove', component: GridRowMoveComponent },
Expand Down
26 changes: 19 additions & 7 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<nav class="navbar navbar-default navbar-fixed-top"
role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse" menu-collapse>
<button type="button"
class="navbar-toggle"
data-toggle="collapse"
data-target=".navbar-collapse"
menu-collapse>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="https://github.com/ghiscoding/Angular-Slickgrid">
<a class="navbar-brand"
href="https://github.com/ghiscoding/Angular-Slickgrid">
<i class="fa fa-github"></i>
<span>{{title}}</span>
</a>
<span style="position: relative; top: 15px">
<iframe allowtransparency="true" scrolling="no" frameborder="0"
src="https://buttons.github.io/buttons.html#href=https%3A%2F%2Fgithub.51.al%2Fghiscoding%2FAngular-Slickgrid&amp;aria-label=Star%20ghiscoding%2FAngular-Slickgrid%20on%20GitHub&amp;data-icon=octicon-star&amp;data-text=Star&amp;data-show-count=true"
style="width: 90px; height: 20px; border: none;"></iframe>
<iframe allowtransparency="true"
scrolling="no"
frameborder="0"
src="https://buttons.github.io/buttons.html#href=https%3A%2F%2Fgithub.51.al%2Fghiscoding%2FAngular-Slickgrid&amp;aria-label=Star%20ghiscoding%2FAngular-Slickgrid%20on%20GitHub&amp;data-icon=octicon-star&amp;data-text=Star&amp;data-show-count=true"
style="width: 90px; height: 20px; border: none;"></iframe>
</span>
</div>
<div class="navbar-collapse collapse">
Expand All @@ -28,7 +36,8 @@

<div class="container-fluid">
<div class="panel-wm">
<section id="panel-left" class="panel-wm-left">
<section id="panel-left"
class="panel-wm-left">
<ul class="nav nav-pills nav-stacked">
<li routerLinkActive="active">
<a [routerLink]="['/basic']">1- Basic Grid / 2 Grids</a>
Expand Down Expand Up @@ -102,6 +111,9 @@
<li routerLinkActive="active">
<a [routerLink]="['/gridtabs']">24- within Bootstrap Tabs</a>
</li>
<li routerLinkActive="active">
<a [routerLink]="['/range']">25- Filter by Range of Values</a>
</li>
</ul>
</section>

Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { GridHeaderMenuComponent } from './examples/grid-headermenu.component';
import { GridLocalizationComponent } from './examples/grid-localization.component';
import { GridMenuComponent } from './examples/grid-menu.component';
import { GridOdataComponent } from './examples/grid-odata.component';
import { GridRangeComponent } from './examples/grid-range.component';
import { GridRemoteComponent } from './examples/grid-remote.component';
import { GridRowDetailComponent } from './examples/grid-rowdetail.component';
import { GridRowMoveComponent } from './examples/grid-rowmove.component';
Expand Down Expand Up @@ -96,6 +97,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
GridLocalizationComponent,
GridMenuComponent,
GridOdataComponent,
GridRangeComponent,
GridRemoteComponent,
GridRowDetailComponent,
GridRowMoveComponent,
Expand Down
12 changes: 6 additions & 6 deletions src/app/examples/grid-clientside.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ export class GridClientSideComponent implements OnInit {
}
},
{
id: 'complete', name: '% Complete', field: 'percentComplete', /*formatter: Formatters.percentCompleteBar,*/ minWidth: 70, type: FieldType.number, sortable: true,
filterable: true, filter: { model: Filters.input /*Filters.compoundInputNumber*/ }
id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, minWidth: 70, type: FieldType.number, sortable: true,
filterable: true, filter: { model: Filters.compoundInputNumber }
},
{
id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: true,
Expand Down Expand Up @@ -172,10 +172,10 @@ export class GridClientSideComponent implements OnInit {
// use columnDef searchTerms OR use presets as shown below
presets: {
filters: [
// { columnId: 'duration', searchTerms: [10, 220] },
{ columnId: 'complete', searchTerms: ['5..10'] },
// { columnId: 'usDateShort', operator: '<', searchTerms: ['4/20/25'] },
// { columnId: 'effort-driven', searchTerms: [true] }
{ columnId: 'duration', searchTerms: [10, 220] },
// { columnId: 'complete', searchTerms: ['5'], operator: '>' },
{ columnId: 'usDateShort', operator: '<', searchTerms: ['4/20/25'] },
// { columnId: 'effort-driven', searchTerms: [true] },
],
sorters: [
{ columnId: 'duration', direction: 'DESC' },
Expand Down
21 changes: 21 additions & 0 deletions src/app/examples/grid-range.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="container-fluid">
<h2>{{title}}</h2>
<div class="subtitle" [innerHTML]="subTitle"></div>

<br/>
<span *ngIf="statistics" style="margin-right: 10px">
<b>Statistics:</b> {{statistics.startTime | date: 'yyyy-MM-dd HH:mm aaaaa\'m\''}} | {{statistics.itemCount}} of {{statistics.totalItemCount}} items
</span>
<button class="btn btn-default btn-sm" (click)="angularGrid.filterService.clearFilters()">Clear Filters</button>
<button class="btn btn-default btn-sm" (click)="angularGrid.sortService.clearSorting()">Clear Sorting</button>

<angular-slickgrid gridId="grid2"
[columnDefinitions]="columnDefinitions"
[gridOptions]="gridOptions"
[dataset]="dataset"
(onAngularGridCreated)="angularGridReady($event)"
(onGridStateChanged)="gridStateChanged($event)"
(onBeforeGridDestroy)="saveCurrentGridState($event)"
(sgOnRowCountChanged)="refreshStatistics($event.detail.eventData, $event.detail.args)">
</angular-slickgrid>
</div>
197 changes: 197 additions & 0 deletions src/app/examples/grid-range.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
import { AngularGridInstance, Column, FieldType, Filters, Formatters, GridOption, GridStateChange, OperatorType, Statistic } from '../modules/angular-slickgrid';
import { CustomInputFilter } from './custom-inputFilter';
import * as moment from 'moment-mini';

function randomBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const NB_ITEMS = 500;
const URL_SAMPLE_COLLECTION_DATA = 'assets/data/collection_500_numbers.json';

@Component({
templateUrl: './grid-range.component.html'
})
export class GridRangeComponent implements OnInit {
title = 'Example 25: Filtering from Range of Search Values';
subTitle = `
This demo shows how to use Filters with Range of Search Values (for example, shown below, filters by duration of 1 to 5 days)
<br/>
<ul class="small">
<li>All column types support the following operators: (>, >=, <, <=, <>, !=, =, ==, *)
<ul>
<li>For the range in a text input filters, you can use 2 dots (..) to represent a range</li>
<li>example: type "5..10" to filter between 5 and 10 (non-inclusive)</li>
</ul>
<ul>
<li>by default the range are not inclusive but if you wish to be inclusive, you can see the Operator to "operator: 'RangeInclusive'" or "operator: OperatoryType.rangeIncluside"</li>
<li>you can also set the inverse (and default) to "operator: 'RangeNotInclusive'" or "operator: OperatoryType.rangeNotIncluside"</li>
</ul>
<li>Date Range with Flatpickr Date Picker</li>
</ul>
`;

angularGrid: AngularGridInstance;
columnDefinitions: Column[];
gridOptions: GridOption;
dataset: any[];
statistics: Statistic;

constructor(private http: HttpClient, private translate: TranslateService) { }

ngOnInit(): void {
this.columnDefinitions = [
{
id: 'title', name: 'Title', field: 'title', sortable: true, minWidth: 55,
type: FieldType.string, filterable: true, filter: { model: Filters.compoundInputText }
},
{
id: 'description', name: 'Description', field: 'description', filterable: true, sortable: true, minWidth: 80,
type: FieldType.string,
filter: {
model: new CustomInputFilter(), // create a new instance to make each Filter independent from each other
enableTrimWhiteSpace: true // or use global "enableFilterTrimWhiteSpace" to trim on all Filters
}
},
{
id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number, exportCsvForceToKeepAsString: true,
minWidth: 55,
filterable: true,
filter: {
collectionAsync: this.http.get<{ option: string; value: string; }[]>(URL_SAMPLE_COLLECTION_DATA),
customStructure: {
value: 'value',
label: 'label',
optionLabel: 'value', // if selected text is too long, we can use option labels instead
labelSuffix: 'text',
},
collectionOptions: {
separatorBetweenTextLabels: ' ',
filterResultAfterEachPass: 'chain' // options are "merge" or "chain" (defaults to "chain")
},
model: Filters.multipleSelect,

// we could add certain option(s) to the "multiple-select" plugin
filterOptions: {
maxHeight: 250,
width: 175,

// if we want to display shorter text as the selected text (on the select filter itself, parent element)
// we can use "useSelectOptionLabel" or "useSelectOptionLabelToHtml" the latter will parse html
useSelectOptionLabelToHtml: true
}
}
},
{
id: 'complete', name: '% Complete', field: 'percentComplete', /*formatter: Formatters.percentCompleteBar,*/ minWidth: 70, type: FieldType.number, sortable: true,
filterable: true, filter: { model: Filters.input /*Filters.compoundInputNumber*/ }
},
{
id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: true,
type: FieldType.date, filterable: true, filter: { model: Filters.compoundDate }
},
{
id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: true,
type: FieldType.date,
filterable: true,
filter: {
model: Filters.rangeDate,
}
},
{
id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven.isEffort', minWidth: 85, maxWidth: 85,
exportWithFormatter: true, // you can set this property in the column definition OR in the grid options, column def has priority over grid options
filterable: true,
filter: {
collection: [{ value: '', label: '' }, { value: true, label: 'TRUE' }, { value: false, labelKey: 'False' }],
model: Filters.singleSelect,
filterOptions: {
autoDropWidth: true
}
}
}
];

this.gridOptions = {
autoResize: {
containerId: 'demo-container',
sidePadding: 15
},
enableExcelCopyBuffer: true,
enableFiltering: true,
// enableFilterTrimWhiteSpace: true,
i18n: this.translate,

// use columnDef searchTerms OR use presets as shown below
presets: {
filters: [
// { columnId: 'duration', searchTerms: [10, 220] },
{ columnId: 'complete', searchTerms: ['15..75'] },
{ columnId: 'finish', operator: 'RangeInclusive', searchTerms: [`${moment().add(-2, 'days').format('YYYY-MM-DD')}..${moment().add(20, 'days').format('YYYY-MM-DD')}`] },
// { columnId: 'effort-driven', searchTerms: [true] }
],
sorters: [
{ columnId: 'duration', direction: 'DESC' },
{ columnId: 'complete', direction: 'ASC' }
],
}
};

// mock a dataset
this.dataset = this.mockData(NB_ITEMS);
}

angularGridReady(angularGrid: AngularGridInstance) {
this.angularGrid = angularGrid;
}

mockData(itemCount, startingIndex = 0): any[] {
// mock a dataset
const tempDataset = [];
for (let i = startingIndex; i < (startingIndex + itemCount); i++) {
const randomDuration = Math.round(Math.random() * 100);
const randomYear = randomBetween(moment().year(), moment().year() + 1);
const randomMonth = randomBetween(1, 12);
const randomDay = randomBetween(10, 28);
const randomPercent = randomBetween(0, 100);

tempDataset.push({
id: i,
title: 'Task ' + i,
description: (i % 5) ? 'desc ' + i : null, // also add some random to test NULL field
duration: randomDuration,
percentComplete: randomPercent,
percentCompleteNumber: randomPercent,
start: (i % 4) ? null : new Date(randomYear, randomMonth, randomDay), // provide a Date format
finish: new Date(randomYear, (randomMonth + 1), randomDay),
effortDriven: (i % 5 === 0) ? true : false,
});
}

return tempDataset;
}

/** Dispatched event of a Grid State Changed event */
gridStateChanged(gridState: GridStateChange) {
console.log('Client sample, Grid State changed:: ', gridState);
}

/** Save current Filters, Sorters in LocaleStorage or DB */
saveCurrentGridState(grid) {
console.log('Client sample, last Grid State:: ', this.angularGrid.gridStateService.getCurrentGridState());
}

refreshStatistics(e, args) {
if (args && args.current > 0) {
setTimeout(() => {
this.statistics = {
startTime: new Date(),
itemCount: args && args.current,
totalItemCount: this.dataset.length
};
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,34 @@ describe('dateFilterCondition method', () => {
const output = dateFilterCondition(options);
expect(output).toBe(false);
});

it('should return True when input value is in the range of search terms', () => {
const options = { dataKey: '', operator: 'EQ', cellValue: '1993-12-25', fieldType: FieldType.number, searchTerms: ['1993-12-01..1993-12-31'] } as FilterConditionOption;
const output = dateFilterCondition(options);
expect(output).toBe(true);
});

it('should return False when input value is not in the range of search terms', () => {
const options = { dataKey: '', operator: 'EQ', cellValue: '1993-11-25', fieldType: FieldType.number, searchTerms: ['1993-12-01..1993-12-31'] } as FilterConditionOption;
const output = dateFilterCondition(options);
expect(output).toBe(false);
});

it('should return True when input value equals the search terms min inclusive value and operator is set to "rangeInclusive"', () => {
const options = { dataKey: '', operator: 'RangeInclusive', cellValue: '1993-12-01', fieldType: FieldType.number, searchTerms: ['1993-12-01..1993-12-31'] } as FilterConditionOption;
const output = dateFilterCondition(options);
expect(output).toBe(true);
});

it('should return False when input value equals the search terms min inclusive value and operator is set to "RangeNotInclusive"', () => {
const options = { dataKey: '', operator: 'RangeNotInclusive', cellValue: '1993-12-01', fieldType: FieldType.number, searchTerms: ['1993-12-01..1993-12-31'] } as FilterConditionOption;
const output = dateFilterCondition(options);
expect(output).toBe(false);
});

it('should return False when any of the 2 search term value is not a valid date', () => {
const options = { dataKey: '', operator: 'RangeNotInclusive', cellValue: '1993-12-05', fieldType: FieldType.number, searchTerms: ['1993-12-01..1993-12-60'] } as FilterConditionOption;
const output = dateFilterCondition(options);
expect(output).toBe(false);
});
});
Loading

0 comments on commit 0878569

Please sign in to comment.