Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit cc8c31d

Browse files
authored
fix(editors): add saveOutputType to finally have proper save format (#535)
- we now have 3 available types to use with the Date Editors formatting 1. `type` (data input format), 2. `outputType` (picker display format) 3. `saveOutputType` (save format)
1 parent a18609b commit cc8c31d

File tree

4 files changed

+77
-31
lines changed

4 files changed

+77
-31
lines changed

src/app/examples/grid-editor.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,9 @@ export class GridEditorComponent implements OnInit {
272272
filter: { model: Filters.compoundDate },
273273
formatter: Formatters.dateIso,
274274
exportWithFormatter: true,
275-
type: FieldType.date,
275+
type: FieldType.date, // dataset cell input format
276+
// outputType: FieldType.dateUs, // date picker format
277+
saveOutputType: FieldType.dateUtc, // save output date formattype: FieldType.date,
276278
editor: {
277279
model: Editors.date,
278280
// override any of the Flatpickr options through "editorOptions"

src/app/modules/angular-slickgrid/editors/__tests__/dateEditor.spec.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -210,36 +210,64 @@ describe('DateEditor', () => {
210210

211211
expect(editor.isValueChanged()).toBe(false);
212212
});
213+
214+
it('should return False when input date is invalid', () => {
215+
mockItemData = { id: 1, startDate: '1900-02-32', isActive: true };
216+
mockColumn.type = FieldType.dateUs;
217+
mockColumn.internalColumnEditor.editorOptions = { allowInput: true }; // change to allow input value only for testing purposes
218+
219+
editor = new DateEditor(editorArguments);
220+
editor.loadValue(mockItemData);
221+
const editorInputElm = divContainer.querySelector<HTMLInputElement>('input.flatpickr-alt-input');
222+
editorInputElm.value = '1900-02-32';
223+
editorInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true }));
224+
225+
expect(editor.isValueChanged()).toBe(false);
226+
});
213227
});
214228

215229
describe('applyValue method', () => {
216-
it('should apply the value to the startDate property when it passes validation', () => {
230+
it('should apply the value to the startDate property with ISO format when no "outputType" is defined and when it passes validation', () => {
217231
mockColumn.internalColumnEditor.validator = null;
218-
mockColumn.type = FieldType.dateTimeIsoAmPm;
232+
mockColumn.type = FieldType.date;
219233
mockItemData = { id: 1, startDate: '2001-04-05T11:33:42.000Z', isActive: true };
220234

221-
const newDate = '2001-01-02T16:02:02.000+05:00';
235+
const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0));
222236
editor = new DateEditor(editorArguments);
223237
editor.applyValue(mockItemData, newDate);
224238

225-
expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate, 'YYYY-MM-DD hh:mm:ss a').toDate(), isActive: true });
239+
expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD'), isActive: true });
226240
});
227241

228-
it('should apply the value to the startDate property with a field having dot notation (complex object) that passes validation', () => {
242+
it('should apply the value to the startDate property with "outputType" format with a field having dot notation (complex object) that passes validation', () => {
229243
mockColumn.internalColumnEditor.validator = null;
230-
mockColumn.type = FieldType.dateTimeIsoAmPm;
244+
mockColumn.type = FieldType.date;
245+
mockColumn.outputType = FieldType.dateTimeIsoAmPm;
231246
mockColumn.field = 'employee.startDate';
232-
mockItemData = { id: 1, employee: { startDate: new Date(Date.UTC(2001, 3, 5, 16, 11, 33, 0)) }, isActive: true };
247+
mockItemData = { id: 1, employee: { startDate: '2001-04-05T11:33:42.000Z' }, isActive: true };
248+
249+
const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0));
250+
editor = new DateEditor(editorArguments);
251+
editor.applyValue(mockItemData, newDate);
252+
253+
expect(mockItemData).toEqual({ id: 1, employee: { startDate: moment(newDate).format('YYYY-MM-DD hh:mm:ss a') }, isActive: true });
254+
});
255+
256+
it('should apply the value to the startDate property with output format defined by "saveOutputType" when it passes validation', () => {
257+
mockColumn.internalColumnEditor.validator = null;
258+
mockColumn.type = FieldType.date;
259+
mockColumn.saveOutputType = FieldType.dateTimeIsoAmPm;
260+
mockItemData = { id: 1, startDate: '2001-04-05T11:33:42.000Z', isActive: true };
233261

234-
const newDate = '2001-01-02T16:02:02.000+05:00';
262+
const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0));
235263
editor = new DateEditor(editorArguments);
236264
editor.applyValue(mockItemData, newDate);
237265

238-
expect(mockItemData).toEqual({ id: 1, employee: { startDate: moment(newDate, 'YYYY-MM-DD hh:mm:ss a').toDate() }, isActive: true });
266+
expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD hh:mm:ss a'), isActive: true });
239267
});
240268

