Skip to content

Commit 14097d3

Browse files
committed
feat: allow providing custom date format via base Date Formatter
1 parent 9c1ec74 commit 14097d3

File tree

8 files changed

+103
-32
lines changed

8 files changed

+103
-32
lines changed

docs/column-functionalities/Formatters.md

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ A good example of a `Formatter` could be a timestamp or a `Date` object that we
5252
* `Formatters.dateTimeShortUs`: Takes a Date object and displays it as an US Date+Time (without seconds) format (MM/DD/YYYY HH:mm:ss)
5353
* `Formatters.dateTimeUsAmPm` : Takes a Date object and displays it as an US Date+Time+(am/pm) format (MM/DD/YYYY hh:mm:ss a)
5454
* `Formatters.dateUtc` : Takes a Date object and displays it as a TZ format (YYYY-MM-DDThh:mm:ssZ)
55+
* `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`
56+
- for example: `{ type: 'date', formatter: Formatters.date, params: { dateFormat: 'MMM DD, YYYY' }}`
5557
* `Formatters.decimal`: Display the value as x decimals formatted, defaults to 2 decimals. You can pass "minDecimal" and/or "maxDecimal" to the "params" property.
5658
* `Formatters.dollar`: Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value.
5759
* `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

docs/column-functionalities/Sorting.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- [Custom Sort Comparer](#custom-sort-comparer)
55
- [Update Sorting Dynamically](#update-sorting-dynamically)
66
- [Dynamic Query Field](#dynamic-query-field)
7+
- [Sorting Dates](#sorting-dates)
78
- [Pre-Parse Date Columns for better perf](#pre-parse-date-columns-for-better-perf)
89

910
### Demo
@@ -134,6 +135,14 @@ queryFieldNameGetterFn: (dataContext) => {
134135
},
135136
```
136137

138+
### Sorting Dates
139+
140+
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:
141+
142+
- `type`: input/output of date fields, or in other words, parsing/formatting dates with the field `type` provided
143+
- `outputType`: when a `type` is provided for parsing (i.e. from your dataset), you could use a different `outputType` to format your date differently
144+
- `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
145+
137146
### Pre-Parse Date Columns for better perf
138147
##### requires v8.8.0 and higher
139148

@@ -149,11 +158,11 @@ The summary, is that we get a 10x boost **but** not only that, we also get an ex
149158

150159
#### Usage
151160

152-
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).
161+
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).
153162
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)
154163
2. `boolean` (i.e. parse `"start"` date string and reassign it as a `Date` object on the same `"start"` prop)
155164

156-
> **Note** this option **does not work** with Backend Services because it simply has no effect.
165+
> **Note** this option **does not work** with the Backend Service API because it simply has no effect.
157166
158167
For example if our dataset has 2 columns named "start" and "finish", then pre-parse the dataset,
159168

@@ -179,7 +188,7 @@ data = [
179188
]
180189
```
181190

182-
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.
191+
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.
183192

184193
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.
185194

package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@
5252
},
5353
"dependencies": {
5454
"@ngx-translate/core": "^15.0.0",
55-
"@slickgrid-universal/common": "5.14.0",
56-
"@slickgrid-universal/custom-footer-component": "5.14.0",
57-
"@slickgrid-universal/empty-warning-component": "5.14.0",
55+
"@slickgrid-universal/common": "~5.14.0",
56+
"@slickgrid-universal/custom-footer-component": "~5.14.0",
57+
"@slickgrid-universal/empty-warning-component": "~5.14.0",
5858
"@slickgrid-universal/event-pub-sub": "~5.13.0",
59-
"@slickgrid-universal/pagination-component": "5.14.0",
60-
"@slickgrid-universal/row-detail-view-plugin": "5.14.0",
61-
"@slickgrid-universal/rxjs-observable": "5.14.0",
59+
"@slickgrid-universal/pagination-component": "~5.14.0",
60+
"@slickgrid-universal/row-detail-view-plugin": "~5.14.0",
61+
"@slickgrid-universal/rxjs-observable": "~5.14.0",
6262
"dequal": "^2.0.3",
6363
"rxjs": "^7.8.2"
6464
},

