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

Commit 8c82e2a

Browse files
Ghislain BeaulacGhislain Beaulac
authored andcommitted
feat(editors): add "required" and "alwaysSaveOnEnterKey" options
- these 2 new options might not be supported by all Editors (e.g. checkbox doesn't use ENTER key) - the "alwaysSaveOnEnterKey" can be helpful if user want to save value regardless if it's null or not - also fixed integer editor validation, a few validations were missing like minValue/maxValue/...
1 parent 35b8750 commit 8c82e2a

File tree

14 files changed

+325
-186
lines changed

14 files changed

+325
-186
lines changed

src/app/examples/custom-angularComponentEditor.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Subscription } from 'rxjs';
33
import {
44
AngularUtilService,
55
Column,
6+
ColumnEditor,
67
Editor,
78
EditorValidator,
89
EditorValidatorOutput,
@@ -53,7 +54,7 @@ export class CustomAngularComponentEditor implements Editor {
5354
}
5455

5556
/** Get Column Editor object */
56-
get columnEditor(): any {
57+
get columnEditor(): ColumnEditor {
5758
return this.columnDef && this.columnDef.internalColumnEditor || {};
5859
}
5960

@@ -94,13 +95,10 @@ export class CustomAngularComponentEditor implements Editor {
9495
}
9596

9697
save() {
97-
const validation = this.validate();
98-
if (validation && validation.valid) {
99-
if (this.hasAutoCommitEdit) {
100-
this.args.grid.getEditorLock().commitCurrentEdit();
101-
} else {
102-
this.args.commitChanges();
103-
}
98+
if (this.hasAutoCommitEdit) {
99+
this.args.grid.getEditorLock().commitCurrentEdit();
100+
} else {
101+
this.args.commitChanges();
104102
}
105103
}
106104

@@ -166,10 +164,7 @@ export class CustomAngularComponentEditor implements Editor {
166164
validate(): EditorValidatorOutput {
167165
if (this.validator) {
168166
const value = this.componentRef.instance.selectedId;
169-
const validationResults = this.validator(value, this.args);
170-
if (!validationResults.valid) {
171-
return validationResults;
172-
}
167+
return this.validator(value, this.args);
173168
}
174169

175170
// by default the editor is always valid

src/app/examples/custom-inputEditor.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Column, Editor, EditorValidator, EditorValidatorOutput, KeyCode } from './../modules/angular-slickgrid';
1+
import { Column, ColumnEditor, Editor, EditorValidator, EditorValidatorOutput, KeyCode } from './../modules/angular-slickgrid';
22

33
// using external non-typed js libraries
44
declare var $: any;
@@ -8,6 +8,7 @@ declare var $: any;
88
* KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
99
*/
1010
export class CustomInputEditor implements Editor {
11+
private _lastInputEvent: KeyboardEvent;
1112
$input: any;
1213
defaultValue: any;
1314

@@ -21,7 +22,7 @@ export class CustomInputEditor implements Editor {
2122
}
2223

2324
/** Get Column Editor object */
24-
get columnEditor(): any {
25+
get columnEditor(): ColumnEditor {
2526
return this.columnDef && this.columnDef.internalColumnEditor || {};
2627
}
2728

@@ -39,9 +40,10 @@ export class CustomInputEditor implements Editor {
3940

4041
this.$input = $(`<input type="text" class="editor-text" placeholder="${placeholder}" />`)
4142
.appendTo(this.args.container)
42-
.on('keydown.nav', (e) => {
43-
if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.RIGHT) {
44-
e.stopImmediatePropagation();
43+
.on('keydown.nav', (event: KeyboardEvent) => {
44+
this._lastInputEvent = event;
45+
if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) {
46+
event.stopImmediatePropagation();
4547
}
4648
});
4749

@@ -88,6 +90,10 @@ export class CustomInputEditor implements Editor {
8890
}
8991

9092
isValueChanged() {
93+
const lastEvent = this._lastInputEvent && this._lastInputEvent.keyCode;
94+
if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastEvent === KeyCode.ENTER) {
95+
return true;
96+
}
9197
return (!(this.$input.val() === '' && this.defaultValue === null)) && (this.$input.val() !== this.defaultValue);
9298
}
9399

@@ -102,11 +108,7 @@ export class CustomInputEditor implements Editor {
102108
validate(): EditorValidatorOutput {
103109
if (this.validator) {
104110
const value = this.$input && this.$input.val && this.$input.val();
105-
const validationResults = this.validator(value, this.args);
106-
107-
if (!validationResults.valid) {
108-
return validationResults;
109-
}
111+
return this.validator(value, this.args);
110112
}
111113

112114
return {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export class GridEditorComponent implements OnInit {
145145
type: FieldType.string,
146146
editor: {
147147
model: Editors.longText,
148+
required: true,
148149
validator: myCustomTitleValidator, // use a custom validator
149150
},
150151
onCellChange: (e: Event, args: OnEventArgs) => {
@@ -187,6 +188,7 @@ export class GridEditorComponent implements OnInit {
187188
editor: {
188189
// default is 0 decimals, if no decimals is passed it will accept 0 or more decimals
189190
// however if you pass the "decimalPlaces", it will validate with that maximum
191+
alwaysSaveOnEnterKey: true, // defaults to False, when set to true and user presses ENTER it will always call a Save even if value is empty
190192
model: Editors.float,
191193
minValue: 0,
192194
maxValue: 365,
@@ -383,6 +385,7 @@ export class GridEditorComponent implements OnInit {
383385
separatorBetweenTextLabels: ' '
384386
},
385387
model: Editors.multipleSelect,
388+
required: true
386389
},
387390
filter: {
388391
collectionAsync: this.http.get<{ value: string; label: string; }[]>(URL_SAMPLE_COLLECTION_DATA),
@@ -511,7 +514,7 @@ export class GridEditorComponent implements OnInit {
511514
tempDataset.push({
512515
id: i,
513516
title: 'Task ' + i,
514-
duration: Math.round(Math.random() * 100) + '',
517+
duration: (i % 33 === 0) ? null : Math.round(Math.random() * 100) + '',
515518
percentComplete: randomPercent,
516519
percentCompleteNumber: randomPercent,
517520
start: new Date(randomYear, randomMonth, randomDay),

src/app/modules/angular-slickgrid/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ export class Constants {
1717
static TEXT_SORT_DESCENDING = 'Sort Descending';
1818
static TEXT_TOGGLE_FILTER_ROW = 'Toggle Filter Row';
1919
static TEXT_TOGGLE_PRE_HEADER_ROW = 'Toggle Pre-Header Row';
20+
static VALIDATION_REQUIRED_FIELD = 'Field is required';
2021
static VALIDATION_EDITOR_VALID_NUMBER = 'Please enter a valid number';
2122
static VALIDATION_EDITOR_VALID_INTEGER = 'Please enter a valid integer number';
23+
static VALIDATION_EDITOR_INTEGER_BETWEEN = 'Please enter a valid integer number between {{minValue}} and {{maxValue}}';
24+
static VALIDATION_EDITOR_INTEGER_MAX = 'Please enter a valid integer number that is lower than {{maxValue}}';
25+
static VALIDATION_EDITOR_INTEGER_MIN = 'Please enter a valid integer number that is greater than {{minValue}}';
2226
static VALIDATION_EDITOR_NUMBER_BETWEEN = 'Please enter a valid number between {{minValue}} and {{maxValue}}';
2327
static VALIDATION_EDITOR_NUMBER_MAX = 'Please enter a valid number that is lower than {{maxValue}}';
2428
static VALIDATION_EDITOR_NUMBER_MIN = 'Please enter a valid number that is greater than {{minValue}}';

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

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {
22
Column,
3+
ColumnEditor,
34
Editor,
45
EditorValidator,
56
EditorValidatorOutput,
67
KeyCode,
78
CollectionCustomStructure,
89
FieldType
910
} from './../models/index';
11+
import { Constants } from './../constants';
1012

1113
// using external non-typed js libraries
1214
declare var $: any;
@@ -18,6 +20,7 @@ declare var $: any;
1820
export class AutoCompleteEditor implements Editor {
1921
private _currentValue: any;
2022
private _defaultTextValue: string;
23+
private _lastInputEvent: KeyboardEvent;
2124
$input: any;
2225

2326
/** The property name for labels in the collection */
@@ -41,7 +44,7 @@ export class AutoCompleteEditor implements Editor {
4144
}
4245

4346
/** Get Column Editor object */
44-
get columnEditor(): any {
47+
get columnEditor(): ColumnEditor {
4548
return this.columnDef && this.columnDef.internalColumnEditor || {};
4649
}
4750

@@ -50,6 +53,10 @@ export class AutoCompleteEditor implements Editor {
5053
return this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.customStructure;
5154
}
5255

56+
get hasAutoCommitEdit() {
57+
return this.args.grid.getOptions().autoCommitEdit;
58+
}
59+
5360
/** Get the Validator function, can be passed in Editor property or Column Definition */
5461
get validator(): EditorValidator {
5562
return this.columnEditor.validator || this.columnDef.validator;
@@ -63,9 +70,10 @@ export class AutoCompleteEditor implements Editor {
6370

6471
this.$input = $(`<input type="text" class="autocomplete editor-text editor-${columnId}" placeholder="${placeholder}" />`)
6572
.appendTo(this.args.container)
66-
.on('keydown.nav', (e) => {
67-
if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.RIGHT) {
68-
e.stopImmediatePropagation();
73+
.on('keydown.nav', (event: KeyboardEvent) => {
74+
this._lastInputEvent = event;
75+
if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) {
76+
event.stopImmediatePropagation();
6977
}
7078
});
7179

@@ -124,6 +132,14 @@ export class AutoCompleteEditor implements Editor {
124132
this.$input.select();
125133
}
126134

135+
save() {
136+
if (this.hasAutoCommitEdit) {
137+
this.args.grid.getEditorLock().commitCurrentEdit();
138+
} else {
139+
this.args.commitChanges();
140+
}
141+
}
142+
127143
serializeValue() {
128144
// if user provided a custom structure, we need to reswap the properties
129145
// we do this because autocomplete needed label/value pair which might not be what the user provided
@@ -141,20 +157,30 @@ export class AutoCompleteEditor implements Editor {
141157
}
142158

143159
isValueChanged() {
160+
const lastEvent = this._lastInputEvent && this._lastInputEvent.keyCode;
161+
if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastEvent === KeyCode.ENTER) {
162+
return true;
163+
}
144164
return (!(this.$input.val() === '' && this._defaultTextValue === null)) && (this.$input.val() !== this._defaultTextValue);
145165
}
146166

147167
validate(): EditorValidatorOutput {
168+
const isRequired = this.columnEditor.required;
169+
const elmValue = this.$input && this.$input.val && this.$input.val();
170+
const errorMsg = this.columnEditor.errorMessage;
171+
148172
if (this.validator) {
149-
const value = this.$input && this.$input.val && this.$input.val();
150-
const validationResults = this.validator(value, this.args);
151-
if (!validationResults.valid) {
152-
return validationResults;
153-
}
173+
return this.validator(elmValue, this.args);
174+
}
175+
176+
// by default the editor is almost always valid (except when it's required but not provided)
177+
if (isRequired && elmValue === '') {
178+
return {
179+
valid: false,
180+
msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD
181+
};
154182
}
155183

156-
// by default the editor is always valid
157-
// if user want it to be a required checkbox, he would have to provide his own validator
158184
return {
159185
valid: true,
160186
msg: null

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Column, Editor, EditorValidator, EditorValidatorOutput } from './../models/index';
1+
import { Constants } from './../constants';
2+
import { Column, ColumnEditor, Editor, EditorValidator, EditorValidatorOutput } from './../models/index';
23

34
// using external non-typed js libraries
45
declare var $: any;
@@ -21,7 +22,7 @@ export class CheckboxEditor implements Editor {
2122
}
2223

2324
/** Get Column Editor object */
24-
get columnEditor(): any {
25+
get columnEditor(): ColumnEditor {
2526
return this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor || {};
2627
}
2728

@@ -84,16 +85,22 @@ export class CheckboxEditor implements Editor {
8485
}
8586

8687
validate(): EditorValidatorOutput {
88+
const isRequired = this.columnEditor.required;
89+
const isChecked = this.$input && this.$input.prop && this.$input.prop('checked');
90+
const errorMsg = this.columnEditor.errorMessage;
91+
8792
if (this.validator) {
88-
const value = this.$input && this.$input.val && this.$input.val();
89-
const validationResults = this.validator(value, this.args);
90-
if (!validationResults.valid) {
91-
return validationResults;
92-
}
93+
return this.validator(isChecked, this.args);
94+
}
95+
96+
// by default the editor is almost always valid (except when it's required but not provided)
97+
if (isRequired && !isChecked) {
98+
return {
99+
valid: false,
100+
msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD
101+
};
93102
}
94103

95-
// by default the editor is always valid
96-
// if user want it to be a required checkbox, he would have to provide his own validator
97104
return {
98105
valid: true,
99106
msg: null

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { mapFlatpickrDateFormatWithFieldType, mapMomentDateFormatWithFieldType } from './../services/utilities';
2-
import { Column, Editor, EditorValidator, EditorValidatorOutput, FieldType, GridOption } from './../models/index';
31
import { TranslateService } from '@ngx-translate/core';
2+
import { Constants } from './../constants';
3+
import { mapFlatpickrDateFormatWithFieldType, mapMomentDateFormatWithFieldType } from './../services/utilities';
4+
import { Column, ColumnEditor, Editor, EditorValidator, EditorValidatorOutput, FieldType, GridOption } from './../models/index';
45
import * as moment_ from 'moment-mini';
56
const moment = moment_; // patch to fix rollup "moment has no default export" issue, document here https://github.com/rollup/rollup/issues/670
67

@@ -29,7 +30,7 @@ export class DateEditor implements Editor {
2930
}
3031

3132
/** Get Column Editor object */
32-
get columnEditor(): any {
33+
get columnEditor(): ColumnEditor {
3334
return this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor || {};
3435
}
3536

@@ -115,13 +116,10 @@ export class DateEditor implements Editor {
115116

116117
save() {
117118
// autocommit will not focus the next editor
118-
const validation = this.validate();
119-
if (validation && validation.valid) {
120-
if (this.args.grid.getOptions().autoCommitEdit) {
121-
this.args.grid.getEditorLock().commitCurrentEdit();
122-
} else {
123-
this.args.commitChanges();
124-
}
119+
if (this.args.grid.getOptions().autoCommitEdit) {
120+
this.args.grid.getEditorLock().commitCurrentEdit();
121+
} else {
122+
this.args.commitChanges();
125123
}
126124
}
127125

@@ -161,16 +159,23 @@ export class DateEditor implements Editor {
161159
}
162160

163161
validate(): EditorValidatorOutput {
162+
const isRequired = this.columnEditor.required;
163+
const elmValue = this.$input && this.$input.val && this.$input.val();
164+
const errorMsg = this.columnEditor.errorMessage;
165+
164166
if (this.validator) {
165167
const value = this.$input && this.$input.val && this.$input.val();
166-
const validationResults = this.validator(value, this.args);
167-
if (!validationResults.valid) {
168-
return validationResults;
169-
}
168+
return this.validator(value, this.args);
169+
}
170+
171+
// by default the editor is almost always valid (except when it's required but not provided)
172+
if (isRequired && elmValue === '') {
173+
return {
174+
valid: false,
175+
msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD
176+
};
170177
}
171178

172-
// by default the editor is always valid
173-
// if user want it to be a required checkbox, he would have to provide his own validator
174179
return {
175180
valid: true,
176181
msg: null

0 commit comments

Comments
 (0)