241269
it('should return item data with an empty string in its value when it fails the custom validation', () => {
242-
mockColumn.internalColumnEditor.validator = (value: any, args: EditorArgs) => {
270+
mockColumn.internalColumnEditor.validator = (value: any) => {
243271
if (value.length > 10) {
244272
return { valid: false, msg: 'Must be at least 10 chars long.' };
245273
}

src/app/modules/angular-slickgrid/editors/dateEditor.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export class DateEditor implements Editor {
9696
const placeholder = this.columnEditor && this.columnEditor.placeholder || '';
9797
const title = this.columnEditor && this.columnEditor.title || '';
9898
this.defaultDate = (this.args.item) ? this.args.item[this.columnDef.field] : null;
99+
const inputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.type || FieldType.dateUtc);
99100
const outputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.outputType || this.columnDef.type || FieldType.dateUtc);
100101
let currentLocale = this._translate && this._translate.currentLang || this.gridOptions.locale || 'en';
101102
if (currentLocale && currentLocale.length > 2) {
@@ -106,12 +107,10 @@ export class DateEditor implements Editor {
106107
defaultDate: this.defaultDate as string,
107108
altInput: true,
108109
altFormat: outputFormat,
109-
dateFormat: outputFormat,
110+
dateFormat: inputFormat,
110111
closeOnSelect: false,
111112
locale: (currentLocale !== 'en') ? this.loadFlatpickrLocale(currentLocale) : 'en',
112-
onChange: (selectedDates: Date[] | Date, dateStr: string, instance: any) => {
113-
this.save();
114-
},
113+
onChange: () => this.save(),
115114
errorHandler: () => {
116115
// do nothing, Flatpickr is a little too sensitive and will throw an error when provided date is lower than minDate so just disregard the error completely
117116
}
@@ -174,26 +173,33 @@ export class DateEditor implements Editor {
174173

175174
applyValue(item: any, state: any) {
176175
const fieldName = this.columnDef && this.columnDef.field;
177-
const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnDef.type)) || FieldType.dateUtc);
178-
const isComplexObject = fieldName.indexOf('.') > 0; // is the field a complex object, "address.streetNumber"
179-
180-
// validate the value before applying it (if not valid we'll set an empty string)
181-
const validation = this.validate(state);
182-
const newValue = (validation && validation.valid) ? moment(state, outputTypeFormat).toDate() : '';
183-
184-
// set the new value to the item datacontext
185-
if (isComplexObject) {
186-
setDeepValue(item, fieldName, newValue);
187-
} else {
188-
item[fieldName] = newValue;
176+
if (fieldName !== undefined) {
177+
const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnDef.type)) || FieldType.dateUtc);
178+
const saveTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.saveOutputType || this.columnDef.outputType || this.columnDef.type)) || FieldType.dateUtc);
179+
const isComplexObject = fieldName.indexOf('.') > 0; // is the field a complex object, "address.streetNumber"
180+
181+
// validate the value before applying it (if not valid we'll set an empty string)
182+
const validation = this.validate(state);
183+
const newValue = (validation && validation.valid) ? moment(state, outputTypeFormat).format(saveTypeFormat) : '';
184+
185+
// set the new value to the item datacontext
186+
if (isComplexObject) {
187+
setDeepValue(item, fieldName, newValue);
188+
} else {
189+
item[fieldName] = newValue;
190+
}
189191
}
190192
}
191193

192194
isValueChanged() {
193195
const elmValue = this._$input.val();
196+
const inputFormat = mapMomentDateFormatWithFieldType(this.columnDef && this.columnDef.type || FieldType.dateIso);
194197
const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnDef.type)) || FieldType.dateUtc);
195-
const elmDateStr = elmValue ? moment(elmValue, outputTypeFormat, false).format(outputTypeFormat) : '';
196-
const orgDateStr = this.originalDate ? moment(this.originalDate, outputTypeFormat, false).format(outputTypeFormat) : '';
198+
const elmDateStr = elmValue ? moment(elmValue, inputFormat, false).format(outputTypeFormat) : '';
199+
const orgDateStr = this.originalDate ? moment(this.originalDate, inputFormat, false).format(outputTypeFormat) : '';
200+
if (elmDateStr === 'Invalid date' || orgDateStr === 'Invalid date') {
201+
return false;
202+
}
197203

198204
return (!(elmDateStr === '' && orgDateStr === '')) && (elmDateStr !== orgDateStr);
199205
}
@@ -232,8 +238,9 @@ export class DateEditor implements Editor {
232238
return '';
233239
}
234240

241+
const inputFormat = mapMomentDateFormatWithFieldType(this.columnDef && this.columnDef.type || FieldType.dateIso);
235242
const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnDef.type)) || FieldType.dateIso);
236-
const value = moment(domValue, outputTypeFormat, false).format(outputTypeFormat);
243+
const value = moment(domValue, inputFormat, false).format(outputTypeFormat);
237244

238245
return value;
239246
}

src/app/modules/angular-slickgrid/models/column.interface.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,18 @@ export interface Column<T = any> {
169169
/** an event that can be used for triggering an action after a cell click */
170170
onCellClick?: (e: KeyboardEvent | MouseEvent, args: OnEventArgs) => void;
171171

172-
/** column output type */
172+
/**
173+
* Column output type(e.g.Date Picker, the output format that we will see in the picker)
174+
* NOTE: this is only currently used by the Editors / Filters with a Date Picker
175+
*/
173176
outputType?: FieldType;
174177

178+
/**
179+
* Column Editor save format type (e.g. which date format to use when saving after choosing a date from the Date Editor picker)
180+
* NOTE: this is only currently used by the Date Editor (date picker)
181+
*/
182+
saveOutputType?: FieldType;
183+
175184
/** if you want to pass custom paramaters to your Formatter/Editor or anything else */
176185
params?: any | any[];
177186

0 commit comments

Comments
 (0)