src/app/examples/grid-infinite-json.component.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ <h2>
7777
(onAngularGridCreated)="angularGridReady($event.detail)"
7878
(onSort)="handleOnSort()"
7979
(onScroll)="handleOnScroll($event.detail.args)"
80-
(onRowCountChanged)="refreshMetrics($event.detail.args)"
80+
(onRowCountChanged)="handleOnRowCountChanged($event.detail.args)"
8181
>
8282
</angular-slickgrid>
8383
</div>

src/app/examples/grid-infinite-json.component.ts

+30-14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import {
1313
SortComparers,
1414
SortDirectionNumber,
1515
} from '../modules/angular-slickgrid';
16+
import { ExcelExportService } from '@slickgrid-universal/excel-export';
17+
18+
import { randomNumber } from './utilities';
1619

1720
const FETCH_SIZE = 50;
1821

@@ -66,19 +69,31 @@ export class GridInfiniteJsonComponent implements OnInit {
6669
id: 'start',
6770
name: 'Start',
6871
field: 'start',
69-
formatter: Formatters.dateIso,
72+
type: FieldType.date,
73+
outputType: FieldType.dateIso, // for date picker format
74+
formatter: Formatters.date,
7075
exportWithFormatter: true,
76+
params: { dateFormat: 'MMM DD, YYYY' },
77+
sortable: true,
7178
filterable: true,
72-
filter: { model: Filters.compoundDate },
79+
filter: {
80+
model: Filters.compoundDate,
81+
},
7382
},
7483
{
7584
id: 'finish',
7685
name: 'Finish',
7786
field: 'finish',
78-
formatter: Formatters.dateIso,
87+
type: FieldType.date,
88+
outputType: FieldType.dateIso, // for date picker format
89+
formatter: Formatters.date,
7990
exportWithFormatter: true,
91+
params: { dateFormat: 'MMM DD, YYYY' },
92+
sortable: true,
8093
filterable: true,
81-
filter: { model: Filters.compoundDate },
94+
filter: {
95+
model: Filters.compoundDate,
96+
},
8297
},
8398
{
8499
id: 'effort-driven',
@@ -101,6 +116,8 @@ export class GridInfiniteJsonComponent implements OnInit {
101116
enableGrouping: true,
102117
editable: false,
103118
rowHeight: 33,
119+
enableExcelExport: true,
120+
externalResources: [new ExcelExportService()],
104121
};
105122
}
106123

@@ -156,18 +173,13 @@ export class GridInfiniteJsonComponent implements OnInit {
156173
}
157174

