diff --git a/docs/column-functionalities/Formatters.md b/docs/column-functionalities/Formatters.md
index e02251c7..3604864c 100644
--- a/docs/column-functionalities/Formatters.md
+++ b/docs/column-functionalities/Formatters.md
@@ -52,6 +52,8 @@ A good example of a `Formatter` could be a timestamp or a `Date` object that we
* `Formatters.dateTimeShortUs`: Takes a Date object and displays it as an US Date+Time (without seconds) format (MM/DD/YYYY HH:mm:ss)
* `Formatters.dateTimeUsAmPm` : Takes a Date object and displays it as an US Date+Time+(am/pm) format (MM/DD/YYYY hh:mm:ss a)
* `Formatters.dateUtc` : Takes a Date object and displays it as a TZ format (YYYY-MM-DDThh:mm:ssZ)
+* `Formatters.date`: Base Date Formatter, this formatter is a bit different compare to other date formatter since this one requires the user to provide a custom format in `params.dateFormat`
+ - for example: `{ type: 'date', formatter: Formatters.date, params: { dateFormat: 'MMM DD, YYYY' }}`
* `Formatters.decimal`: Display the value as x decimals formatted, defaults to 2 decimals. You can pass "minDecimal" and/or "maxDecimal" to the "params" property.
* `Formatters.dollar`: Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value.
* `Formatters.dollarColored`: Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value, change color of text to red/green on negative/positive value
diff --git a/docs/column-functionalities/Sorting.md b/docs/column-functionalities/Sorting.md
index 8b5770bd..3b826f51 100644
--- a/docs/column-functionalities/Sorting.md
+++ b/docs/column-functionalities/Sorting.md
@@ -4,6 +4,7 @@
- [Custom Sort Comparer](#custom-sort-comparer)
- [Update Sorting Dynamically](#update-sorting-dynamically)
- [Dynamic Query Field](#dynamic-query-field)
+- [Sorting Dates](#sorting-dates)
- [Pre-Parse Date Columns for better perf](#pre-parse-date-columns-for-better-perf)
### Demo
@@ -134,6 +135,14 @@ queryFieldNameGetterFn: (dataContext) => {
},
```
+### Sorting Dates
+
+Date sorting should work out of the box as long as you provide the correct column field type. Note that there are various field types that can be provided and they all do different things. For the Sorting to work properly, you need to make sure to use the correct type for Date parsing to work accordingly. Below is a list of column definition types that you can provide:
+
+- `type`: input/output of date fields, or in other words, parsing/formatting dates with the field `type` provided
+- `outputType`: when a `type` is provided for parsing (i.e. from your dataset), you could use a different `outputType` to format your date differently
+- `saveOutputType`: if you already have a `type` and an `outputType` but you wish to save your date (i.e. save to DB) in yet another format
+
### Pre-Parse Date Columns for better perf
##### requires v8.8.0 and higher
@@ -149,11 +158,11 @@ The summary, is that we get a 10x boost **but** not only that, we also get an ex
#### Usage
-You can use the `preParseDateColumns` grid option, it can be either set as either `boolean` or a `string` but there's big distinction between the 2 approaches (both approaches will mutate the dataset).
+You can use the `preParseDateColumns` grid option, it can be set as either a `boolean` or a `string` but there's a big distinction between the 2 approaches as shown below (note that both approaches will mutate the dataset).
1. `string` (i.e. set to `"__"`, it will parse a `"start"` date string and assign it as a `Date` object to a new `"__start"` prop)
2. `boolean` (i.e. parse `"start"` date string and reassign it as a `Date` object on the same `"start"` prop)
-> **Note** this option **does not work** with Backend Services because it simply has no effect.
+> **Note** this option **does not work** with the Backend Service API because it simply has no effect.
For example if our dataset has 2 columns named "start" and "finish", then pre-parse the dataset,
@@ -179,7 +188,7 @@ data = [
]
```
-Which approach to choose? Both have pros and cons, overwriting the same props might cause problems with the column `type` that you use, you will have to give it a try yoursel. On the other hand, with the other approach, it will duplicate all date properties and take a bit more memory usage and when changing cells we'll need to make sure to keep these props in sync, however you will likely have less `type` issues.
+Which approach to choose? Both have pros and cons, overwriting the same props might cause problems with the column `type` that you use, you will have to give it a try yourself. On the other hand, with the other approach, it will duplicate all date properties and take a bit more memory usage and when changing cells we'll need to make sure to keep these props in sync, however you will likely have less `type` issues.
What happens when we do any cell changes (for our use case, it would be Create/Update), for any Editors we simply subscribe to the `onCellChange` change event and we re-parse the date strings when detected. We also subscribe to certain CRUD functions as long as they come from the `GridService` then all is fine... However, if you use the DataView functions directly then we have no way of knowing when to parse because these functions from the DataView don't have any events. Lastly, if we overwrite the entire dataset, we will also detect this (via an internal flag) and the next time you sort a date then the pre-parse kicks in again.
diff --git a/package.json b/package.json
index 3d1c0ff5..12de17ec 100644
--- a/package.json
+++ b/package.json
@@ -52,13 +52,13 @@
},
"dependencies": {
"@ngx-translate/core": "^15.0.0",
- "@slickgrid-universal/common": "5.14.0",
- "@slickgrid-universal/custom-footer-component": "5.14.0",
- "@slickgrid-universal/empty-warning-component": "5.14.0",
+ "@slickgrid-universal/common": "~5.14.0",
+ "@slickgrid-universal/custom-footer-component": "~5.14.0",
+ "@slickgrid-universal/empty-warning-component": "~5.14.0",
"@slickgrid-universal/event-pub-sub": "~5.13.0",
- "@slickgrid-universal/pagination-component": "5.14.0",
- "@slickgrid-universal/row-detail-view-plugin": "5.14.0",
- "@slickgrid-universal/rxjs-observable": "5.14.0",
+ "@slickgrid-universal/pagination-component": "~5.14.0",
+ "@slickgrid-universal/row-detail-view-plugin": "~5.14.0",
+ "@slickgrid-universal/rxjs-observable": "~5.14.0",
"dequal": "^2.0.3",
"rxjs": "^7.8.2"
},
diff --git a/src/app/examples/grid-infinite-json.component.html b/src/app/examples/grid-infinite-json.component.html
index 985f139e..83599d02 100644
--- a/src/app/examples/grid-infinite-json.component.html
+++ b/src/app/examples/grid-infinite-json.component.html
@@ -77,7 +77,7 @@
(onAngularGridCreated)="angularGridReady($event.detail)"
(onSort)="handleOnSort()"
(onScroll)="handleOnScroll($event.detail.args)"
- (onRowCountChanged)="refreshMetrics($event.detail.args)"
+ (onRowCountChanged)="handleOnRowCountChanged($event.detail.args)"
>
diff --git a/src/app/examples/grid-infinite-json.component.ts b/src/app/examples/grid-infinite-json.component.ts
index e9c51c43..03639def 100644
--- a/src/app/examples/grid-infinite-json.component.ts
+++ b/src/app/examples/grid-infinite-json.component.ts
@@ -13,6 +13,9 @@ import {
SortComparers,
SortDirectionNumber,
} from '../modules/angular-slickgrid';
+import { ExcelExportService } from '@slickgrid-universal/excel-export';
+
+import { randomNumber } from './utilities';
const FETCH_SIZE = 50;
@@ -66,19 +69,31 @@ export class GridInfiniteJsonComponent implements OnInit {
id: 'start',
name: 'Start',
field: 'start',
- formatter: Formatters.dateIso,
+ type: FieldType.date,
+ outputType: FieldType.dateIso, // for date picker format
+ formatter: Formatters.date,
exportWithFormatter: true,
+ params: { dateFormat: 'MMM DD, YYYY' },
+ sortable: true,
filterable: true,
- filter: { model: Filters.compoundDate },
+ filter: {
+ model: Filters.compoundDate,
+ },
},
{
id: 'finish',
name: 'Finish',
field: 'finish',
- formatter: Formatters.dateIso,
+ type: FieldType.date,
+ outputType: FieldType.dateIso, // for date picker format
+ formatter: Formatters.date,
exportWithFormatter: true,
+ params: { dateFormat: 'MMM DD, YYYY' },
+ sortable: true,
filterable: true,
- filter: { model: Filters.compoundDate },
+ filter: {
+ model: Filters.compoundDate,
+ },
},
{
id: 'effort-driven',
@@ -101,6 +116,8 @@ export class GridInfiniteJsonComponent implements OnInit {
enableGrouping: true,
editable: false,
rowHeight: 33,
+ enableExcelExport: true,
+ externalResources: [new ExcelExportService()],
};
}
@@ -156,18 +173,13 @@ export class GridInfiniteJsonComponent implements OnInit {
}
newItem(idx: number) {
- const randomYear = 2000 + Math.floor(Math.random() * 10);
- const randomMonth = Math.floor(Math.random() * 11);
- const randomDay = Math.floor(Math.random() * 29);
- const randomPercent = Math.round(Math.random() * 100);
-
return {
id: idx,
title: 'Task ' + idx,
duration: Math.round(Math.random() * 100) + '',
- percentComplete: randomPercent,
- start: new Date(randomYear, randomMonth + 1, randomDay),
- finish: new Date(randomYear + 1, randomMonth + 1, randomDay),
+ percentComplete: randomNumber(1, 12),
+ start: new Date(2020, randomNumber(1, 11), randomNumber(1, 28)),
+ finish: new Date(2022, randomNumber(1, 11), randomNumber(1, 28)),
effortDriven: idx % 5 === 0,
};
}
@@ -184,11 +196,15 @@ export class GridInfiniteJsonComponent implements OnInit {
setFiltersDynamically() {
// we can Set Filters Dynamically (or different filters) afterward through the FilterService
- this.angularGrid?.filterService.updateFilters([{ columnId: 'percentComplete', searchTerms: ['50'], operator: '>=' }]);
+ this.angularGrid?.filterService.updateFilters([{ columnId: 'start', searchTerms: ['2020-08-25'], operator: '<=' }]);
}
- refreshMetrics(args: OnRowCountChangedEventArgs) {
+ handleOnRowCountChanged(args: OnRowCountChangedEventArgs) {
if (this.angularGrid && args?.current >= 0) {
+ // we probably want to re-sort the data when we get new items
+ this.angularGrid.dataView?.reSort();
+
+ // update metrics
this.metrics.itemCount = this.angularGrid.dataView?.getFilteredItemCount() || 0;
this.metrics.totalItemCount = args.itemCount || 0;
}
diff --git a/src/app/examples/utilities.ts b/src/app/examples/utilities.ts
index 2c71a879..c62fdd32 100644
--- a/src/app/examples/utilities.ts
+++ b/src/app/examples/utilities.ts
@@ -1,3 +1,8 @@
+export function randomNumber(min: number, max: number, floor = true) {
+ const number = Math.random() * (max - min + 1) + min;
+ return floor ? Math.floor(number) : number;
+}
+
export function zeroPadding(input: string | number) {
const number = parseInt(input as string, 10);
return number < 10 ? `0${number}` : number;
diff --git a/test/cypress/e2e/example40.cy.ts b/test/cypress/e2e/example40.cy.ts
index 72e1965b..3a611c6e 100644
--- a/test/cypress/e2e/example40.cy.ts
+++ b/test/cypress/e2e/example40.cy.ts
@@ -18,8 +18,8 @@ describe('Example 40 - Infinite Scroll from JSON data', () => {
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0');
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains(/[0-9]/);
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).contains(/[0-9]/);
- cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).contains(/20[0-9]{2}-[0-9]{2}-[0-9]{2}/);
- cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).contains(/20[0-9]{2}-[0-9]{2}-[0-9]{2}/);
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).contains('2020');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).contains('2022');
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`)
.find('.mdi.mdi-check')
.should('have.length', 1);
@@ -92,4 +92,43 @@ describe('Example 40 - Infinite Scroll from JSON data', () => {
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1);
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).contains(/Duration: [0-9]/);
});
+
+ it('should clear all grouping', () => {
+ cy.get('#grid40').find('.slick-row .slick-cell:nth(1)').rightclick({ force: true });
+
+ cy.get('.slick-context-menu.dropright .slick-menu-command-list')
+ .find('.slick-menu-item')
+ .find('.slick-menu-content')
+ .contains('Clear all Grouping')
+ .click();
+ });
+
+ it('should hover over the "Start" column header menu of 1st grid and click on "Sort Descending" command', () => {
+ cy.get('[data-test="clear-filters-sorting"]').click();
+ cy.get('#grid40')
+ .find('.slick-header-column:nth(3)')
+ .trigger('mouseover')
+ .children('.slick-header-menu-button')
+ .invoke('show')
+ .click();
+
+ cy.get('.slick-header-menu .slick-menu-command-list').should('be.visible').should('contain', 'Sort Descending').click();
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).contains('2020');
+ });
+
+ it('should load 200 items and filter "Start" column with <=2020-08-25', () => {
+ cy.get('[data-test="set-dynamic-filter"]').click();
+ cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom');
+ cy.wait(10);
+ cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom');
+ cy.get('[data-test="totalItemCount"]').should('have.text', '200');
+
+ cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top');
+
+ cy.get(`[data-row=0] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
+ cy.get(`[data-row=1] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
+ cy.get(`[data-row=2] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
+ cy.get(`[data-row=3] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
+ });
});
diff --git a/yarn.lock b/yarn.lock
index 0c7d8db0..e493a5d9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4273,7 +4273,7 @@
resolved "https://registry.yarnpkg.com/@slickgrid-universal/binding/-/binding-5.12.2.tgz#102dc7db985dc3c52cfd13a176153f385f15a5b0"
integrity sha512-o7dTmmW4DVBZi01VQHjO/cIJEH5RNz+rIrnQKwldTRoKC05vKOQTF/vuQAqTkhI7YHzFaspfvdy92oagEt5niw==
-"@slickgrid-universal/common@5.14.0":
+"@slickgrid-universal/common@5.14.0", "@slickgrid-universal/common@~5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@slickgrid-universal/common/-/common-5.14.0.tgz#acee0a082cdf8c4c691a5cdb3a6548382b174ebb"
integrity sha512-Mme6d5G+lUqXCt0VOrzSscOJ1oYRSEIOC1v3Gdg+P4rQK0Hhgl0lSAzsGQX6yBrIw7rOX1VN+OZ9zoEqYidILA==
@@ -4301,7 +4301,7 @@
"@slickgrid-universal/common" "5.14.0"
"@slickgrid-universal/utils" "5.12.0"
-"@slickgrid-universal/custom-footer-component@5.14.0":
+"@slickgrid-universal/custom-footer-component@~5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@slickgrid-universal/custom-footer-component/-/custom-footer-component-5.14.0.tgz#3309db744f807b3b7c676d3f0f7cc78833a0a709"
integrity sha512-4Fr5F+40KF15rI0VixWdNk4VSvjnsSI5DVqSanrmTCNfntJ7sYQ+UBF/LIQMqj99GMkztzPLOdaniFyABk2+Wg==
@@ -4318,7 +4318,7 @@
"@slickgrid-universal/common" "5.14.0"
"@slickgrid-universal/utils" "5.12.0"
-"@slickgrid-universal/empty-warning-component@5.14.0":
+"@slickgrid-universal/empty-warning-component@~5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@slickgrid-universal/empty-warning-component/-/empty-warning-component-5.14.0.tgz#8c82f1e9df514797aec07b62d5023e9048ad67e5"
integrity sha512-RaEBZ5Wg24SkSylKpCbfduC9d9uDaMv7KusCLXvws7z2V4nCoAIBZdb694E6lVBXsZu3UMPZMRw5QBukZQJs9g==
@@ -4357,7 +4357,7 @@
"@slickgrid-universal/common" "5.14.0"
"@slickgrid-universal/utils" "5.12.0"
-"@slickgrid-universal/pagination-component@5.14.0":
+"@slickgrid-universal/pagination-component@~5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@slickgrid-universal/pagination-component/-/pagination-component-5.14.0.tgz#9caabcfb68153226f2f0dd95828d95f9c906d242"
integrity sha512-+HrGGdsnjYcEeT4xB6trCBvOgNHv847X58ccpCYORxXmLIDUmToCNuN+lYvCKvFQh1on3GPpy0v+6A+qZrmLbQ==
@@ -4365,7 +4365,7 @@
"@slickgrid-universal/binding" "5.12.2"
"@slickgrid-universal/common" "5.14.0"
-"@slickgrid-universal/row-detail-view-plugin@5.14.0":
+"@slickgrid-universal/row-detail-view-plugin@~5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@slickgrid-universal/row-detail-view-plugin/-/row-detail-view-plugin-5.14.0.tgz#00bb5d87b3383ada85d5e804be62d5d89ef991cb"
integrity sha512-xyEr0ucwchqAshuidKhcBPDrw/jwtGxUbQJg28W1LiMD+vQbsF5LlGiIB53lfBGniC34lHWiX1B/RI3Z9z0kXA==
@@ -4373,7 +4373,7 @@
"@slickgrid-universal/common" "5.14.0"
"@slickgrid-universal/utils" "5.12.0"
-"@slickgrid-universal/rxjs-observable@5.14.0":
+"@slickgrid-universal/rxjs-observable@~5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@slickgrid-universal/rxjs-observable/-/rxjs-observable-5.14.0.tgz#91b17dc147604d0848b9d7b3fc4b23ba1f2b3c33"
integrity sha512-RA5CiFicA0P8IUOLBJjmkCZNJ5Qzfwk8NKNXfh0RSFKrr53386m/59XYh2pmDt65YmgGbpu5iTZ1qgDhUMiKoA==