@slickgrid-universal/composite-editor-component 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,887 +1,887 @@
1
- import { deepCopy, deepMerge, emptyObject, setDeepValue } from '@slickgrid-universal/utils';
2
- import { BindingEventService, Constants, createDomElement, getDescendantProperty, numericSortComparer, sanitizeTextByAvailableSanitizer, SortDirectionNumber, } from '@slickgrid-universal/common';
3
- import { CompositeEditor } from './compositeEditor.factory';
4
- const DEFAULT_ON_ERROR = (error) => console.log(error.message);
5
- export class SlickCompositeEditorComponent {
6
- get eventHandler() {
7
- return this._eventHandler;
8
- }
9
- get dataView() {
10
- return this.grid.getData();
11
- }
12
- get dataViewLength() {
13
- return this.dataView.getLength();
14
- }
15
- get formValues() {
16
- return this._formValues;
17
- }
18
- get editors() {
19
- return this._editors;
20
- }
21
- set editors(editors) {
22
- this._editors = editors;
23
- }
24
- get gridOptions() {
25
- var _a;
26
- return (_a = this.grid) === null || _a === void 0 ? void 0 : _a.getOptions();
27
- }
28
- constructor() {
29
- this._columnDefinitions = [];
30
- this._lastActiveRowNumber = -1;
31
- this._formValues = null;
32
- this.gridService = null;
33
- this._eventHandler = new Slick.EventHandler();
34
- this._bindEventService = new BindingEventService();
35
- }
36
- /**
37
- * initialize the Composite Editor by passing the SlickGrid object and the container service
38
- *
39
- * Note: we aren't using DI in the constructor simply to be as framework agnostic as possible,
40
- * we are simply using this init() function with a very basic container service to do the job
41
- */
42
- init(grid, containerService) {
43
- var _a, _b;
44
- this.grid = grid;
45
- this.gridService = containerService.get('GridService');
46
- this.translaterService = containerService.get('TranslaterService');
47
- if (!this.gridService) {
48
- throw new Error('[Slickgrid-Universal] it seems that the GridService is not being loaded properly, make sure the Container Service is properly implemented.');
49
- }
50
- if (this.gridOptions.enableTranslate && (!this.translaterService || !this.translaterService.translate)) {
51
- throw new Error('[Slickgrid-Universal] requires a Translate Service to be installed and configured when the grid option "enableTranslate" is enabled.');
52
- }
53
- // get locales provided by user in forRoot or else use default English locales via the Constants
54
- this._locales = (_b = (_a = this.gridOptions) === null || _a === void 0 ? void 0 : _a.locales) !== null && _b !== void 0 ? _b : Constants.locales;
55
- }
56
- /** Dispose of the Component & unsubscribe all events */
57
- dispose() {
58
- this._eventHandler.unsubscribeAll();
59
- this._bindEventService.unbindAll();
60
- this._formValues = null;
61
- this.disposeComponent();
62
- }
63
- /** Dispose of the Component without unsubscribing any events */
64
- disposeComponent() {
65
- var _a, _b, _c;
66
- // protected _editorContainers!: Array<HTMLElement | null>;
67
- (_a = this._modalBodyTopValidationElm) === null || _a === void 0 ? void 0 : _a.remove();
68
- (_b = this._modalSaveButtonElm) === null || _b === void 0 ? void 0 : _b.remove();
69
- if (typeof ((_c = this._modalElm) === null || _c === void 0 ? void 0 : _c.remove) === 'function') {
70
- this._modalElm.remove();
71
- // remove the body backdrop click listener, every other listeners will be dropped automatically since we destroy the component
72
- document.body.classList.remove('slick-modal-open');
73
- }
74
- this._editorContainers = [];
75
- }
76
- /**
77
- * Dynamically change value of an input from the Composite Editor form.
78
- *
79
- * NOTE: user might get an error thrown when trying to apply a value on a Composite Editor that was not found in the form,
80
- * but in some cases the user might still want the value to be applied to the formValues so that it will be sent to the save in final item data context
81
- * and when that happens, you can just skip that error so it won't throw.
82
- * @param {String | Column} columnIdOrDef - column id or column definition
83
- * @param {*} newValue - the new value
84
- * @param {Boolean} skipMissingEditorError - defaults to False, skipping the error when the Composite Editor was not found will allow to still apply the value into the formValues object
85
- * @param {Boolean} triggerOnCompositeEditorChange - defaults to True, will this change trigger a onCompositeEditorChange event?
86
- */
87
- changeFormInputValue(columnIdOrDef, newValue, skipMissingEditorError = false, triggerOnCompositeEditorChange = true) {
88
- var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o;
89
- const columnDef = this.getColumnByObjectOrId(columnIdOrDef);
90
- const columnId = typeof columnIdOrDef === 'string' ? columnIdOrDef : (_a = columnDef === null || columnDef === void 0 ? void 0 : columnDef.id) !== null && _a !== void 0 ? _a : '';
91
- const editor = (_b = this._editors) === null || _b === void 0 ? void 0 : _b[columnId];
92
- let outputValue = newValue;
93
- if (!editor && !skipMissingEditorError) {
94
- throw new Error(`Composite Editor with column id "${columnId}" not found.`);
95
- }
96
- if (typeof (editor === null || editor === void 0 ? void 0 : editor.setValue) === 'function' && Array.isArray(this._editorContainers)) {
97
- editor.setValue(newValue, true, triggerOnCompositeEditorChange);
98
- const editorContainerElm = this._editorContainers.find(editorElm => editorElm.dataset.editorid === columnId);
99
- const excludeDisabledFieldFormValues = (_f = (_d = (_c = this.gridOptions) === null || _c === void 0 ? void 0 : _c.compositeEditorOptions) === null || _d === void 0 ? void 0 : _d.excludeDisabledFieldFormValues) !== null && _f !== void 0 ? _f : false;
100
- if (!editor.disabled || (editor.disabled && !excludeDisabledFieldFormValues)) {
101
- (_g = editorContainerElm === null || editorContainerElm === void 0 ? void 0 : editorContainerElm.classList) === null || _g === void 0 ? void 0 : _g.add('modified');
102
- }
103
- else {
104
- outputValue = '';
105
- (_h = editorContainerElm === null || editorContainerElm === void 0 ? void 0 : editorContainerElm.classList) === null || _h === void 0 ? void 0 : _h.remove('modified');
106
- }
107
- // when the field is disabled, we will only allow a blank value anything else will be disregarded
108
- if (editor.disabled && (outputValue !== '' || outputValue !== null || outputValue !== undefined || outputValue !== 0)) {
109
- outputValue = '';
110
- }
111
- }
112
- // is the field a complex object, like "address.streetNumber"
113
- // we'll set assign the value as a complex object following the `field` dot notation
114
- const fieldName = (_j = columnDef === null || columnDef === void 0 ? void 0 : columnDef.field) !== null && _j !== void 0 ? _j : '';
115
- if (columnDef && (fieldName === null || fieldName === void 0 ? void 0 : fieldName.includes('.'))) {
116
- // when it's a complex object, user could override the object path (where the editable object is located)
117
- // else we use the path provided in the Field Column Definition
118
- const objectPath = (_m = (_l = (_k = columnDef.internalColumnEditor) === null || _k === void 0 ? void 0 : _k.complexObjectPath) !== null && _l !== void 0 ? _l : fieldName) !== null && _m !== void 0 ? _m : '';
119
- setDeepValue((_o = this._formValues) !== null && _o !== void 0 ? _o : {}, objectPath, newValue);
120
- }
121
- else {
122
- this._formValues = { ...this._formValues, [columnId]: outputValue };
123
- }
124
- }
125
- /**
126
- * Dynamically update the `formValues` object directly without triggering the onCompositeEditorChange event.
127
- * The fact that this doesn't trigger an event, might not always be good though, in these cases you are probably better with using the changeFormInputValue() method
128
- * @param {String | Column} columnIdOrDef - column id or column definition
129
- * @param {*} newValue - the new value
130
- */
131
- changeFormValue(columnIdOrDef, newValue) {
132
- var _a, _b, _c, _d, _f;
133
- const columnDef = this.getColumnByObjectOrId(columnIdOrDef);
134
- const columnId = typeof columnIdOrDef === 'string' ? columnIdOrDef : (_a = columnDef === null || columnDef === void 0 ? void 0 : columnDef.id) !== null && _a !== void 0 ? _a : '';
135
- // is the field a complex object, like "address.streetNumber"
136
- // we'll set assign the value as a complex object following the `field` dot notation
137
- const fieldName = (_b = columnDef === null || columnDef === void 0 ? void 0 : columnDef.field) !== null && _b !== void 0 ? _b : columnIdOrDef;
138
- if (fieldName === null || fieldName === void 0 ? void 0 : fieldName.includes('.')) {
139
- // when it's a complex object, user could override the object path (where the editable object is located)
140
- // else we use the path provided in the Field Column Definition
141
- const objectPath = (_f = (_d = (_c = columnDef === null || columnDef === void 0 ? void 0 : columnDef.internalColumnEditor) === null || _c === void 0 ? void 0 : _c.complexObjectPath) !== null && _d !== void 0 ? _d : fieldName) !== null && _f !== void 0 ? _f : '';
142
- setDeepValue(this._formValues, objectPath, newValue);
143
- }
144
- else {
145
- this._formValues = { ...this._formValues, [columnId]: newValue };
146
- }
147
- this._formValues = deepMerge({}, this._itemDataContext, this._formValues);
148
- }
149
- /**
150
- * Dynamically change an Editor option of the Composite Editor form
151
- * For example, a use case could be to dynamically change the "minDate" of another Date Editor in the Composite Editor form.
152
- * @param {String} columnId - column id
153
- * @param {*} newValue - the new value
154
- */
155
- changeFormEditorOption(columnId, optionName, newOptionValue) {
156
- var _a;
157
- const editor = (_a = this._editors) === null || _a === void 0 ? void 0 : _a[columnId];
158
- // change an Editor option (not all Editors have that method, so make sure it exists before trying to call it)
159
- if (editor === null || editor === void 0 ? void 0 : editor.changeEditorOption) {
160
- editor.changeEditorOption(optionName, newOptionValue);
161
- }
162
- else {
163
- throw new Error(`Editor with column id "${columnId}" not found OR the Editor does not support "changeEditorOption" (current only available with AutoComplete, Date, MultipleSelect & SingleSelect Editors).`);
164
- }
165
- }
166
- /**
167
- * Disable (or enable) an input of the Composite Editor form
168
- * @param {String} columnId - column definition id
169
- * @param isDisabled - defaults to True, are we disabling the associated form input
170
- */
171
- disableFormInput(columnId, isDisabled = true) {
172
- var _a;
173
- const editor = (_a = this._editors) === null || _a === void 0 ? void 0 : _a[columnId];
174
- if ((editor === null || editor === void 0 ? void 0 : editor.disable) && Array.isArray(this._editorContainers)) {
175
- editor.disable(isDisabled);
176
- }
177
- }
178
- /** Entry point to initialize and open the Composite Editor modal window */
179
- openDetails(options) {
180
- var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
181
- const onError = (_a = options.onError) !== null && _a !== void 0 ? _a : DEFAULT_ON_ERROR;
182
- const defaultOptions = {
183
- backdrop: 'static',
184
- showCloseButtonOutside: true,
185
- shouldClearRowSelectionAfterMassAction: true,
186
- viewColumnLayout: 'auto',
187
- modalType: 'edit',
188
- };
189
- try {
190
- if (!this.grid || (this.grid.getEditorLock().isActive() && !this.grid.getEditorLock().commitCurrentEdit())) {
191
- return null;
192
- }
193
- this._formValues = null; // make sure there's no leftover from previous change
194
- this._options = { ...defaultOptions, ...this.gridOptions.compositeEditorOptions, ...options, labels: { ...(_b = this.gridOptions.compositeEditorOptions) === null || _b === void 0 ? void 0 : _b.labels, ...options === null || options === void 0 ? void 0 : options.labels } }; // merge default options with user options
195
- this._options.backdrop = options.backdrop !== undefined ? options.backdrop : 'static';
196
- const viewColumnLayout = this._options.viewColumnLayout || 1;
197
- const activeCell = this.grid.getActiveCell();
198
- const activeColIndex = (_c = activeCell === null || activeCell === void 0 ? void 0 : activeCell.cell) !== null && _c !== void 0 ? _c : 0;
199
- const activeRow = (_d = activeCell === null || activeCell === void 0 ? void 0 : activeCell.row) !== null && _d !== void 0 ? _d : 0;
200
- const gridUid = this.grid.getUID() || '';
201
- let headerTitle = options.headerTitle || '';
202
- // execute callback before creating the modal window (that is in short the first event in the lifecycle)
203
- if (typeof this._options.onBeforeOpen === 'function') {
204
- this._options.onBeforeOpen();
205
- }
206
- if (this.hasRowSelectionEnabled() && this._options.modalType === 'auto-mass' && this.grid.getSelectedRows) {
207
- const selectedRowsIndexes = this.grid.getSelectedRows() || [];
208
- if (selectedRowsIndexes.length > 0) {
209
- this._options.modalType = 'mass-selection';
210
- if (options === null || options === void 0 ? void 0 : options.headerTitleMassSelection) {
211
- headerTitle = options === null || options === void 0 ? void 0 : options.headerTitleMassSelection;
212
- }
213
- }
214
- else {
215
- this._options.modalType = 'mass-update';
216
- if (options === null || options === void 0 ? void 0 : options.headerTitleMassUpdate) {
217
- headerTitle = options === null || options === void 0 ? void 0 : options.headerTitleMassUpdate;
218
- }
219
- }
220
- }
221
- const modalType = this._options.modalType || 'edit';
222
- if (!this.gridOptions.editable) {
223
- onError({ type: 'error', code: 'EDITABLE_GRID_REQUIRED', message: 'Your grid must be editable in order to use the Composite Editor Modal.' });
224
- return null;
225
- }
226
- else if (!this.gridOptions.enableCellNavigation) {
227
- onError({ type: 'error', code: 'ENABLE_CELL_NAVIGATION_REQUIRED', message: 'Composite Editor requires the flag "enableCellNavigation" to be set to True in your Grid Options.' });
228
- return null;
229
- }
230
- else if (!this.gridOptions.enableAddRow && (modalType === 'clone' || modalType === 'create')) {
231
- onError({ type: 'error', code: 'ENABLE_ADD_ROW_REQUIRED', message: 'Composite Editor requires the flag "enableAddRow" to be set to True in your Grid Options when cloning/creating a new item.' });
232
- return null;
233
- }
234
- else if (!activeCell && (modalType === 'clone' || modalType === 'edit')) {
235
- onError({ type: 'warning', code: 'NO_RECORD_FOUND', message: 'No records selected for edit or clone operation.' });
236
- return null;
237
- }
238
- else {
239
- const isWithMassChange = (modalType === 'mass-update' || modalType === 'mass-selection');
240
- const dataContext = !isWithMassChange ? this.grid.getDataItem(activeRow) : {};
241
- this._originalDataContext = deepCopy(dataContext);
242
- this._columnDefinitions = this.grid.getColumns();
243
- const selectedRowsIndexes = this.hasRowSelectionEnabled() ? this.grid.getSelectedRows() : [];
244
- const fullDatasetLength = (_g = (_f = this.dataView) === null || _f === void 0 ? void 0 : _f.getItemCount()) !== null && _g !== void 0 ? _g : 0;
245
- this._lastActiveRowNumber = activeRow;
246
- const dataContextIds = this.dataView.getAllSelectedIds();
247
- // focus on a first cell with an Editor (unless current cell already has an Editor then do nothing)
248
- // also when it's a "Create" modal, we'll scroll to the end of the grid
249
- const rowIndex = modalType === 'create' ? this.dataViewLength : activeRow;
250
- const hasFoundEditor = this.focusOnFirstColumnCellWithEditor(this._columnDefinitions, dataContext, activeColIndex, rowIndex, isWithMassChange);
251
- if (!hasFoundEditor) {
252
- return null;
253
- }
254
- if (modalType === 'edit' && !dataContext) {
255
- onError({ type: 'warning', code: 'ROW_NOT_EDITABLE', message: 'Current row is not editable.' });
256
- return null;
257
- }
258
- else if (modalType === 'mass-selection') {
259
- if (selectedRowsIndexes.length < 1) {
260
- onError({ type: 'warning', code: 'ROW_SELECTION_REQUIRED', message: 'You must select some rows before trying to apply new value(s).' });
261
- return null;
262
- }
263
- }
264
- let modalColumns = [];
265
- if (isWithMassChange) {
266
- // when using Mass Update, we only care about the columns that have the "massUpdate: true", we disregard anything else
267
- modalColumns = this._columnDefinitions.filter(col => { var _a; return col.editor && ((_a = col.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.massUpdate) === true; });
268
- }
269
- else {
270
- modalColumns = this._columnDefinitions.filter(col => col.editor);
271
- }
272
- // user could optionally show the form inputs in a specific order instead of using default column definitions order
273
- if (modalColumns.some(col => { var _a; return ((_a = col.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.compositeEditorFormOrder) !== undefined; })) {
274
- modalColumns.sort((col1, col2) => {
275
- var _a, _b, _c, _d;
276
- const val1 = (_b = (_a = col1 === null || col1 === void 0 ? void 0 : col1.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.compositeEditorFormOrder) !== null && _b !== void 0 ? _b : Infinity;
277
- const val2 = (_d = (_c = col2 === null || col2 === void 0 ? void 0 : col2.internalColumnEditor) === null || _c === void 0 ? void 0 : _c.compositeEditorFormOrder) !== null && _d !== void 0 ? _d : Infinity;
278
- return numericSortComparer(val1, val2, SortDirectionNumber.asc);
279
- });
280
- }
281
- // open the editor modal and we can also provide a header title with optional parsing pulled from the dataContext, via template {{ }}
282
- // for example {{title}} => display the item title, or even complex object works {{product.name}} => display item product name
283
- const parsedHeaderTitle = headerTitle.replace(/\{\{(.*?)\}\}/g, (_match, group) => getDescendantProperty(dataContext, group));
284
- const layoutColCount = viewColumnLayout === 'auto' ? this.autoCalculateLayoutColumnCount(modalColumns.length) : viewColumnLayout;
285
- this._modalElm = createDomElement('div', { className: `slick-editor-modal ${gridUid}` });
286
- const modalContentElm = createDomElement('div', { className: 'slick-editor-modal-content' });
287
- if ((!isNaN(viewColumnLayout) && +viewColumnLayout > 1) || (viewColumnLayout === 'auto' && layoutColCount > 1)) {
288
- const splitClassName = layoutColCount === 2 ? 'split-view' : 'triple-split-view';
289
- modalContentElm.classList.add(splitClassName);
290
- }
291
- const modalHeaderTitleElm = createDomElement('div', {
292
- className: 'slick-editor-modal-title',
293
- innerHTML: sanitizeTextByAvailableSanitizer(this.gridOptions, parsedHeaderTitle),
294
- });
295
- const modalCloseButtonElm = createDomElement('button', { type: 'button', textContent: '×', className: 'close', dataset: { action: 'close' } });
296
- modalCloseButtonElm.setAttribute('aria-label', 'Close');
297
- if (this._options.showCloseButtonOutside) {
298
- (_h = modalHeaderTitleElm === null || modalHeaderTitleElm === void 0 ? void 0 : modalHeaderTitleElm.classList) === null || _h === void 0 ? void 0 : _h.add('outside');
299
- (_j = modalCloseButtonElm === null || modalCloseButtonElm === void 0 ? void 0 : modalCloseButtonElm.classList) === null || _j === void 0 ? void 0 : _j.add('outside');
300
- }
301
- const modalHeaderElm = createDomElement('div', { className: 'slick-editor-modal-header' });
302
- modalHeaderElm.setAttribute('aria-label', 'Close');
303
- modalHeaderElm.appendChild(modalHeaderTitleElm);
304
- modalHeaderElm.appendChild(modalCloseButtonElm);
305
- const modalBodyElm = createDomElement('div', { className: 'slick-editor-modal-body' });
306
- this._modalBodyTopValidationElm = createDomElement('div', { className: 'validation-summary', style: { display: 'none' } });
307
- modalBodyElm.appendChild(this._modalBodyTopValidationElm);
308
- const modalFooterElm = createDomElement('div', { className: 'slick-editor-modal-footer' });
309
- const modalCancelButtonElm = createDomElement('button', {
310
- type: 'button',
311
- className: 'btn btn-cancel btn-default btn-sm',
312
- textContent: this.getLabelText('cancelButton', 'TEXT_CANCEL', 'Cancel'),
313
- dataset: { action: 'cancel' },
314
- });
315
- modalCancelButtonElm.setAttribute('aria-label', this.getLabelText('cancelButton', 'TEXT_CANCEL', 'Cancel'));
316
- let leftFooterText = '';
317
- let saveButtonText = '';
318
- switch (modalType) {
319
- case 'clone':
320
- saveButtonText = this.getLabelText('cloneButton', 'TEXT_CLONE', 'Clone');
321
- break;
322
- case 'mass-update':
323
- const footerUnparsedText = this.getLabelText('massUpdateStatus', 'TEXT_ALL_X_RECORDS_SELECTED', 'All {{x}} records selected');
324
- leftFooterText = this.parseText(footerUnparsedText, { x: fullDatasetLength });
325
- saveButtonText = this.getLabelText('massUpdateButton', 'TEXT_APPLY_MASS_UPDATE', 'Mass Update');
326
- break;
327
- case 'mass-selection':
328
- const selectionUnparsedText = this.getLabelText('massSelectionStatus', 'TEXT_X_OF_Y_MASS_SELECTED', '{{x}} of {{y}} selected');
329
- leftFooterText = this.parseText(selectionUnparsedText, { x: dataContextIds.length, y: fullDatasetLength });
330
- saveButtonText = this.getLabelText('massSelectionButton', 'TEXT_APPLY_TO_SELECTION', 'Update Selection');
331
- break;
332
- default:
333
- saveButtonText = this.getLabelText('saveButton', 'TEXT_SAVE', 'Save');
334
- }
335
- const selectionCounterElm = createDomElement('div', { className: 'footer-status-text', textContent: leftFooterText });
336
- this._modalSaveButtonElm = createDomElement('button', {
337
- type: 'button', className: 'btn btn-save btn-primary btn-sm',
338
- textContent: saveButtonText,
339
- dataset: {
340
- action: (modalType === 'create' || modalType === 'edit') ? 'save' : modalType,
341
- ariaLabel: saveButtonText
342
- }
343
- });
344
- this._modalSaveButtonElm.setAttribute('aria-label', saveButtonText);
345
- const footerContainerElm = createDomElement('div', { className: 'footer-buttons' });
346
- if (modalType === 'mass-update' || modalType === 'mass-selection') {
347
- modalFooterElm.appendChild(selectionCounterElm);
348
- }
349
- footerContainerElm.appendChild(modalCancelButtonElm);
350
- footerContainerElm.appendChild(this._modalSaveButtonElm);
351
- modalFooterElm.appendChild(footerContainerElm);
352
- modalContentElm.appendChild(modalHeaderElm);
353
- modalContentElm.appendChild(modalBodyElm);
354
- modalContentElm.appendChild(modalFooterElm);
355
- this._modalElm.appendChild(modalContentElm);
356
- for (const columnDef of modalColumns) {
357
- if (columnDef.editor) {
358
- const itemContainer = createDomElement('div', { className: `item-details-container editor-${columnDef.id}` });
359
- if (layoutColCount === 1) {
360
- itemContainer.classList.add('slick-col-medium-12');
361
- }
362
- else {
363
- itemContainer.classList.add('slick-col-medium-6', `slick-col-xlarge-${12 / layoutColCount}`);
364
- }
365
- const templateItemLabelElm = createDomElement('div', {
366
- className: `item-details-label editor-${columnDef.id}`,
367
- innerHTML: sanitizeTextByAvailableSanitizer(this.gridOptions, this.getColumnLabel(columnDef) || 'n/a')
368
- });
369
- const templateItemEditorElm = createDomElement('div', {
370
- className: 'item-details-editor-container slick-cell',
371
- dataset: { editorid: `${columnDef.id}` },
372
- });
373
- const templateItemValidationElm = createDomElement('div', { className: `item-details-validation editor-${columnDef.id}` });
374
- // optionally add a reset button beside each editor
375
- if ((_k = this._options) === null || _k === void 0 ? void 0 : _k.showResetButtonOnEachEditor) {
376
- const editorResetButtonElm = this.createEditorResetButtonElement(`${columnDef.id}`);
377
- this._bindEventService.bind(editorResetButtonElm, 'click', this.handleResetInputValue.bind(this));
378
- templateItemLabelElm.appendChild(editorResetButtonElm);
379
- }
380
- itemContainer.appendChild(templateItemLabelElm);
381
- itemContainer.appendChild(templateItemEditorElm);
382
- itemContainer.appendChild(templateItemValidationElm);
383
- modalBodyElm.appendChild(itemContainer);
384
- }
385
- }
386
- // optionally add a form reset button
387
- if ((_l = this._options) === null || _l === void 0 ? void 0 : _l.showFormResetButton) {
388
- const resetButtonContainerElm = this.createFormResetButtonElement();
389
- this._bindEventService.bind(resetButtonContainerElm, 'click', this.handleResetFormClicked.bind(this));
390
- modalBodyElm.appendChild(resetButtonContainerElm);
391
- }
392
- document.body.appendChild(this._modalElm);
393
- document.body.classList.add('slick-modal-open'); // add backdrop to body
394
- this._bindEventService.bind(document.body, 'click', this.handleBodyClicked.bind(this));
395
- this._editors = {};
396
- this._editorContainers = modalColumns.map(col => modalBodyElm.querySelector(`[data-editorid=${col.id}]`)) || [];
397
- this._compositeOptions = { destroy: this.disposeComponent.bind(this), modalType, validationMsgPrefix: '* ', formValues: {}, editors: this._editors };
398
- const compositeEditor = new CompositeEditor(modalColumns, this._editorContainers, this._compositeOptions);
399
- this.grid.editActiveCell(compositeEditor);
400
- // --
401
- // Add a few Event Handlers
402
- // keyboard, blur & button event handlers
403
- this._bindEventService.bind(modalCloseButtonElm, 'click', this.cancelEditing.bind(this));
404
- this._bindEventService.bind(modalCancelButtonElm, 'click', this.cancelEditing.bind(this));
405
- this._bindEventService.bind(this._modalSaveButtonElm, 'click', this.handleSaveClicked.bind(this));
406
- this._bindEventService.bind(this._modalElm, 'keydown', this.handleKeyDown.bind(this));
407
- this._bindEventService.bind(this._modalElm, 'focusout', this.validateCurrentEditor.bind(this));
408
- this._bindEventService.bind(this._modalElm, 'blur', this.validateCurrentEditor.bind(this));
409
- // when any of the input of the composite editor form changes, we'll add/remove a "modified" CSS className for styling purposes
410
- this._eventHandler.subscribe(this.grid.onCompositeEditorChange, this.handleOnCompositeEditorChange.bind(this));
411
- // when adding a new row to the grid, we need to invalidate that row and re-render the grid
412
- this._eventHandler.subscribe(this.grid.onAddNewRow, (_e, args) => {
413
- this.insertNewItemInDataView(args.item);
414
- this._originalDataContext = args.item; // this becomes the new data context
415
- this.dispose();
416
- });
417
- }
418
- return this;
419
- }
420
- catch (error) {
421
- this.dispose();
422
- const errorMsg = (typeof error === 'string') ? error : ((_p = (_m = error === null || error === void 0 ? void 0 : error.message) !== null && _m !== void 0 ? _m : (_o = error === null || error === void 0 ? void 0 : error.body) === null || _o === void 0 ? void 0 : _o.message) !== null && _p !== void 0 ? _p : '');
423
- const errorCode = (typeof error === 'string') ? error : (_s = (_q = error === null || error === void 0 ? void 0 : error.status) !== null && _q !== void 0 ? _q : (_r = error === null || error === void 0 ? void 0 : error.body) === null || _r === void 0 ? void 0 : _r.status) !== null && _s !== void 0 ? _s : errorMsg;
424
- onError({ type: 'error', code: errorCode, message: errorMsg });
425
- return null;
426
- }
427
- }
428
- /** Cancel the Editing which will also close the modal window */
429
- async cancelEditing() {
430
- var _a, _b;
431
- let confirmed = true;
432
- if (this.formValues && Object.keys(this.formValues).length > 0 && typeof this._options.onClose === 'function') {
433
- confirmed = await this._options.onClose();
434
- }
435
- if (confirmed) {
436
- this.grid.getEditController().cancelCurrentEdit();
437
- // cancel current edit is not enough when editing/cloning,
438
- // we also need to reset with the original item data context to undo/reset the entire row
439
- if (((_a = this._options) === null || _a === void 0 ? void 0 : _a.modalType) === 'edit' || ((_b = this._options) === null || _b === void 0 ? void 0 : _b.modalType) === 'clone') {
440
- this.resetCurrentRowDataContext();
441
- }
442
- this.grid.setActiveRow(this._lastActiveRowNumber);
443
- this.dispose();
444
- }
445
- }
446
- /** Show a Validation Summary text (as a <div>) when a validation fails or simply hide it when there's no error */
447
- showValidationSummaryText(isShowing, errorMsg = '') {
448
- var _a, _b;
449
- if (isShowing && errorMsg !== '') {
450
- this._modalBodyTopValidationElm.textContent = errorMsg;
451
- this._modalBodyTopValidationElm.style.display = 'block';
452
- (_b = (_a = this._modalBodyTopValidationElm).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a);
453
- this._modalSaveButtonElm.disabled = false;
454
- this._modalSaveButtonElm.classList.remove('saving');
455
- }
456
- else {
457
- this._modalBodyTopValidationElm.style.display = 'none';
458
- this._modalBodyTopValidationElm.textContent = errorMsg;
459
- }
460
- }
461
- // --
462
- // protected methods
463
- // ----------------
464
- /** Apply Mass Update Changes (form values) to the entire dataset */
465
- applySaveMassUpdateChanges(formValues, _selection, applyToDataview = true) {
466
- // not applying to dataView means that we're doing a preview of dataset and we should use a deep copy of it instead of applying changes directly to it
467
- const data = applyToDataview ? this.dataView.getItems() : deepCopy(this.dataView.getItems());
468
- // from the "lastCompositeEditor" object that we kept as reference, it contains all the changes inside the "formValues" property
469
- // we can loop through these changes and apply them on the selected row indexes
470
- for (const itemProp in formValues) {
471
- if (itemProp in formValues) {
472
- data.forEach((dataContext) => {
473
- var _a;
474
- if (itemProp in formValues && (((_a = this._options) === null || _a === void 0 ? void 0 : _a.validateMassUpdateChange) === undefined || this._options.validateMassUpdateChange(itemProp, dataContext, formValues) !== false)) {
475
- dataContext[itemProp] = formValues[itemProp];
476
- }
477
- });
478
- }
479
- }
480
- // change the entire dataset with our updated dataset
481
- if (applyToDataview) {
482
- this.dataView.setItems(data, this.gridOptions.datasetIdPropertyName);
483
- this.grid.invalidate();
484
- }
485
- return data;
486
- }
487
- /** Apply Mass Changes to the Selected rows in the grid (form values) */
488
- applySaveMassSelectionChanges(formValues, selection, applyToDataview = true) {
489
- var _a, _b;
490
- const selectedItemIds = (_a = selection === null || selection === void 0 ? void 0 : selection.dataContextIds) !== null && _a !== void 0 ? _a : [];
491
- const selectedTmpItems = selectedItemIds.map(itemId => this.dataView.getItemById(itemId));
492
- // not applying to dataView means that we're doing a preview of dataset and we should use a deep copy of it instead of applying changes directly to it
493
- const selectedItems = applyToDataview ? selectedTmpItems : deepCopy(selectedTmpItems);
494
- // from the "lastCompositeEditor" object that we kept as reference, it contains all the changes inside the "formValues" property
495
- // we can loop through these changes and apply them on the selected row indexes
496
- for (const itemProp in formValues) {
497
- if (itemProp in formValues) {
498
- selectedItems.forEach((dataContext) => {
499
- var _a;
500
- if (itemProp in formValues && (((_a = this._options) === null || _a === void 0 ? void 0 : _a.validateMassUpdateChange) === undefined || this._options.validateMassUpdateChange(itemProp, dataContext, formValues) !== false)) {
501
- dataContext[itemProp] = formValues[itemProp];
502
- }
503
- });
504
- }
505
- }
506
- // update all items in the grid with the grid service
507
- if (applyToDataview) {
508
- (_b = this.gridService) === null || _b === void 0 ? void 0 : _b.updateItems(selectedItems);
509
- }
510
- return selectedItems;
511
- }
512
- /**
513
- * Auto-Calculate how many columns to display in the view layout (1, 2, or 3).
514
- * We'll display a 1 column layout for 8 or less Editors, 2 columns layout for less than 15 Editors or 3 columns when more than 15 Editors
515
- * @param {number} editorCount - how many Editors do we have in total
516
- * @returns {number} count - calculated column count (1, 2 or 3)
517
- */
518
- autoCalculateLayoutColumnCount(editorCount) {
519
- if (editorCount >= 15) {
520
- return 3;
521
- }
522
- else if (editorCount >= 8) {
523
- return 2;
524
- }
525
- return 1;
526
- }
527
- /**
528
- * Create a reset button for each editor and attach a button click handler
529
- * @param {String} columnId - column id
530
- * @returns {Object} - html button
531
- */
532
- createEditorResetButtonElement(columnId) {
533
- var _a, _b, _c, _d, _f;
534
- const resetButtonElm = createDomElement('button', {
535
- type: 'button', name: columnId,
536
- title: (_c = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.labels) === null || _b === void 0 ? void 0 : _b.resetFormButton) !== null && _c !== void 0 ? _c : 'Reset Form Input',
537
- className: 'btn btn-xs btn-editor-reset'
538
- });
539
- resetButtonElm.setAttribute('aria-label', 'Reset');
540
- if ((_d = this._options) === null || _d === void 0 ? void 0 : _d.resetEditorButtonCssClass) {
541
- const resetBtnClasses = (_f = this._options) === null || _f === void 0 ? void 0 : _f.resetEditorButtonCssClass.split(' ');
542
- for (const cssClass of resetBtnClasses) {
543
- resetButtonElm.classList.add(cssClass);
544
- }
545
- }
546
- return resetButtonElm;
547
- }
548
- /**
549
- * Create a form reset button and attach a button click handler
550
- * @param {String} columnId - column id
551
- * @returns {Object} - html button
552
- */
553
- createFormResetButtonElement() {
554
- var _a, _b;
555
- const resetButtonContainerElm = createDomElement('div', { className: 'reset-container' });
556
- const resetButtonElm = createDomElement('button', { type: 'button', className: 'btn btn-sm reset-form' });
557
- const resetIconSpanElm = createDomElement('span', { className: (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.resetFormButtonIconCssClass) !== null && _b !== void 0 ? _b : '' });
558
- resetButtonElm.appendChild(resetIconSpanElm);
559
- resetButtonElm.appendChild(document.createTextNode(' Reset Form'));
560
- resetButtonContainerElm.appendChild(resetButtonElm);
561
- return resetButtonContainerElm;
562
- }
563
- /**
564
- * Execute the onError callback when defined
565
- * or use the default onError callback which is to simply display the error in the console
566
- */
567
- executeOnError(error) {
568
- var _a, _b;
569
- const onError = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.onError) !== null && _b !== void 0 ? _b : DEFAULT_ON_ERROR;
570
- onError(error);
571
- }
572
- /**
573
- * A simple and generic method to execute the "OnSave" callback if it's defined by the user OR else simply execute built-in apply changes callback.
574
- * This method deals with multiple callbacks as shown below
575
- * @param {Function} applyChangesCallback - first callback to apply the changes into the grid (this could be a user custom callback)
576
- * @param {Function} executePostCallback - second callback to execute right after the "onSave"
577
- * @param {Function} beforeClosingCallback - third and last callback to execute after Saving but just before closing the modal window
578
- * @param {Object} itemDataContext - item data context when modal type is (create/clone/edit)
579
- */
580
- async executeOnSave(applyChangesCallback, executePostCallback, beforeClosingCallback, itemDataContext) {
581
- var _a, _b, _c, _d, _f, _g;
582
- try {
583
- this.showValidationSummaryText(false, '');
584
- const validationResults = this.validateCompositeEditors();
585
- if (validationResults.valid) {
586
- this._modalSaveButtonElm.classList.add('saving');
587
- this._modalSaveButtonElm.disabled = true;
588
- if (typeof ((_a = this._options) === null || _a === void 0 ? void 0 : _a.onSave) === 'function') {
589
- const isMassChange = (this._options.modalType === 'mass-update' || this._options.modalType === 'mass-selection');
590
- // apply the changes in the grid early when that option is enabled (that is before the await of `onSave`)
591
- let updatedDataset;
592
- if (isMassChange && ((_b = this._options) === null || _b === void 0 ? void 0 : _b.shouldPreviewMassChangeDataset)) {
593
- updatedDataset = applyChangesCallback(this.formValues, this.getCurrentRowSelections(), false);
594
- }
595
- // call the custon onSave callback when defined and note that the item data context will only be filled for create/clone/edit
596
- const dataContextOrUpdatedDatasetPreview = isMassChange ? updatedDataset : itemDataContext;
597
- const successful = await ((_c = this._options) === null || _c === void 0 ? void 0 : _c.onSave(this.formValues, this.getCurrentRowSelections(), dataContextOrUpdatedDatasetPreview));
598
- if (successful) {
599
- // apply the changes in the grid (if it's not yet applied)
600
- applyChangesCallback(this.formValues, this.getCurrentRowSelections());
601
- // once we're done doing the mass update, we can cancel the current editor since we don't want to add any new row
602
- // that will also destroy/close the modal window
603
- executePostCallback();
604
- }
605
- }
606
- else {
607
- applyChangesCallback(this.formValues, this.getCurrentRowSelections());
608
- executePostCallback();
609
- }
610
- // run any function before closing the modal
611
- if (typeof beforeClosingCallback === 'function') {
612
- beforeClosingCallback();
613
- }
614
- // close the modal only when successful
615
- this.dispose();
616
- }
617
- }
618
- catch (error) {
619
- const errorMsg = (typeof error === 'string') ? error : ((_g = (_d = error === null || error === void 0 ? void 0 : error.message) !== null && _d !== void 0 ? _d : (_f = error === null || error === void 0 ? void 0 : error.body) === null || _f === void 0 ? void 0 : _f.message) !== null && _g !== void 0 ? _g : '');
620
- this.showValidationSummaryText(true, errorMsg);
621
- }
622
- }
623
- // For the Composite Editor to work, the current active cell must have an Editor (because it calls editActiveCell() and that only works with a cell with an Editor)
624
- // so if current active cell doesn't have an Editor, we'll find the first column with an Editor and focus on it (from left to right starting at index 0)
625
- focusOnFirstColumnCellWithEditor(columns, dataContext, columnIndex, rowIndex, isWithMassChange) {
626
- // make sure we're not trying to activate a cell outside of the grid, that can happen when using MassUpdate without `enableAddRow` flag enabled
627
- const activeCellIndex = (isWithMassChange && !this.gridOptions.enableAddRow && (rowIndex >= this.dataViewLength)) ? this.dataViewLength - 1 : rowIndex;
628
- let columnIndexWithEditor = columnIndex;
629
- const cellEditor = columns[columnIndex].editor;
630
- let activeEditorCellNode = this.grid.getCellNode(activeCellIndex, columnIndex);
631
- if (!cellEditor || !activeEditorCellNode || !this.getActiveCellEditor(activeCellIndex, columnIndex)) {
632
- columnIndexWithEditor = this.findNextAvailableEditorColumnIndex(columns, dataContext, rowIndex, isWithMassChange);
633
- if (columnIndexWithEditor === -1) {
634
- this.executeOnError({ type: 'error', code: 'NO_EDITOR_FOUND', message: 'We could not find any Editor in your Column Definition' });
635
- return false;
636
- }
637
- else {
638
- this.grid.setActiveCell(activeCellIndex, columnIndexWithEditor, false);
639
- if (isWithMassChange) {
640
- // when it's a mass change, we'll activate the last row without scrolling to it
641
- // that is possible via the 3rd argument "suppressScrollIntoView" set to "true"
642
- this.grid.setActiveRow(this.dataViewLength, columnIndexWithEditor, true);
643
- }
644
- }
645
- }
646
- // check again if the cell node is now being created, if it is then we're good
647
- activeEditorCellNode = this.grid.getCellNode(activeCellIndex, columnIndexWithEditor);
648
- return !!activeEditorCellNode;
649
- }
650
- findNextAvailableEditorColumnIndex(columns, dataContext, rowIndex, isWithMassUpdate) {
651
- var _a;
652
- let columnIndexWithEditor = -1;
653
- for (let colIndex = 0; colIndex < columns.length; colIndex++) {
654
- const col = columns[colIndex];
655
- if (col.editor && (!isWithMassUpdate || (isWithMassUpdate && ((_a = col.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.massUpdate)))) {
656
- // we can check that the cell is really editable by checking the onBeforeEditCell event not returning false (returning undefined, null also mean it is editable)
657
- const isCellEditable = this.grid.onBeforeEditCell.notify({ row: rowIndex, cell: colIndex, item: dataContext, column: col, grid: this.grid, target: 'composite', compositeEditorOptions: this._compositeOptions });
658
- this.grid.setActiveCell(rowIndex, colIndex, false);
659
- if (isCellEditable !== false) {
660
- columnIndexWithEditor = colIndex;
661
- break;
662
- }
663
- }
664
- }
665
- return columnIndexWithEditor;
666
- }
667
- /**
668
- * Get a column definition by providing a column id OR a column definition.
669
- * If the input is a string, we'll assume it's a columnId and we'll simply search for the column in the column definitions list
670
- */
671
- getColumnByObjectOrId(columnIdOrDef) {
672
- let column;
673
- if (typeof columnIdOrDef === 'object') {
674
- column = columnIdOrDef;
675
- }
676
- else if (typeof columnIdOrDef === 'string') {
677
- column = this._columnDefinitions.find(col => col.id === columnIdOrDef);
678
- }
679
- return column;
680
- }
681
- getActiveCellEditor(row, cell) {
682
- this.grid.setActiveCell(row, cell, false);
683
- return this.grid.getCellEditor();
684
- }
685
- /**
686
- * Get the column label, the label might have an optional "columnGroup" (or "columnGroupKey" which need to be translated)
687
- * @param {object} columnDef - column definition
688
- * @returns {string} label - column label
689
- */
690
- getColumnLabel(columnDef) {
691
- var _a;
692
- const columnGroupSeparator = this.gridOptions.columnGroupSeparator || ' - ';
693
- let columnName = columnDef.nameCompositeEditor || columnDef.name || '';
694
- let columnGroup = columnDef.columnGroup || '';
695
- if (this.gridOptions.enableTranslate && this.translaterService) {
696
- const translationKey = columnDef.nameCompositeEditorKey || columnDef.nameKey;
697
- if (translationKey) {
698
- columnName = this.translaterService.translate(translationKey);
699
- }
700
- if (columnDef.columnGroupKey && ((_a = this.translaterService) === null || _a === void 0 ? void 0 : _a.translate)) {
701
- columnGroup = this.translaterService.translate(columnDef.columnGroupKey);
702
- }
703
- }
704
- const columnLabel = columnGroup ? `${columnGroup}${columnGroupSeparator}${columnName}` : columnName;
705
- return columnLabel || '';
706
- }
707
- /** Get the correct label text depending, if we use a Translater Service then translate the text when possible else use default text */
708
- getLabelText(labelProperty, localeText, defaultText) {
709
- var _a, _b, _c, _d, _f, _g, _h;
710
- const textLabels = { ...(_a = this.gridOptions.compositeEditorOptions) === null || _a === void 0 ? void 0 : _a.labels, ...(_b = this._options) === null || _b === void 0 ? void 0 : _b.labels };
711
- if (((_c = this.gridOptions) === null || _c === void 0 ? void 0 : _c.enableTranslate) && ((_d = this.translaterService) === null || _d === void 0 ? void 0 : _d.translate) && textLabels.hasOwnProperty(`${labelProperty}Key`)) {
712
- const translationKey = textLabels[`${labelProperty}Key`];
713
- return this.translaterService.translate(translationKey || '');
714
- }
715
- return (_h = (_f = textLabels === null || textLabels === void 0 ? void 0 : textLabels[labelProperty]) !== null && _f !== void 0 ? _f : (_g = this._locales) === null || _g === void 0 ? void 0 : _g[localeText]) !== null && _h !== void 0 ? _h : defaultText;
716
- }
717
- /** Retrieve the current selection of row indexes & data context Ids */
718
- getCurrentRowSelections() {
719
- const dataContextIds = this.dataView.getAllSelectedIds();
720
- const gridRowIndexes = this.dataView.mapIdsToRows(dataContextIds);
721
- return { gridRowIndexes, dataContextIds };
722
- }
723
- handleBodyClicked(event) {
724
- var _a, _b, _c;
725
- if ((_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.classList) === null || _b === void 0 ? void 0 : _b.contains('slick-editor-modal')) {
726
- if (((_c = this._options) === null || _c === void 0 ? void 0 : _c.backdrop) !== 'static') {
727
- this.dispose();
728
- }
729
- }
730
- }
731
- handleKeyDown(event) {
732
- if (event.code === 'Escape') {
733
- this.cancelEditing();
734
- event.stopPropagation();
735
- event.preventDefault();
736
- }
737
- else if (event.code === 'Tab') {
738
- this.validateCurrentEditor();
739
- }
740
- }
741
- handleResetInputValue(event) {
742
- var _a, _b;
743
- const columnId = event.target.name;
744
- const editor = (_a = this._editors) === null || _a === void 0 ? void 0 : _a[columnId];
745
- if (editor === null || editor === void 0 ? void 0 : editor.reset) {
746
- editor.reset();
747
- }
748
- (_b = this._formValues) === null || _b === void 0 ? true : delete _b[columnId];
749
- }
750
- /** Callback which processes a Mass Update or Mass Selection Changes */
751
- async handleMassSaving(modalType, executePostCallback) {
752
- if (!this.formValues || Object.keys(this.formValues).length === 0) {
753
- this.executeOnError({ type: 'warning', code: 'NO_CHANGES_DETECTED', message: 'Sorry we could not detect any changes.' });
754
- }
755
- else {
756
- const applyCallbackFnName = (modalType === 'mass-update') ? 'applySaveMassUpdateChanges' : 'applySaveMassSelectionChanges';
757
- this.executeOnSave(this[applyCallbackFnName].bind(this), executePostCallback.bind(this));
758
- }
759
- }
760
- /** Anytime an input of the Composite Editor form changes, we'll add/remove a "modified" CSS className for styling purposes */
761
- handleOnCompositeEditorChange(_e, args) {
762
- var _a, _b, _c, _d, _f, _g, _h, _j;
763
- const columnId = (_b = (_a = args.column) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '';
764
- this._formValues = { ...this._formValues, ...args.formValues };
765
- const editor = (_c = this._editors) === null || _c === void 0 ? void 0 : _c[columnId];
766
- const isEditorValueTouched = (_h = (_f = (_d = editor === null || editor === void 0 ? void 0 : editor.isValueTouched) === null || _d === void 0 ? void 0 : _d.call(editor)) !== null && _f !== void 0 ? _f : (_g = editor === null || editor === void 0 ? void 0 : editor.isValueChanged) === null || _g === void 0 ? void 0 : _g.call(editor)) !== null && _h !== void 0 ? _h : false;
767
- this._itemDataContext = (_j = editor === null || editor === void 0 ? void 0 : editor.dataContext) !== null && _j !== void 0 ? _j : {}; // keep reference of the item data context
768
- // add extra css styling to the composite editor input(s) that got modified
769
- const editorElm = this._modalElm.querySelector(`[data-editorid=${columnId}]`);
770
- if (editorElm === null || editorElm === void 0 ? void 0 : editorElm.classList) {
771
- if (isEditorValueTouched) {
772
- editorElm.classList.add('modified');
773
- }
774
- else {
775
- editorElm.classList.remove('modified');
776
- }
777
- }
778
- // after any input changes we'll re-validate all fields
779
- this.validateCompositeEditors();
780
- }
781
- /** Check wether the grid has the Row Selection enabled */
782
- hasRowSelectionEnabled() {
783
- const selectionModel = this.grid.getSelectionModel();
784
- const isRowSelectionEnabled = this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector;
785
- return (isRowSelectionEnabled && selectionModel);
786
- }
787
- /** Reset Form button handler */
788
- handleResetFormClicked() {
789
- for (const columnId of Object.keys(this._editors)) {
790
- const editor = this._editors[columnId];
791
- if (editor === null || editor === void 0 ? void 0 : editor.reset) {
792
- editor.reset();
793
- }
794
- }
795
- this._formValues = emptyObject(this._formValues);
796
- }
797
- /** switch case handler to determine which code to execute depending on the modal type */
798
- handleSaveClicked() {
799
- var _a, _b, _c;
800
- const modalType = (_a = this._options) === null || _a === void 0 ? void 0 : _a.modalType;
801
- switch (modalType) {
802
- case 'mass-update':
803
- this.handleMassSaving(modalType, () => {
804
- this.grid.getEditController().cancelCurrentEdit();
805
- this.grid.setActiveCell(0, 0, false);
806
- if (this._options.shouldClearRowSelectionAfterMassAction) {
807
- this.grid.setSelectedRows([]);
808
- }
809
- });
810
- break;
811
- case 'mass-selection':
812
- this.handleMassSaving(modalType, () => {
813
- this.grid.getEditController().cancelCurrentEdit();
814
- this.grid.setActiveRow(this._lastActiveRowNumber);
815
- if (this._options.shouldClearRowSelectionAfterMassAction) {
816
- this.grid.setSelectedRows([]);
817
- }
818
- });
819
- break;
820
- case 'clone':
821
- // the clone object will be a merge of the selected data context (original object) with the changed form values
822
- const clonedItemDataContext = { ...this._originalDataContext, ...this.formValues };
823
- // post save callback (before closing modal)
824
- const postSaveCloneCallback = () => {
825
- this.grid.getEditController().cancelCurrentEdit();
826
- this.grid.setActiveCell(0, 0, false);
827
- };
828
- // call the onSave execution and provide the item data context so that it's available to the user
829
- this.executeOnSave(this.insertNewItemInDataView.bind(this, clonedItemDataContext), postSaveCloneCallback, this.resetCurrentRowDataContext.bind(this), clonedItemDataContext);
830
- break;
831
- case 'create':
832
- case 'edit':
833
- default:
834
- // commit the changes into the grid
835
- // if it's a "create" then it will triggered the "onAddNewRow" event which will in term push it to the grid
836
- // while an "edit" will simply applies the changes directly on the same row
837
- this.grid.getEditController().commitCurrentEdit();
838
- // if the user provided the "onSave" callback, let's execute it with the item data context
839
- if (typeof ((_b = this._options) === null || _b === void 0 ? void 0 : _b.onSave) === 'function') {
840
- const itemDataContext = this.grid.getDataItem(this._lastActiveRowNumber); // we can get item data context directly from DataView
841
- (_c = this._options) === null || _c === void 0 ? void 0 : _c.onSave(this.formValues, this.getCurrentRowSelections(), itemDataContext);
842
- }
843
- break;
844
- }
845
- }
846
- /** Insert an item into the DataView or throw an error when finding duplicate id in the dataset */
847
- insertNewItemInDataView(item) {
848
- var _a, _b, _c, _d;
849
- const fullDatasetLength = (_b = (_a = this.dataView) === null || _a === void 0 ? void 0 : _a.getItemCount()) !== null && _b !== void 0 ? _b : 0;
850
- const newId = (_c = this._options.insertNewId) !== null && _c !== void 0 ? _c : fullDatasetLength + 1;
851
- item[this.gridOptions.datasetIdPropertyName || 'id'] = newId;
852
- if (!this.dataView.getItemById(newId)) {
853
- (_d = this.gridService) === null || _d === void 0 ? void 0 : _d.addItem(item, this._options.insertOptions);
854
- }
855
- else {
856
- this.executeOnError({ type: 'error', code: 'ITEM_ALREADY_EXIST', message: `The item object which you are trying to add already exist with the same Id:: ${newId}` });
857
- }
858
- }
859
- parseText(inputText, mappedArgs) {
860
- return inputText.replace(/\{\{(.*?)\}\}/g, (match, group) => {
861
- return mappedArgs[group] !== undefined ? mappedArgs[group] : match;
862
- });
863
- }
864
- /** Put back the current row to its original item data context using the DataView without triggering a change */
865
- resetCurrentRowDataContext() {
866
- const idPropName = this.gridOptions.datasetIdPropertyName || 'id';
867
- const dataView = this.grid.getData();
868
- dataView.updateItem(this._originalDataContext[idPropName], this._originalDataContext);
869
- }
870
- /** Validate all the Composite Editors that are defined in the form */
871
- validateCompositeEditors(targetElm) {
872
- let validationResults = { valid: true, msg: '' };
873
- const currentEditor = this.grid.getCellEditor();
874
- if (currentEditor) {
875
- validationResults = currentEditor.validate(targetElm);
876
- }
877
- return validationResults;
878
- }
879
- /** Validate the current cell editor */
880
- validateCurrentEditor() {
881
- const currentEditor = this.grid.getCellEditor();
882
- if (currentEditor === null || currentEditor === void 0 ? void 0 : currentEditor.validate) {
883
- currentEditor.validate();
884
- }
885
- }
886
- }
1
+ import { deepCopy, deepMerge, emptyObject, setDeepValue } from '@slickgrid-universal/utils';
2
+ import { BindingEventService, Constants, createDomElement, getDescendantProperty, numericSortComparer, sanitizeTextByAvailableSanitizer, SortDirectionNumber, } from '@slickgrid-universal/common';
3
+ import { CompositeEditor } from './compositeEditor.factory';
4
+ const DEFAULT_ON_ERROR = (error) => console.log(error.message);
5
+ export class SlickCompositeEditorComponent {
6
+ get eventHandler() {
7
+ return this._eventHandler;
8
+ }
9
+ get dataView() {
10
+ return this.grid.getData();
11
+ }
12
+ get dataViewLength() {
13
+ return this.dataView.getLength();
14
+ }
15
+ get formValues() {
16
+ return this._formValues;
17
+ }
18
+ get editors() {
19
+ return this._editors;
20
+ }
21
+ set editors(editors) {
22
+ this._editors = editors;
23
+ }
24
+ get gridOptions() {
25
+ var _a;
26
+ return (_a = this.grid) === null || _a === void 0 ? void 0 : _a.getOptions();
27
+ }
28
+ constructor() {
29
+ this._columnDefinitions = [];
30
+ this._lastActiveRowNumber = -1;
31
+ this._formValues = null;
32
+ this.gridService = null;
33
+ this._eventHandler = new Slick.EventHandler();
34
+ this._bindEventService = new BindingEventService();
35
+ }
36
+ /**
37
+ * initialize the Composite Editor by passing the SlickGrid object and the container service
38
+ *
39
+ * Note: we aren't using DI in the constructor simply to be as framework agnostic as possible,
40
+ * we are simply using this init() function with a very basic container service to do the job
41
+ */
42
+ init(grid, containerService) {
43
+ var _a, _b;
44
+ this.grid = grid;
45
+ this.gridService = containerService.get('GridService');
46
+ this.translaterService = containerService.get('TranslaterService');
47
+ if (!this.gridService) {
48
+ throw new Error('[Slickgrid-Universal] it seems that the GridService is not being loaded properly, make sure the Container Service is properly implemented.');
49
+ }
50
+ if (this.gridOptions.enableTranslate && (!this.translaterService || !this.translaterService.translate)) {
51
+ throw new Error('[Slickgrid-Universal] requires a Translate Service to be installed and configured when the grid option "enableTranslate" is enabled.');
52
+ }
53
+ // get locales provided by user in forRoot or else use default English locales via the Constants
54
+ this._locales = (_b = (_a = this.gridOptions) === null || _a === void 0 ? void 0 : _a.locales) !== null && _b !== void 0 ? _b : Constants.locales;
55
+ }
56
+ /** Dispose of the Component & unsubscribe all events */
57
+ dispose() {
58
+ this._eventHandler.unsubscribeAll();
59
+ this._bindEventService.unbindAll();
60
+ this._formValues = null;
61
+ this.disposeComponent();
62
+ }
63
+ /** Dispose of the Component without unsubscribing any events */
64
+ disposeComponent() {
65
+ var _a, _b, _c;
66
+ // protected _editorContainers!: Array<HTMLElement | null>;
67
+ (_a = this._modalBodyTopValidationElm) === null || _a === void 0 ? void 0 : _a.remove();
68
+ (_b = this._modalSaveButtonElm) === null || _b === void 0 ? void 0 : _b.remove();
69
+ if (typeof ((_c = this._modalElm) === null || _c === void 0 ? void 0 : _c.remove) === 'function') {
70
+ this._modalElm.remove();
71
+ // remove the body backdrop click listener, every other listeners will be dropped automatically since we destroy the component
72
+ document.body.classList.remove('slick-modal-open');
73
+ }
74
+ this._editorContainers = [];
75
+ }
76
+ /**
77
+ * Dynamically change value of an input from the Composite Editor form.
78
+ *
79
+ * NOTE: user might get an error thrown when trying to apply a value on a Composite Editor that was not found in the form,
80
+ * but in some cases the user might still want the value to be applied to the formValues so that it will be sent to the save in final item data context
81
+ * and when that happens, you can just skip that error so it won't throw.
82
+ * @param {String | Column} columnIdOrDef - column id or column definition
83
+ * @param {*} newValue - the new value
84
+ * @param {Boolean} skipMissingEditorError - defaults to False, skipping the error when the Composite Editor was not found will allow to still apply the value into the formValues object
85
+ * @param {Boolean} triggerOnCompositeEditorChange - defaults to True, will this change trigger a onCompositeEditorChange event?
86
+ */
87
+ changeFormInputValue(columnIdOrDef, newValue, skipMissingEditorError = false, triggerOnCompositeEditorChange = true) {
88
+ var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o;
89
+ const columnDef = this.getColumnByObjectOrId(columnIdOrDef);
90
+ const columnId = typeof columnIdOrDef === 'string' ? columnIdOrDef : (_a = columnDef === null || columnDef === void 0 ? void 0 : columnDef.id) !== null && _a !== void 0 ? _a : '';
91
+ const editor = (_b = this._editors) === null || _b === void 0 ? void 0 : _b[columnId];
92
+ let outputValue = newValue;
93
+ if (!editor && !skipMissingEditorError) {
94
+ throw new Error(`Composite Editor with column id "${columnId}" not found.`);
95
+ }
96
+ if (typeof (editor === null || editor === void 0 ? void 0 : editor.setValue) === 'function' && Array.isArray(this._editorContainers)) {
97
+ editor.setValue(newValue, true, triggerOnCompositeEditorChange);
98
+ const editorContainerElm = this._editorContainers.find(editorElm => editorElm.dataset.editorid === columnId);
99
+ const excludeDisabledFieldFormValues = (_f = (_d = (_c = this.gridOptions) === null || _c === void 0 ? void 0 : _c.compositeEditorOptions) === null || _d === void 0 ? void 0 : _d.excludeDisabledFieldFormValues) !== null && _f !== void 0 ? _f : false;
100
+ if (!editor.disabled || (editor.disabled && !excludeDisabledFieldFormValues)) {
101
+ (_g = editorContainerElm === null || editorContainerElm === void 0 ? void 0 : editorContainerElm.classList) === null || _g === void 0 ? void 0 : _g.add('modified');
102
+ }
103
+ else {
104
+ outputValue = '';
105
+ (_h = editorContainerElm === null || editorContainerElm === void 0 ? void 0 : editorContainerElm.classList) === null || _h === void 0 ? void 0 : _h.remove('modified');
106
+ }
107
+ // when the field is disabled, we will only allow a blank value anything else will be disregarded
108
+ if (editor.disabled && (outputValue !== '' || outputValue !== null || outputValue !== undefined || outputValue !== 0)) {
109
+ outputValue = '';
110
+ }
111
+ }
112
+ // is the field a complex object, like "address.streetNumber"
113
+ // we'll set assign the value as a complex object following the `field` dot notation
114
+ const fieldName = (_j = columnDef === null || columnDef === void 0 ? void 0 : columnDef.field) !== null && _j !== void 0 ? _j : '';
115
+ if (columnDef && (fieldName === null || fieldName === void 0 ? void 0 : fieldName.includes('.'))) {
116
+ // when it's a complex object, user could override the object path (where the editable object is located)
117
+ // else we use the path provided in the Field Column Definition
118
+ const objectPath = (_m = (_l = (_k = columnDef.internalColumnEditor) === null || _k === void 0 ? void 0 : _k.complexObjectPath) !== null && _l !== void 0 ? _l : fieldName) !== null && _m !== void 0 ? _m : '';
119
+ setDeepValue((_o = this._formValues) !== null && _o !== void 0 ? _o : {}, objectPath, newValue);
120
+ }
121
+ else {
122
+ this._formValues = { ...this._formValues, [columnId]: outputValue };
123
+ }
124
+ }
125
+ /**
126
+ * Dynamically update the `formValues` object directly without triggering the onCompositeEditorChange event.
127
+ * The fact that this doesn't trigger an event, might not always be good though, in these cases you are probably better with using the changeFormInputValue() method
128
+ * @param {String | Column} columnIdOrDef - column id or column definition
129
+ * @param {*} newValue - the new value
130
+ */
131
+ changeFormValue(columnIdOrDef, newValue) {
132
+ var _a, _b, _c, _d, _f;
133
+ const columnDef = this.getColumnByObjectOrId(columnIdOrDef);
134
+ const columnId = typeof columnIdOrDef === 'string' ? columnIdOrDef : (_a = columnDef === null || columnDef === void 0 ? void 0 : columnDef.id) !== null && _a !== void 0 ? _a : '';
135
+ // is the field a complex object, like "address.streetNumber"
136
+ // we'll set assign the value as a complex object following the `field` dot notation
137
+ const fieldName = (_b = columnDef === null || columnDef === void 0 ? void 0 : columnDef.field) !== null && _b !== void 0 ? _b : columnIdOrDef;
138
+ if (fieldName === null || fieldName === void 0 ? void 0 : fieldName.includes('.')) {
139
+ // when it's a complex object, user could override the object path (where the editable object is located)
140
+ // else we use the path provided in the Field Column Definition
141
+ const objectPath = (_f = (_d = (_c = columnDef === null || columnDef === void 0 ? void 0 : columnDef.internalColumnEditor) === null || _c === void 0 ? void 0 : _c.complexObjectPath) !== null && _d !== void 0 ? _d : fieldName) !== null && _f !== void 0 ? _f : '';
142
+ setDeepValue(this._formValues, objectPath, newValue);
143
+ }
144
+ else {
145
+ this._formValues = { ...this._formValues, [columnId]: newValue };
146
+ }
147
+ this._formValues = deepMerge({}, this._itemDataContext, this._formValues);
148
+ }
149
+ /**
150
+ * Dynamically change an Editor option of the Composite Editor form
151
+ * For example, a use case could be to dynamically change the "minDate" of another Date Editor in the Composite Editor form.
152
+ * @param {String} columnId - column id
153
+ * @param {*} newValue - the new value
154
+ */
155
+ changeFormEditorOption(columnId, optionName, newOptionValue) {
156
+ var _a;
157
+ const editor = (_a = this._editors) === null || _a === void 0 ? void 0 : _a[columnId];
158
+ // change an Editor option (not all Editors have that method, so make sure it exists before trying to call it)
159
+ if (editor === null || editor === void 0 ? void 0 : editor.changeEditorOption) {
160
+ editor.changeEditorOption(optionName, newOptionValue);
161
+ }
162
+ else {
163
+ throw new Error(`Editor with column id "${columnId}" not found OR the Editor does not support "changeEditorOption" (current only available with AutoComplete, Date, MultipleSelect & SingleSelect Editors).`);
164
+ }
165
+ }
166
+ /**
167
+ * Disable (or enable) an input of the Composite Editor form
168
+ * @param {String} columnId - column definition id
169
+ * @param isDisabled - defaults to True, are we disabling the associated form input
170
+ */
171
+ disableFormInput(columnId, isDisabled = true) {
172
+ var _a;
173
+ const editor = (_a = this._editors) === null || _a === void 0 ? void 0 : _a[columnId];
174
+ if ((editor === null || editor === void 0 ? void 0 : editor.disable) && Array.isArray(this._editorContainers)) {
175
+ editor.disable(isDisabled);
176
+ }
177
+ }
178
+ /** Entry point to initialize and open the Composite Editor modal window */
179
+ openDetails(options) {
180
+ var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
181
+ const onError = (_a = options.onError) !== null && _a !== void 0 ? _a : DEFAULT_ON_ERROR;
182
+ const defaultOptions = {
183
+ backdrop: 'static',
184
+ showCloseButtonOutside: true,
185
+ shouldClearRowSelectionAfterMassAction: true,
186
+ viewColumnLayout: 'auto',
187
+ modalType: 'edit',
188
+ };
189
+ try {
190
+ if (!this.grid || (this.grid.getEditorLock().isActive() && !this.grid.getEditorLock().commitCurrentEdit())) {
191
+ return null;
192
+ }
193
+ this._formValues = null; // make sure there's no leftover from previous change
194
+ this._options = { ...defaultOptions, ...this.gridOptions.compositeEditorOptions, ...options, labels: { ...(_b = this.gridOptions.compositeEditorOptions) === null || _b === void 0 ? void 0 : _b.labels, ...options === null || options === void 0 ? void 0 : options.labels } }; // merge default options with user options
195
+ this._options.backdrop = options.backdrop !== undefined ? options.backdrop : 'static';
196
+ const viewColumnLayout = this._options.viewColumnLayout || 1;
197
+ const activeCell = this.grid.getActiveCell();
198
+ const activeColIndex = (_c = activeCell === null || activeCell === void 0 ? void 0 : activeCell.cell) !== null && _c !== void 0 ? _c : 0;
199
+ const activeRow = (_d = activeCell === null || activeCell === void 0 ? void 0 : activeCell.row) !== null && _d !== void 0 ? _d : 0;
200
+ const gridUid = this.grid.getUID() || '';
201
+ let headerTitle = options.headerTitle || '';
202
+ // execute callback before creating the modal window (that is in short the first event in the lifecycle)
203
+ if (typeof this._options.onBeforeOpen === 'function') {
204
+ this._options.onBeforeOpen();
205
+ }
206
+ if (this.hasRowSelectionEnabled() && this._options.modalType === 'auto-mass' && this.grid.getSelectedRows) {
207
+ const selectedRowsIndexes = this.grid.getSelectedRows() || [];
208
+ if (selectedRowsIndexes.length > 0) {
209
+ this._options.modalType = 'mass-selection';
210
+ if (options === null || options === void 0 ? void 0 : options.headerTitleMassSelection) {
211
+ headerTitle = options === null || options === void 0 ? void 0 : options.headerTitleMassSelection;
212
+ }
213
+ }
214
+ else {
215
+ this._options.modalType = 'mass-update';
216
+ if (options === null || options === void 0 ? void 0 : options.headerTitleMassUpdate) {
217
+ headerTitle = options === null || options === void 0 ? void 0 : options.headerTitleMassUpdate;
218
+ }
219
+ }
220
+ }
221
+ const modalType = this._options.modalType || 'edit';
222
+ if (!this.gridOptions.editable) {
223
+ onError({ type: 'error', code: 'EDITABLE_GRID_REQUIRED', message: 'Your grid must be editable in order to use the Composite Editor Modal.' });
224
+ return null;
225
+ }
226
+ else if (!this.gridOptions.enableCellNavigation) {
227
+ onError({ type: 'error', code: 'ENABLE_CELL_NAVIGATION_REQUIRED', message: 'Composite Editor requires the flag "enableCellNavigation" to be set to True in your Grid Options.' });
228
+ return null;
229
+ }
230
+ else if (!this.gridOptions.enableAddRow && (modalType === 'clone' || modalType === 'create')) {
231
+ onError({ type: 'error', code: 'ENABLE_ADD_ROW_REQUIRED', message: 'Composite Editor requires the flag "enableAddRow" to be set to True in your Grid Options when cloning/creating a new item.' });
232
+ return null;
233
+ }
234
+ else if (!activeCell && (modalType === 'clone' || modalType === 'edit')) {
235
+ onError({ type: 'warning', code: 'NO_RECORD_FOUND', message: 'No records selected for edit or clone operation.' });
236
+ return null;
237
+ }
238
+ else {
239
+ const isWithMassChange = (modalType === 'mass-update' || modalType === 'mass-selection');
240
+ const dataContext = !isWithMassChange ? this.grid.getDataItem(activeRow) : {};
241
+ this._originalDataContext = deepCopy(dataContext);
242
+ this._columnDefinitions = this.grid.getColumns();
243
+ const selectedRowsIndexes = this.hasRowSelectionEnabled() ? this.grid.getSelectedRows() : [];
244
+ const fullDatasetLength = (_g = (_f = this.dataView) === null || _f === void 0 ? void 0 : _f.getItemCount()) !== null && _g !== void 0 ? _g : 0;
245
+ this._lastActiveRowNumber = activeRow;
246
+ const dataContextIds = this.dataView.getAllSelectedIds();
247
+ // focus on a first cell with an Editor (unless current cell already has an Editor then do nothing)
248
+ // also when it's a "Create" modal, we'll scroll to the end of the grid
249
+ const rowIndex = modalType === 'create' ? this.dataViewLength : activeRow;
250
+ const hasFoundEditor = this.focusOnFirstColumnCellWithEditor(this._columnDefinitions, dataContext, activeColIndex, rowIndex, isWithMassChange);
251
+ if (!hasFoundEditor) {
252
+ return null;
253
+ }
254
+ if (modalType === 'edit' && !dataContext) {
255
+ onError({ type: 'warning', code: 'ROW_NOT_EDITABLE', message: 'Current row is not editable.' });
256
+ return null;
257
+ }
258
+ else if (modalType === 'mass-selection') {
259
+ if (selectedRowsIndexes.length < 1) {
260
+ onError({ type: 'warning', code: 'ROW_SELECTION_REQUIRED', message: 'You must select some rows before trying to apply new value(s).' });
261
+ return null;
262
+ }
263
+ }
264
+ let modalColumns = [];
265
+ if (isWithMassChange) {
266
+ // when using Mass Update, we only care about the columns that have the "massUpdate: true", we disregard anything else
267
+ modalColumns = this._columnDefinitions.filter(col => { var _a; return col.editor && ((_a = col.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.massUpdate) === true; });
268
+ }
269
+ else {
270
+ modalColumns = this._columnDefinitions.filter(col => col.editor);
271
+ }
272
+ // user could optionally show the form inputs in a specific order instead of using default column definitions order
273
+ if (modalColumns.some(col => { var _a; return ((_a = col.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.compositeEditorFormOrder) !== undefined; })) {
274
+ modalColumns.sort((col1, col2) => {
275
+ var _a, _b, _c, _d;
276
+ const val1 = (_b = (_a = col1 === null || col1 === void 0 ? void 0 : col1.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.compositeEditorFormOrder) !== null && _b !== void 0 ? _b : Infinity;
277
+ const val2 = (_d = (_c = col2 === null || col2 === void 0 ? void 0 : col2.internalColumnEditor) === null || _c === void 0 ? void 0 : _c.compositeEditorFormOrder) !== null && _d !== void 0 ? _d : Infinity;
278
+ return numericSortComparer(val1, val2, SortDirectionNumber.asc);
279
+ });
280
+ }
281
+ // open the editor modal and we can also provide a header title with optional parsing pulled from the dataContext, via template {{ }}
282
+ // for example {{title}} => display the item title, or even complex object works {{product.name}} => display item product name
283
+ const parsedHeaderTitle = headerTitle.replace(/\{\{(.*?)\}\}/g, (_match, group) => getDescendantProperty(dataContext, group));
284
+ const layoutColCount = viewColumnLayout === 'auto' ? this.autoCalculateLayoutColumnCount(modalColumns.length) : viewColumnLayout;
285
+ this._modalElm = createDomElement('div', { className: `slick-editor-modal ${gridUid}` });
286
+ const modalContentElm = createDomElement('div', { className: 'slick-editor-modal-content' });
287
+ if ((!isNaN(viewColumnLayout) && +viewColumnLayout > 1) || (viewColumnLayout === 'auto' && layoutColCount > 1)) {
288
+ const splitClassName = layoutColCount === 2 ? 'split-view' : 'triple-split-view';
289
+ modalContentElm.classList.add(splitClassName);
290
+ }
291
+ const modalHeaderTitleElm = createDomElement('div', {
292
+ className: 'slick-editor-modal-title',
293
+ innerHTML: sanitizeTextByAvailableSanitizer(this.gridOptions, parsedHeaderTitle),
294
+ });
295
+ const modalCloseButtonElm = createDomElement('button', { type: 'button', textContent: '×', className: 'close', dataset: { action: 'close' } });
296
+ modalCloseButtonElm.setAttribute('aria-label', 'Close');
297
+ if (this._options.showCloseButtonOutside) {
298
+ (_h = modalHeaderTitleElm === null || modalHeaderTitleElm === void 0 ? void 0 : modalHeaderTitleElm.classList) === null || _h === void 0 ? void 0 : _h.add('outside');
299
+ (_j = modalCloseButtonElm === null || modalCloseButtonElm === void 0 ? void 0 : modalCloseButtonElm.classList) === null || _j === void 0 ? void 0 : _j.add('outside');
300
+ }
301
+ const modalHeaderElm = createDomElement('div', { className: 'slick-editor-modal-header' });
302
+ modalHeaderElm.setAttribute('aria-label', 'Close');
303
+ modalHeaderElm.appendChild(modalHeaderTitleElm);
304
+ modalHeaderElm.appendChild(modalCloseButtonElm);
305
+ const modalBodyElm = createDomElement('div', { className: 'slick-editor-modal-body' });
306
+ this._modalBodyTopValidationElm = createDomElement('div', { className: 'validation-summary', style: { display: 'none' } });
307
+ modalBodyElm.appendChild(this._modalBodyTopValidationElm);
308
+ const modalFooterElm = createDomElement('div', { className: 'slick-editor-modal-footer' });
309
+ const modalCancelButtonElm = createDomElement('button', {
310
+ type: 'button',
311
+ className: 'btn btn-cancel btn-default btn-sm',
312
+ textContent: this.getLabelText('cancelButton', 'TEXT_CANCEL', 'Cancel'),
313
+ dataset: { action: 'cancel' },
314
+ });
315
+ modalCancelButtonElm.setAttribute('aria-label', this.getLabelText('cancelButton', 'TEXT_CANCEL', 'Cancel'));
316
+ let leftFooterText = '';
317
+ let saveButtonText = '';
318
+ switch (modalType) {
319
+ case 'clone':
320
+ saveButtonText = this.getLabelText('cloneButton', 'TEXT_CLONE', 'Clone');
321
+ break;
322
+ case 'mass-update':
323
+ const footerUnparsedText = this.getLabelText('massUpdateStatus', 'TEXT_ALL_X_RECORDS_SELECTED', 'All {{x}} records selected');
324
+ leftFooterText = this.parseText(footerUnparsedText, { x: fullDatasetLength });
325
+ saveButtonText = this.getLabelText('massUpdateButton', 'TEXT_APPLY_MASS_UPDATE', 'Mass Update');
326
+ break;
327
+ case 'mass-selection':
328
+ const selectionUnparsedText = this.getLabelText('massSelectionStatus', 'TEXT_X_OF_Y_MASS_SELECTED', '{{x}} of {{y}} selected');
329
+ leftFooterText = this.parseText(selectionUnparsedText, { x: dataContextIds.length, y: fullDatasetLength });
330
+ saveButtonText = this.getLabelText('massSelectionButton', 'TEXT_APPLY_TO_SELECTION', 'Update Selection');
331
+ break;
332
+ default:
333
+ saveButtonText = this.getLabelText('saveButton', 'TEXT_SAVE', 'Save');
334
+ }
335
+ const selectionCounterElm = createDomElement('div', { className: 'footer-status-text', textContent: leftFooterText });
336
+ this._modalSaveButtonElm = createDomElement('button', {
337
+ type: 'button', className: 'btn btn-save btn-primary btn-sm',
338
+ textContent: saveButtonText,
339
+ dataset: {
340
+ action: (modalType === 'create' || modalType === 'edit') ? 'save' : modalType,
341
+ ariaLabel: saveButtonText
342
+ }
343
+ });
344
+ this._modalSaveButtonElm.setAttribute('aria-label', saveButtonText);
345
+ const footerContainerElm = createDomElement('div', { className: 'footer-buttons' });
346
+ if (modalType === 'mass-update' || modalType === 'mass-selection') {
347
+ modalFooterElm.appendChild(selectionCounterElm);
348
+ }
349
+ footerContainerElm.appendChild(modalCancelButtonElm);
350
+ footerContainerElm.appendChild(this._modalSaveButtonElm);
351
+ modalFooterElm.appendChild(footerContainerElm);
352
+ modalContentElm.appendChild(modalHeaderElm);
353
+ modalContentElm.appendChild(modalBodyElm);
354
+ modalContentElm.appendChild(modalFooterElm);
355
+ this._modalElm.appendChild(modalContentElm);
356
+ for (const columnDef of modalColumns) {
357
+ if (columnDef.editor) {
358
+ const itemContainer = createDomElement('div', { className: `item-details-container editor-${columnDef.id}` });
359
+ if (layoutColCount === 1) {
360
+ itemContainer.classList.add('slick-col-medium-12');
361
+ }
362
+ else {
363
+ itemContainer.classList.add('slick-col-medium-6', `slick-col-xlarge-${12 / layoutColCount}`);
364
+ }
365
+ const templateItemLabelElm = createDomElement('div', {
366
+ className: `item-details-label editor-${columnDef.id}`,
367
+ innerHTML: sanitizeTextByAvailableSanitizer(this.gridOptions, this.getColumnLabel(columnDef) || 'n/a')
368
+ });
369
+ const templateItemEditorElm = createDomElement('div', {
370
+ className: 'item-details-editor-container slick-cell',
371
+ dataset: { editorid: `${columnDef.id}` },
372
+ });
373
+ const templateItemValidationElm = createDomElement('div', { className: `item-details-validation editor-${columnDef.id}` });
374
+ // optionally add a reset button beside each editor
375
+ if ((_k = this._options) === null || _k === void 0 ? void 0 : _k.showResetButtonOnEachEditor) {
376
+ const editorResetButtonElm = this.createEditorResetButtonElement(`${columnDef.id}`);
377
+ this._bindEventService.bind(editorResetButtonElm, 'click', this.handleResetInputValue.bind(this));
378
+ templateItemLabelElm.appendChild(editorResetButtonElm);
379
+ }
380
+ itemContainer.appendChild(templateItemLabelElm);
381
+ itemContainer.appendChild(templateItemEditorElm);
382
+ itemContainer.appendChild(templateItemValidationElm);
383
+ modalBodyElm.appendChild(itemContainer);
384
+ }
385
+ }
386
+ // optionally add a form reset button
387
+ if ((_l = this._options) === null || _l === void 0 ? void 0 : _l.showFormResetButton) {
388
+ const resetButtonContainerElm = this.createFormResetButtonElement();
389
+ this._bindEventService.bind(resetButtonContainerElm, 'click', this.handleResetFormClicked.bind(this));
390
+ modalBodyElm.appendChild(resetButtonContainerElm);
391
+ }
392
+ document.body.appendChild(this._modalElm);
393
+ document.body.classList.add('slick-modal-open'); // add backdrop to body
394
+ this._bindEventService.bind(document.body, 'click', this.handleBodyClicked.bind(this));
395
+ this._editors = {};
396
+ this._editorContainers = modalColumns.map(col => modalBodyElm.querySelector(`[data-editorid=${col.id}]`)) || [];
397
+ this._compositeOptions = { destroy: this.disposeComponent.bind(this), modalType, validationMsgPrefix: '* ', formValues: {}, editors: this._editors };
398
+ const compositeEditor = new CompositeEditor(modalColumns, this._editorContainers, this._compositeOptions);
399
+ this.grid.editActiveCell(compositeEditor);
400
+ // --
401
+ // Add a few Event Handlers
402
+ // keyboard, blur & button event handlers
403
+ this._bindEventService.bind(modalCloseButtonElm, 'click', this.cancelEditing.bind(this));
404
+ this._bindEventService.bind(modalCancelButtonElm, 'click', this.cancelEditing.bind(this));
405
+ this._bindEventService.bind(this._modalSaveButtonElm, 'click', this.handleSaveClicked.bind(this));
406
+ this._bindEventService.bind(this._modalElm, 'keydown', this.handleKeyDown.bind(this));
407
+ this._bindEventService.bind(this._modalElm, 'focusout', this.validateCurrentEditor.bind(this));
408
+ this._bindEventService.bind(this._modalElm, 'blur', this.validateCurrentEditor.bind(this));
409
+ // when any of the input of the composite editor form changes, we'll add/remove a "modified" CSS className for styling purposes
410
+ this._eventHandler.subscribe(this.grid.onCompositeEditorChange, this.handleOnCompositeEditorChange.bind(this));
411
+ // when adding a new row to the grid, we need to invalidate that row and re-render the grid
412
+ this._eventHandler.subscribe(this.grid.onAddNewRow, (_e, args) => {
413
+ this.insertNewItemInDataView(args.item);
414
+ this._originalDataContext = args.item; // this becomes the new data context
415
+ this.dispose();
416
+ });
417
+ }
418
+ return this;
419
+ }
420
+ catch (error) {
421
+ this.dispose();
422
+ const errorMsg = (typeof error === 'string') ? error : ((_p = (_m = error === null || error === void 0 ? void 0 : error.message) !== null && _m !== void 0 ? _m : (_o = error === null || error === void 0 ? void 0 : error.body) === null || _o === void 0 ? void 0 : _o.message) !== null && _p !== void 0 ? _p : '');
423
+ const errorCode = (typeof error === 'string') ? error : (_s = (_q = error === null || error === void 0 ? void 0 : error.status) !== null && _q !== void 0 ? _q : (_r = error === null || error === void 0 ? void 0 : error.body) === null || _r === void 0 ? void 0 : _r.status) !== null && _s !== void 0 ? _s : errorMsg;
424
+ onError({ type: 'error', code: errorCode, message: errorMsg });
425
+ return null;
426
+ }
427
+ }
428
+ /** Cancel the Editing which will also close the modal window */
429
+ async cancelEditing() {
430
+ var _a, _b;
431
+ let confirmed = true;
432
+ if (this.formValues && Object.keys(this.formValues).length > 0 && typeof this._options.onClose === 'function') {
433
+ confirmed = await this._options.onClose();
434
+ }
435
+ if (confirmed) {
436
+ this.grid.getEditController().cancelCurrentEdit();
437
+ // cancel current edit is not enough when editing/cloning,
438
+ // we also need to reset with the original item data context to undo/reset the entire row
439
+ if (((_a = this._options) === null || _a === void 0 ? void 0 : _a.modalType) === 'edit' || ((_b = this._options) === null || _b === void 0 ? void 0 : _b.modalType) === 'clone') {
440
+ this.resetCurrentRowDataContext();
441
+ }
442
+ this.grid.setActiveRow(this._lastActiveRowNumber);
443
+ this.dispose();
444
+ }
445
+ }
446
+ /** Show a Validation Summary text (as a <div>) when a validation fails or simply hide it when there's no error */
447
+ showValidationSummaryText(isShowing, errorMsg = '') {
448
+ var _a, _b;
449
+ if (isShowing && errorMsg !== '') {
450
+ this._modalBodyTopValidationElm.textContent = errorMsg;
451
+ this._modalBodyTopValidationElm.style.display = 'block';
452
+ (_b = (_a = this._modalBodyTopValidationElm).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a);
453
+ this._modalSaveButtonElm.disabled = false;
454
+ this._modalSaveButtonElm.classList.remove('saving');
455
+ }
456
+ else {
457
+ this._modalBodyTopValidationElm.style.display = 'none';
458
+ this._modalBodyTopValidationElm.textContent = errorMsg;
459
+ }
460
+ }
461
+ // --
462
+ // protected methods
463
+ // ----------------
464
+ /** Apply Mass Update Changes (form values) to the entire dataset */
465
+ applySaveMassUpdateChanges(formValues, _selection, applyToDataview = true) {
466
+ // not applying to dataView means that we're doing a preview of dataset and we should use a deep copy of it instead of applying changes directly to it
467
+ const data = applyToDataview ? this.dataView.getItems() : deepCopy(this.dataView.getItems());
468
+ // from the "lastCompositeEditor" object that we kept as reference, it contains all the changes inside the "formValues" property
469
+ // we can loop through these changes and apply them on the selected row indexes
470
+ for (const itemProp in formValues) {
471
+ if (itemProp in formValues) {
472
+ data.forEach((dataContext) => {
473
+ var _a;
474
+ if (itemProp in formValues && (((_a = this._options) === null || _a === void 0 ? void 0 : _a.validateMassUpdateChange) === undefined || this._options.validateMassUpdateChange(itemProp, dataContext, formValues) !== false)) {
475
+ dataContext[itemProp] = formValues[itemProp];
476
+ }
477
+ });
478
+ }
479
+ }
480
+ // change the entire dataset with our updated dataset
481
+ if (applyToDataview) {
482
+ this.dataView.setItems(data, this.gridOptions.datasetIdPropertyName);
483
+ this.grid.invalidate();
484
+ }
485
+ return data;
486
+ }
487
+ /** Apply Mass Changes to the Selected rows in the grid (form values) */
488
+ applySaveMassSelectionChanges(formValues, selection, applyToDataview = true) {
489
+ var _a, _b;
490
+ const selectedItemIds = (_a = selection === null || selection === void 0 ? void 0 : selection.dataContextIds) !== null && _a !== void 0 ? _a : [];
491
+ const selectedTmpItems = selectedItemIds.map(itemId => this.dataView.getItemById(itemId));
492
+ // not applying to dataView means that we're doing a preview of dataset and we should use a deep copy of it instead of applying changes directly to it
493
+ const selectedItems = applyToDataview ? selectedTmpItems : deepCopy(selectedTmpItems);
494
+ // from the "lastCompositeEditor" object that we kept as reference, it contains all the changes inside the "formValues" property
495
+ // we can loop through these changes and apply them on the selected row indexes
496
+ for (const itemProp in formValues) {
497
+ if (itemProp in formValues) {
498
+ selectedItems.forEach((dataContext) => {
499
+ var _a;
500
+ if (itemProp in formValues && (((_a = this._options) === null || _a === void 0 ? void 0 : _a.validateMassUpdateChange) === undefined || this._options.validateMassUpdateChange(itemProp, dataContext, formValues) !== false)) {
501
+ dataContext[itemProp] = formValues[itemProp];
502
+ }
503
+ });
504
+ }
505
+ }
506
+ // update all items in the grid with the grid service
507
+ if (applyToDataview) {
508
+ (_b = this.gridService) === null || _b === void 0 ? void 0 : _b.updateItems(selectedItems);
509
+ }
510
+ return selectedItems;
511
+ }
512
+ /**
513
+ * Auto-Calculate how many columns to display in the view layout (1, 2, or 3).
514
+ * We'll display a 1 column layout for 8 or less Editors, 2 columns layout for less than 15 Editors or 3 columns when more than 15 Editors
515
+ * @param {number} editorCount - how many Editors do we have in total
516
+ * @returns {number} count - calculated column count (1, 2 or 3)
517
+ */
518
+ autoCalculateLayoutColumnCount(editorCount) {
519
+ if (editorCount >= 15) {
520
+ return 3;
521
+ }
522
+ else if (editorCount >= 8) {
523
+ return 2;
524
+ }
525
+ return 1;
526
+ }
527
+ /**
528
+ * Create a reset button for each editor and attach a button click handler
529
+ * @param {String} columnId - column id
530
+ * @returns {Object} - html button
531
+ */
532
+ createEditorResetButtonElement(columnId) {
533
+ var _a, _b, _c, _d, _f;
534
+ const resetButtonElm = createDomElement('button', {
535
+ type: 'button', name: columnId,
536
+ title: (_c = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.labels) === null || _b === void 0 ? void 0 : _b.resetFormButton) !== null && _c !== void 0 ? _c : 'Reset Form Input',
537
+ className: 'btn btn-xs btn-editor-reset'
538
+ });
539
+ resetButtonElm.setAttribute('aria-label', 'Reset');
540
+ if ((_d = this._options) === null || _d === void 0 ? void 0 : _d.resetEditorButtonCssClass) {
541
+ const resetBtnClasses = (_f = this._options) === null || _f === void 0 ? void 0 : _f.resetEditorButtonCssClass.split(' ');
542
+ for (const cssClass of resetBtnClasses) {
543
+ resetButtonElm.classList.add(cssClass);
544
+ }
545
+ }
546
+ return resetButtonElm;
547
+ }
548
+ /**
549
+ * Create a form reset button and attach a button click handler
550
+ * @param {String} columnId - column id
551
+ * @returns {Object} - html button
552
+ */
553
+ createFormResetButtonElement() {
554
+ var _a, _b;
555
+ const resetButtonContainerElm = createDomElement('div', { className: 'reset-container' });
556
+ const resetButtonElm = createDomElement('button', { type: 'button', className: 'btn btn-sm reset-form' });
557
+ const resetIconSpanElm = createDomElement('span', { className: (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.resetFormButtonIconCssClass) !== null && _b !== void 0 ? _b : '' });
558
+ resetButtonElm.appendChild(resetIconSpanElm);
559
+ resetButtonElm.appendChild(document.createTextNode(' Reset Form'));
560
+ resetButtonContainerElm.appendChild(resetButtonElm);
561
+ return resetButtonContainerElm;
562
+ }
563
+ /**
564
+ * Execute the onError callback when defined
565
+ * or use the default onError callback which is to simply display the error in the console
566
+ */
567
+ executeOnError(error) {
568
+ var _a, _b;
569
+ const onError = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.onError) !== null && _b !== void 0 ? _b : DEFAULT_ON_ERROR;
570
+ onError(error);
571
+ }
572
+ /**
573
+ * A simple and generic method to execute the "OnSave" callback if it's defined by the user OR else simply execute built-in apply changes callback.
574
+ * This method deals with multiple callbacks as shown below
575
+ * @param {Function} applyChangesCallback - first callback to apply the changes into the grid (this could be a user custom callback)
576
+ * @param {Function} executePostCallback - second callback to execute right after the "onSave"
577
+ * @param {Function} beforeClosingCallback - third and last callback to execute after Saving but just before closing the modal window
578
+ * @param {Object} itemDataContext - item data context when modal type is (create/clone/edit)
579
+ */
580
+ async executeOnSave(applyChangesCallback, executePostCallback, beforeClosingCallback, itemDataContext) {
581
+ var _a, _b, _c, _d, _f, _g;
582
+ try {
583
+ this.showValidationSummaryText(false, '');
584
+ const validationResults = this.validateCompositeEditors();
585
+ if (validationResults.valid) {
586
+ this._modalSaveButtonElm.classList.add('saving');
587
+ this._modalSaveButtonElm.disabled = true;
588
+ if (typeof ((_a = this._options) === null || _a === void 0 ? void 0 : _a.onSave) === 'function') {
589
+ const isMassChange = (this._options.modalType === 'mass-update' || this._options.modalType === 'mass-selection');
590
+ // apply the changes in the grid early when that option is enabled (that is before the await of `onSave`)
591
+ let updatedDataset;
592
+ if (isMassChange && ((_b = this._options) === null || _b === void 0 ? void 0 : _b.shouldPreviewMassChangeDataset)) {
593
+ updatedDataset = applyChangesCallback(this.formValues, this.getCurrentRowSelections(), false);
594
+ }
595
+ // call the custon onSave callback when defined and note that the item data context will only be filled for create/clone/edit
596
+ const dataContextOrUpdatedDatasetPreview = isMassChange ? updatedDataset : itemDataContext;
597
+ const successful = await ((_c = this._options) === null || _c === void 0 ? void 0 : _c.onSave(this.formValues, this.getCurrentRowSelections(), dataContextOrUpdatedDatasetPreview));
598
+ if (successful) {
599
+ // apply the changes in the grid (if it's not yet applied)
600
+ applyChangesCallback(this.formValues, this.getCurrentRowSelections());
601
+ // once we're done doing the mass update, we can cancel the current editor since we don't want to add any new row
602
+ // that will also destroy/close the modal window
603
+ executePostCallback();
604
+ }
605
+ }
606
+ else {
607
+ applyChangesCallback(this.formValues, this.getCurrentRowSelections());
608
+ executePostCallback();
609
+ }
610
+ // run any function before closing the modal
611
+ if (typeof beforeClosingCallback === 'function') {
612
+ beforeClosingCallback();
613
+ }
614
+ // close the modal only when successful
615
+ this.dispose();
616
+ }
617
+ }
618
+ catch (error) {
619
+ const errorMsg = (typeof error === 'string') ? error : ((_g = (_d = error === null || error === void 0 ? void 0 : error.message) !== null && _d !== void 0 ? _d : (_f = error === null || error === void 0 ? void 0 : error.body) === null || _f === void 0 ? void 0 : _f.message) !== null && _g !== void 0 ? _g : '');
620
+ this.showValidationSummaryText(true, errorMsg);
621
+ }
622
+ }
623
+ // For the Composite Editor to work, the current active cell must have an Editor (because it calls editActiveCell() and that only works with a cell with an Editor)
624
+ // so if current active cell doesn't have an Editor, we'll find the first column with an Editor and focus on it (from left to right starting at index 0)
625
+ focusOnFirstColumnCellWithEditor(columns, dataContext, columnIndex, rowIndex, isWithMassChange) {
626
+ // make sure we're not trying to activate a cell outside of the grid, that can happen when using MassUpdate without `enableAddRow` flag enabled
627
+ const activeCellIndex = (isWithMassChange && !this.gridOptions.enableAddRow && (rowIndex >= this.dataViewLength)) ? this.dataViewLength - 1 : rowIndex;
628
+ let columnIndexWithEditor = columnIndex;
629
+ const cellEditor = columns[columnIndex].editor;
630
+ let activeEditorCellNode = this.grid.getCellNode(activeCellIndex, columnIndex);
631
+ if (!cellEditor || !activeEditorCellNode || !this.getActiveCellEditor(activeCellIndex, columnIndex)) {
632
+ columnIndexWithEditor = this.findNextAvailableEditorColumnIndex(columns, dataContext, rowIndex, isWithMassChange);
633
+ if (columnIndexWithEditor === -1) {
634
+ this.executeOnError({ type: 'error', code: 'NO_EDITOR_FOUND', message: 'We could not find any Editor in your Column Definition' });
635
+ return false;
636
+ }
637
+ else {
638
+ this.grid.setActiveCell(activeCellIndex, columnIndexWithEditor, false);
639
+ if (isWithMassChange) {
640
+ // when it's a mass change, we'll activate the last row without scrolling to it
641
+ // that is possible via the 3rd argument "suppressScrollIntoView" set to "true"
642
+ this.grid.setActiveRow(this.dataViewLength, columnIndexWithEditor, true);
643
+ }
644
+ }
645
+ }
646
+ // check again if the cell node is now being created, if it is then we're good
647
+ activeEditorCellNode = this.grid.getCellNode(activeCellIndex, columnIndexWithEditor);
648
+ return !!activeEditorCellNode;
649
+ }
650
+ findNextAvailableEditorColumnIndex(columns, dataContext, rowIndex, isWithMassUpdate) {
651
+ var _a;
652
+ let columnIndexWithEditor = -1;
653
+ for (let colIndex = 0; colIndex < columns.length; colIndex++) {
654
+ const col = columns[colIndex];
655
+ if (col.editor && (!isWithMassUpdate || (isWithMassUpdate && ((_a = col.internalColumnEditor) === null || _a === void 0 ? void 0 : _a.massUpdate)))) {
656
+ // we can check that the cell is really editable by checking the onBeforeEditCell event not returning false (returning undefined, null also mean it is editable)
657
+ const isCellEditable = this.grid.onBeforeEditCell.notify({ row: rowIndex, cell: colIndex, item: dataContext, column: col, grid: this.grid, target: 'composite', compositeEditorOptions: this._compositeOptions });
658
+ this.grid.setActiveCell(rowIndex, colIndex, false);
659
+ if (isCellEditable !== false) {
660
+ columnIndexWithEditor = colIndex;
661
+ break;
662
+ }
663
+ }
664
+ }
665
+ return columnIndexWithEditor;
666
+ }
667
+ /**
668
+ * Get a column definition by providing a column id OR a column definition.
669
+ * If the input is a string, we'll assume it's a columnId and we'll simply search for the column in the column definitions list
670
+ */
671
+ getColumnByObjectOrId(columnIdOrDef) {
672
+ let column;
673
+ if (typeof columnIdOrDef === 'object') {
674
+ column = columnIdOrDef;
675
+ }
676
+ else if (typeof columnIdOrDef === 'string') {
677
+ column = this._columnDefinitions.find(col => col.id === columnIdOrDef);
678
+ }
679
+ return column;
680
+ }
681
+ getActiveCellEditor(row, cell) {
682
+ this.grid.setActiveCell(row, cell, false);
683
+ return this.grid.getCellEditor();
684
+ }
685
+ /**
686
+ * Get the column label, the label might have an optional "columnGroup" (or "columnGroupKey" which need to be translated)
687
+ * @param {object} columnDef - column definition
688
+ * @returns {string} label - column label
689
+ */
690
+ getColumnLabel(columnDef) {
691
+ var _a;
692
+ const columnGroupSeparator = this.gridOptions.columnGroupSeparator || ' - ';
693
+ let columnName = columnDef.nameCompositeEditor || columnDef.name || '';
694
+ let columnGroup = columnDef.columnGroup || '';
695
+ if (this.gridOptions.enableTranslate && this.translaterService) {
696
+ const translationKey = columnDef.nameCompositeEditorKey || columnDef.nameKey;
697
+ if (translationKey) {
698
+ columnName = this.translaterService.translate(translationKey);
699
+ }
700
+ if (columnDef.columnGroupKey && ((_a = this.translaterService) === null || _a === void 0 ? void 0 : _a.translate)) {
701
+ columnGroup = this.translaterService.translate(columnDef.columnGroupKey);
702
+ }
703
+ }
704
+ const columnLabel = columnGroup ? `${columnGroup}${columnGroupSeparator}${columnName}` : columnName;
705
+ return columnLabel || '';
706
+ }
707
+ /** Get the correct label text depending, if we use a Translater Service then translate the text when possible else use default text */
708
+ getLabelText(labelProperty, localeText, defaultText) {
709
+ var _a, _b, _c, _d, _f, _g, _h;
710
+ const textLabels = { ...(_a = this.gridOptions.compositeEditorOptions) === null || _a === void 0 ? void 0 : _a.labels, ...(_b = this._options) === null || _b === void 0 ? void 0 : _b.labels };
711
+ if (((_c = this.gridOptions) === null || _c === void 0 ? void 0 : _c.enableTranslate) && ((_d = this.translaterService) === null || _d === void 0 ? void 0 : _d.translate) && textLabels.hasOwnProperty(`${labelProperty}Key`)) {
712
+ const translationKey = textLabels[`${labelProperty}Key`];
713
+ return this.translaterService.translate(translationKey || '');
714
+ }
715
+ return (_h = (_f = textLabels === null || textLabels === void 0 ? void 0 : textLabels[labelProperty]) !== null && _f !== void 0 ? _f : (_g = this._locales) === null || _g === void 0 ? void 0 : _g[localeText]) !== null && _h !== void 0 ? _h : defaultText;
716
+ }
717
+ /** Retrieve the current selection of row indexes & data context Ids */
718
+ getCurrentRowSelections() {
719
+ const dataContextIds = this.dataView.getAllSelectedIds();
720
+ const gridRowIndexes = this.dataView.mapIdsToRows(dataContextIds);
721
+ return { gridRowIndexes, dataContextIds };
722
+ }
723
+ handleBodyClicked(event) {
724
+ var _a, _b, _c;
725
+ if ((_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.classList) === null || _b === void 0 ? void 0 : _b.contains('slick-editor-modal')) {
726
+ if (((_c = this._options) === null || _c === void 0 ? void 0 : _c.backdrop) !== 'static') {
727
+ this.dispose();
728
+ }
729
+ }
730
+ }
731
+ handleKeyDown(event) {
732
+ if (event.code === 'Escape') {
733
+ this.cancelEditing();
734
+ event.stopPropagation();
735
+ event.preventDefault();
736
+ }
737
+ else if (event.code === 'Tab') {
738
+ this.validateCurrentEditor();
739
+ }
740
+ }
741
+ handleResetInputValue(event) {
742
+ var _a, _b;
743
+ const columnId = event.target.name;
744
+ const editor = (_a = this._editors) === null || _a === void 0 ? void 0 : _a[columnId];
745
+ if (editor === null || editor === void 0 ? void 0 : editor.reset) {
746
+ editor.reset();
747
+ }
748
+ (_b = this._formValues) === null || _b === void 0 ? true : delete _b[columnId];
749
+ }
750
+ /** Callback which processes a Mass Update or Mass Selection Changes */
751
+ async handleMassSaving(modalType, executePostCallback) {
752
+ if (!this.formValues || Object.keys(this.formValues).length === 0) {
753
+ this.executeOnError({ type: 'warning', code: 'NO_CHANGES_DETECTED', message: 'Sorry we could not detect any changes.' });
754
+ }
755
+ else {
756
+ const applyCallbackFnName = (modalType === 'mass-update') ? 'applySaveMassUpdateChanges' : 'applySaveMassSelectionChanges';
757
+ this.executeOnSave(this[applyCallbackFnName].bind(this), executePostCallback.bind(this));
758
+ }
759
+ }
760
+ /** Anytime an input of the Composite Editor form changes, we'll add/remove a "modified" CSS className for styling purposes */
761
+ handleOnCompositeEditorChange(_e, args) {
762
+ var _a, _b, _c, _d, _f, _g, _h, _j;
763
+ const columnId = (_b = (_a = args.column) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '';
764
+ this._formValues = { ...this._formValues, ...args.formValues };
765
+ const editor = (_c = this._editors) === null || _c === void 0 ? void 0 : _c[columnId];
766
+ const isEditorValueTouched = (_h = (_f = (_d = editor === null || editor === void 0 ? void 0 : editor.isValueTouched) === null || _d === void 0 ? void 0 : _d.call(editor)) !== null && _f !== void 0 ? _f : (_g = editor === null || editor === void 0 ? void 0 : editor.isValueChanged) === null || _g === void 0 ? void 0 : _g.call(editor)) !== null && _h !== void 0 ? _h : false;
767
+ this._itemDataContext = (_j = editor === null || editor === void 0 ? void 0 : editor.dataContext) !== null && _j !== void 0 ? _j : {}; // keep reference of the item data context
768
+ // add extra css styling to the composite editor input(s) that got modified
769
+ const editorElm = this._modalElm.querySelector(`[data-editorid=${columnId}]`);
770
+ if (editorElm === null || editorElm === void 0 ? void 0 : editorElm.classList) {
771
+ if (isEditorValueTouched) {
772
+ editorElm.classList.add('modified');
773
+ }
774
+ else {
775
+ editorElm.classList.remove('modified');
776
+ }
777
+ }
778
+ // after any input changes we'll re-validate all fields
779
+ this.validateCompositeEditors();
780
+ }
781
+ /** Check wether the grid has the Row Selection enabled */
782
+ hasRowSelectionEnabled() {
783
+ const selectionModel = this.grid.getSelectionModel();
784
+ const isRowSelectionEnabled = this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector;
785
+ return (isRowSelectionEnabled && selectionModel);
786
+ }
787
+ /** Reset Form button handler */
788
+ handleResetFormClicked() {
789
+ for (const columnId of Object.keys(this._editors)) {
790
+ const editor = this._editors[columnId];
791
+ if (editor === null || editor === void 0 ? void 0 : editor.reset) {
792
+ editor.reset();
793
+ }
794
+ }
795
+ this._formValues = emptyObject(this._formValues);
796
+ }
797
+ /** switch case handler to determine which code to execute depending on the modal type */
798
+ handleSaveClicked() {
799
+ var _a, _b, _c;
800
+ const modalType = (_a = this._options) === null || _a === void 0 ? void 0 : _a.modalType;
801
+ switch (modalType) {
802
+ case 'mass-update':
803
+ this.handleMassSaving(modalType, () => {
804
+ this.grid.getEditController().cancelCurrentEdit();
805
+ this.grid.setActiveCell(0, 0, false);
806
+ if (this._options.shouldClearRowSelectionAfterMassAction) {
807
+ this.grid.setSelectedRows([]);
808
+ }
809
+ });
810
+ break;
811
+ case 'mass-selection':
812
+ this.handleMassSaving(modalType, () => {
813
+ this.grid.getEditController().cancelCurrentEdit();
814
+ this.grid.setActiveRow(this._lastActiveRowNumber);
815
+ if (this._options.shouldClearRowSelectionAfterMassAction) {
816
+ this.grid.setSelectedRows([]);
817
+ }
818
+ });
819
+ break;
820
+ case 'clone':
821
+ // the clone object will be a merge of the selected data context (original object) with the changed form values
822
+ const clonedItemDataContext = { ...this._originalDataContext, ...this.formValues };
823
+ // post save callback (before closing modal)
824
+ const postSaveCloneCallback = () => {
825
+ this.grid.getEditController().cancelCurrentEdit();
826
+ this.grid.setActiveCell(0, 0, false);
827
+ };
828
+ // call the onSave execution and provide the item data context so that it's available to the user
829
+ this.executeOnSave(this.insertNewItemInDataView.bind(this, clonedItemDataContext), postSaveCloneCallback, this.resetCurrentRowDataContext.bind(this), clonedItemDataContext);
830
+ break;
831
+ case 'create':
832
+ case 'edit':
833
+ default:
834
+ // commit the changes into the grid
835
+ // if it's a "create" then it will triggered the "onAddNewRow" event which will in term push it to the grid
836
+ // while an "edit" will simply applies the changes directly on the same row
837
+ this.grid.getEditController().commitCurrentEdit();
838
+ // if the user provided the "onSave" callback, let's execute it with the item data context
839
+ if (typeof ((_b = this._options) === null || _b === void 0 ? void 0 : _b.onSave) === 'function') {
840
+ const itemDataContext = this.grid.getDataItem(this._lastActiveRowNumber); // we can get item data context directly from DataView
841
+ (_c = this._options) === null || _c === void 0 ? void 0 : _c.onSave(this.formValues, this.getCurrentRowSelections(), itemDataContext);
842
+ }
843
+ break;
844
+ }
845
+ }
846
+ /** Insert an item into the DataView or throw an error when finding duplicate id in the dataset */
847
+ insertNewItemInDataView(item) {
848
+ var _a, _b, _c, _d;
849
+ const fullDatasetLength = (_b = (_a = this.dataView) === null || _a === void 0 ? void 0 : _a.getItemCount()) !== null && _b !== void 0 ? _b : 0;
850
+ const newId = (_c = this._options.insertNewId) !== null && _c !== void 0 ? _c : fullDatasetLength + 1;
851
+ item[this.gridOptions.datasetIdPropertyName || 'id'] = newId;
852
+ if (!this.dataView.getItemById(newId)) {
853
+ (_d = this.gridService) === null || _d === void 0 ? void 0 : _d.addItem(item, this._options.insertOptions);
854
+ }
855
+ else {
856
+ this.executeOnError({ type: 'error', code: 'ITEM_ALREADY_EXIST', message: `The item object which you are trying to add already exist with the same Id:: ${newId}` });
857
+ }
858
+ }
859
+ parseText(inputText, mappedArgs) {
860
+ return inputText.replace(/\{\{(.*?)\}\}/g, (match, group) => {
861
+ return mappedArgs[group] !== undefined ? mappedArgs[group] : match;
862
+ });
863
+ }
864
+ /** Put back the current row to its original item data context using the DataView without triggering a change */
865
+ resetCurrentRowDataContext() {
866
+ const idPropName = this.gridOptions.datasetIdPropertyName || 'id';
867
+ const dataView = this.grid.getData();
868
+ dataView.updateItem(this._originalDataContext[idPropName], this._originalDataContext);
869
+ }
870
+ /** Validate all the Composite Editors that are defined in the form */
871
+ validateCompositeEditors(targetElm) {
872
+ let validationResults = { valid: true, msg: '' };
873
+ const currentEditor = this.grid.getCellEditor();
874
+ if (currentEditor) {
875
+ validationResults = currentEditor.validate(targetElm);
876
+ }
877
+ return validationResults;
878
+ }
879
+ /** Validate the current cell editor */
880
+ validateCurrentEditor() {
881
+ const currentEditor = this.grid.getCellEditor();
882
+ if (currentEditor === null || currentEditor === void 0 ? void 0 : currentEditor.validate) {
883
+ currentEditor.validate();
884
+ }
885
+ }
886
+ }
887
887
  //# sourceMappingURL=slick-composite-editor.component.js.map