158175
newItem(idx: number) {
159-
const randomYear = 2000 + Math.floor(Math.random() * 10);
160-
const randomMonth = Math.floor(Math.random() * 11);
161-
const randomDay = Math.floor(Math.random() * 29);
162-
const randomPercent = Math.round(Math.random() * 100);
163-
164176
return {
165177
id: idx,
166178
title: 'Task ' + idx,
167179
duration: Math.round(Math.random() * 100) + '',
168-
percentComplete: randomPercent,
169-
start: new Date(randomYear, randomMonth + 1, randomDay),
170-
finish: new Date(randomYear + 1, randomMonth + 1, randomDay),
180+
percentComplete: randomNumber(1, 12),
181+
start: new Date(2020, randomNumber(1, 11), randomNumber(1, 28)),
182+
finish: new Date(2022, randomNumber(1, 11), randomNumber(1, 28)),
171183
effortDriven: idx % 5 === 0,
172184
};
173185
}
@@ -184,11 +196,15 @@ export class GridInfiniteJsonComponent implements OnInit {
184196

185197
setFiltersDynamically() {
186198
// we can Set Filters Dynamically (or different filters) afterward through the FilterService
187-
this.angularGrid?.filterService.updateFilters([{ columnId: 'percentComplete', searchTerms: ['50'], operator: '>=' }]);
199+
this.angularGrid?.filterService.updateFilters([{ columnId: 'start', searchTerms: ['2020-08-25'], operator: '<=' }]);
188200
}
189201

190-
refreshMetrics(args: OnRowCountChangedEventArgs) {
202+
handleOnRowCountChanged(args: OnRowCountChangedEventArgs) {
191203
if (this.angularGrid && args?.current >= 0) {
204+
// we probably want to re-sort the data when we get new items
205+
this.angularGrid.dataView?.reSort();
206+
207+
// update metrics
192208
this.metrics.itemCount = this.angularGrid.dataView?.getFilteredItemCount() || 0;
193209
this.metrics.totalItemCount = args.itemCount || 0;
194210
}

src/app/examples/utilities.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
export function randomNumber(min: number, max: number, floor = true) {
2+
const number = Math.random() * (max - min + 1) + min;
3+
return floor ? Math.floor(number) : number;
4+
}
5+
16
export function zeroPadding(input: string | number) {
27
const number = parseInt(input as string, 10);
38
return number < 10 ? `0${number}` : number;

test/cypress/e2e/example40.cy.ts

+41-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ describe('Example 40 - Infinite Scroll from JSON data', () => {
1818
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0');
1919
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains(/[0-9]/);
2020
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).contains(/[0-9]/);
21-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).contains(/20[0-9]{2}-[0-9]{2}-[0-9]{2}/);
22-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).contains(/20[0-9]{2}-[0-9]{2}-[0-9]{2}/);
21+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).contains('2020');
22+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).contains('2022');
2323
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`)
2424
.find('.mdi.mdi-check')
2525
.should('have.length', 1);
@@ -92,4 +92,43 @@ describe('Example 40 - Infinite Scroll from JSON data', () => {
9292
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1);
9393
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).contains(/Duration: [0-9]/);
9494
});
95+
96+
it('should clear all grouping', () => {
97+
cy.get('#grid40').find('.slick-row .slick-cell:nth(1)').rightclick({ force: true });
98+
99+
cy.get('.slick-context-menu.dropright .slick-menu-command-list')
100+
.find('.slick-menu-item')
101+
.find('.slick-menu-content')
102+
.contains('Clear all Grouping')
103+
.click();
104+
});
105+
106+
it('should hover over the "Start" column header menu of 1st grid and click on "Sort Descending" command', () => {
107+
cy.get('[data-test="clear-filters-sorting"]').click();
108+
cy.get('#grid40')
109+
.find('.slick-header-column:nth(3)')
110+
.trigger('mouseover')
111+
.children('.slick-header-menu-button')
112+
.invoke('show')
113+
.click();
114+
115+
cy.get('.slick-header-menu .slick-menu-command-list').should('be.visible').should('contain', 'Sort Descending').click();
116+
117+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).contains('2020');
118+
});
119+
120+
it('should load 200 items and filter "Start" column with <=2020-08-25', () => {
121+
cy.get('[data-test="set-dynamic-filter"]').click();
122+
cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom');
123+
cy.wait(10);
124+
cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom');
125+
cy.get('[data-test="totalItemCount"]').should('have.text', '200');
126+
127+
cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top');
128+
129+
cy.get(`[data-row=0] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
130+
cy.get(`[data-row=1] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
131+
cy.get(`[data-row=2] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
132+
cy.get(`[data-row=3] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/);
133+
});
95134
});

yarn.lock

+6-6
Original file line numberDiff line numberDiff line change
@@ -4273,7 +4273,7 @@
42734273
resolved "https://registry.yarnpkg.com/@slickgrid-universal/binding/-/binding-5.12.2.tgz#102dc7db985dc3c52cfd13a176153f385f15a5b0"
42744274
integrity sha512-o7dTmmW4DVBZi01VQHjO/cIJEH5RNz+rIrnQKwldTRoKC05vKOQTF/vuQAqTkhI7YHzFaspfvdy92oagEt5niw==
42754275

4276-
"@slickgrid-universal/common@5.14.0":
4276+
"@slickgrid-universal/common@5.14.0", "@slickgrid-universal/common@~5.14.0":
42774277
version "5.14.0"
42784278
resolved "https://registry.yarnpkg.com/@slickgrid-universal/common/-/common-5.14.0.tgz#acee0a082cdf8c4c691a5cdb3a6548382b174ebb"
42794279
integrity sha512-Mme6d5G+lUqXCt0VOrzSscOJ1oYRSEIOC1v3Gdg+P4rQK0Hhgl0lSAzsGQX6yBrIw7rOX1VN+OZ9zoEqYidILA==
@@ -4301,7 +4301,7 @@
43014301
"@slickgrid-universal/common" "5.14.0"
43024302
"@slickgrid-universal/utils" "5.12.0"
43034303

4304-
"@slickgrid-universal/custom-footer-component@5.14.0":
4304+
"@slickgrid-universal/custom-footer-component@~5.14.0":
43054305
version "5.14.0"
43064306
resolved "https://registry.yarnpkg.com/@slickgrid-universal/custom-footer-component/-/custom-footer-component-5.14.0.tgz#3309db744f807b3b7c676d3f0f7cc78833a0a709"
43074307
integrity sha512-4Fr5F+40KF15rI0VixWdNk4VSvjnsSI5DVqSanrmTCNfntJ7sYQ+UBF/LIQMqj99GMkztzPLOdaniFyABk2+Wg==
@@ -4318,7 +4318,7 @@
43184318
"@slickgrid-universal/common" "5.14.0"
43194319
"@slickgrid-universal/utils" "5.12.0"
43204320

4321-
"@slickgrid-universal/empty-warning-component@5.14.0":
4321+
"@slickgrid-universal/empty-warning-component@~5.14.0":
43224322
version "5.14.0"
43234323
resolved "https://registry.yarnpkg.com/@slickgrid-universal/empty-warning-component/-/empty-warning-component-5.14.0.tgz#8c82f1e9df514797aec07b62d5023e9048ad67e5"
43244324
integrity sha512-RaEBZ5Wg24SkSylKpCbfduC9d9uDaMv7KusCLXvws7z2V4nCoAIBZdb694E6lVBXsZu3UMPZMRw5QBukZQJs9g==
@@ -4357,23 +4357,23 @@
43574357
"@slickgrid-universal/common" "5.14.0"
43584358
"@slickgrid-universal/utils" "5.12.0"
43594359

4360-
"@slickgrid-universal/pagination-component@5.14.0":
4360+
"@slickgrid-universal/pagination-component@~5.14.0":
43614361
version "5.14.0"
43624362
resolved "https://registry.yarnpkg.com/@slickgrid-universal/pagination-component/-/pagination-component-5.14.0.tgz#9caabcfb68153226f2f0dd95828d95f9c906d242"
43634363
integrity sha512-+HrGGdsnjYcEeT4xB6trCBvOgNHv847X58ccpCYORxXmLIDUmToCNuN+lYvCKvFQh1on3GPpy0v+6A+qZrmLbQ==
43644364
dependencies:
43654365
"@slickgrid-universal/binding" "5.12.2"
43664366
"@slickgrid-universal/common" "5.14.0"
43674367

4368-
"@slickgrid-universal/row-detail-view-plugin@5.14.0":
4368+
"@slickgrid-universal/row-detail-view-plugin@~5.14.0":
43694369
version "5.14.0"
43704370
resolved "https://registry.yarnpkg.com/@slickgrid-universal/row-detail-view-plugin/-/row-detail-view-plugin-5.14.0.tgz#00bb5d87b3383ada85d5e804be62d5d89ef991cb"
43714371
integrity sha512-xyEr0ucwchqAshuidKhcBPDrw/jwtGxUbQJg28W1LiMD+vQbsF5LlGiIB53lfBGniC34lHWiX1B/RI3Z9z0kXA==
43724372
dependencies:
43734373
"@slickgrid-universal/common" "5.14.0"
43744374
"@slickgrid-universal/utils" "5.12.0"
43754375

4376-
"@slickgrid-universal/rxjs-observable@5.14.0":
4376+
"@slickgrid-universal/rxjs-observable@~5.14.0":
43774377
version "5.14.0"
43784378
resolved "https://registry.yarnpkg.com/@slickgrid-universal/rxjs-observable/-/rxjs-observable-5.14.0.tgz#91b17dc147604d0848b9d7b3fc4b23ba1f2b3c33"
43794379
integrity sha512-RA5CiFicA0P8IUOLBJjmkCZNJ5Qzfwk8NKNXfh0RSFKrr53386m/59XYh2pmDt65YmgGbpu5iTZ1qgDhUMiKoA==

0 commit comments

Comments
 (0)