diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example08.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example08.ts index cead60906..37b30a848 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example08.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example08.ts @@ -34,6 +34,11 @@ export class Example08 { this.sgb2 = new Slicker.GridBundle(gridContainerElm2, this.columnDefinitions2, { ...ExampleGridOptions, ...this.gridOptions2 }, this.dataset2); } + dispose() { + this.sgb1?.dispose(); + this.sgb2?.dispose(); + } + definedGrid1() { this.columnDefinitions1 = [ { id: 'title', name: 'Title', field: 'title', sortable: true, columnGroup: 'Common Factor' }, diff --git a/packages/common/src/editors/autoCompleteEditor.ts b/packages/common/src/editors/autoCompleteEditor.ts index d70509a53..9b2549e28 100644 --- a/packages/common/src/editors/autoCompleteEditor.ts +++ b/packages/common/src/editors/autoCompleteEditor.ts @@ -37,7 +37,7 @@ export class AutoCompleteEditor implements Editor { private _currentValue: any; private _defaultTextValue: string; private _elementCollection: any[]; - private _lastInputKeyEvent: JQueryEventObject; + private _lastInputKeyEvent: JQuery.Event; /** The JQuery DOM element */ private _$editorElm: any; @@ -154,7 +154,11 @@ export class AutoCompleteEditor implements Editor { } destroy() { - this._$editorElm.off('keydown.nav').remove(); + if (this._$editorElm) { + this._$editorElm.autocomplete('destroy'); + this._$editorElm.off('keydown.nav').remove(); + this._$editorElm = null; + } } /** @@ -193,7 +197,7 @@ export class AutoCompleteEditor implements Editor { } focus() { - this._$editorElm.focus().select(); + this._$editorElm?.focus().select(); } show() { @@ -443,7 +447,7 @@ export class AutoCompleteEditor implements Editor { this._$editorElm = $(``) .appendTo(this.args.container) - .on('keydown.nav', (event: JQueryEventObject) => { + .on('keydown.nav', (event: JQuery.Event) => { this._lastInputKeyEvent = event; if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { event.stopImmediatePropagation(); diff --git a/packages/common/src/editors/checkboxEditor.ts b/packages/common/src/editors/checkboxEditor.ts index 6fefa4e68..bff9b7f3c 100644 --- a/packages/common/src/editors/checkboxEditor.ts +++ b/packages/common/src/editors/checkboxEditor.ts @@ -10,7 +10,7 @@ declare const Slick: SlickNamespace; * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. */ export class CheckboxEditor implements Editor { - private _input: HTMLInputElement; + private _input: HTMLInputElement | null; private _checkboxContainerElm: HTMLDivElement; private _originalValue?: boolean | string; @@ -97,6 +97,7 @@ export class CheckboxEditor implements Editor { if (this._input?.remove) { this._input.remove(); } + this._input = null; } disable(isDisabled = true) { @@ -122,7 +123,9 @@ export class CheckboxEditor implements Editor { } focus(): void { - this._input.focus(); + if (this._input) { + this._input.focus(); + } } show() { @@ -134,12 +137,14 @@ export class CheckboxEditor implements Editor { } getValue() { - return this._input.checked; + return this._input?.checked ?? false; } setValue(val: boolean | string, isApplyingValue = false) { const isChecked = val ? true : false; - this._input.checked = isChecked; + if (this._input) { + this._input.checked = isChecked; + } if (isApplyingValue) { this.applyValue(this.args.item, this.serializeValue()); @@ -177,7 +182,7 @@ export class CheckboxEditor implements Editor { loadValue(item: any) { const fieldName = this.columnDef && this.columnDef.field; - if (item && fieldName !== undefined) { + if (item && fieldName !== undefined && this._input) { // is the field a complex object, "address.streetNumber" const isComplexObject = fieldName?.indexOf('.') > 0; const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; @@ -201,12 +206,12 @@ export class CheckboxEditor implements Editor { } serializeValue(): boolean { - return this._input.checked; + return this._input?.checked ?? false; } validate(_targetElm?: null, inputValue?: any): EditorValidationResult { const isRequired = this.args?.compositeEditorOptions ? false : this.columnEditor.required; - const isChecked = (inputValue !== undefined) ? inputValue : this._input.checked; + const isChecked = (inputValue !== undefined) ? inputValue : this._input?.checked; const errorMsg = this.columnEditor.errorMessage; // when using Composite Editor, we also want to recheck if the field if disabled/enabled since it might change depending on other inputs on the composite form diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index e74b41bac..e71f4feac 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -166,12 +166,15 @@ export class DateEditor implements Editor { this._$input.remove(); if (this._$editorInputElm?.remove) { this._$editorInputElm.remove(); + this._$editorInputElm = null; } if (this._$inputWithData && typeof this._$inputWithData.remove === 'function') { this._$inputWithData.remove(); + this._$inputWithData = null; } if (this.flatInstance && typeof this.flatInstance.destroy === 'function') { this.flatInstance.destroy(); + this.flatInstance = null; } } diff --git a/packages/common/src/editors/floatEditor.ts b/packages/common/src/editors/floatEditor.ts index aee688040..ac6ee9f18 100644 --- a/packages/common/src/editors/floatEditor.ts +++ b/packages/common/src/editors/floatEditor.ts @@ -13,7 +13,7 @@ declare const Slick: SlickNamespace; * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. */ export class FloatEditor implements Editor { - private _input: HTMLInputElement; + private _input: HTMLInputElement | null; private _lastInputKeyEvent: KeyboardEvent; private _originalValue: number | string; @@ -80,7 +80,7 @@ export class FloatEditor implements Editor { cellContainer.appendChild(this._input); } - this._input.onfocus = () => this._input.select(); + this._input.onfocus = () => this._input?.select(); this._input.onkeydown = ((event: KeyboardEvent) => { this._lastInputKeyEvent = event; if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { @@ -108,7 +108,12 @@ export class FloatEditor implements Editor { this._input.removeEventListener('keyup', this.handleOnKeyUp); this._input.removeEventListener('change', this.handleOnChange); this._input.removeEventListener('wheel', this.handleOnChange.bind(this)); - setTimeout(() => this._input.remove()); + setTimeout(() => { + if (this._input) { + this._input.remove(); + this._input = null; + } + }); } } @@ -133,7 +138,9 @@ export class FloatEditor implements Editor { } focus(): void { - this._input.focus(); + if (this._input) { + this._input.focus(); + } } show() { @@ -168,19 +175,21 @@ export class FloatEditor implements Editor { } getValue(): string { - return this._input.value || ''; + return this._input?.value || ''; } setValue(value: number | string, isApplyingValue = false) { - this._input.value = `${value}`; + if (this._input) { + this._input.value = `${value}`; - if (isApplyingValue) { - this.applyValue(this.args.item, this.serializeValue()); + if (isApplyingValue) { + this.applyValue(this.args.item, this.serializeValue()); - // if it's set by a Composite Editor, then also trigger a change for it - const compositeEditorOptions = this.args.compositeEditorOptions; - if (compositeEditorOptions) { - this.handleChangeOnCompositeEditor(null, compositeEditorOptions); + // if it's set by a Composite Editor, then also trigger a change for it + const compositeEditorOptions = this.args.compositeEditorOptions; + if (compositeEditorOptions) { + this.handleChangeOnCompositeEditor(null, compositeEditorOptions); + } } } } @@ -203,7 +212,7 @@ export class FloatEditor implements Editor { } isValueChanged(): boolean { - const elmValue = this._input.value; + const elmValue = this._input?.value; const lastKeyEvent = this._lastInputKeyEvent && this._lastInputKeyEvent.keyCode; if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastKeyEvent === KeyCode.ENTER) { return true; @@ -216,7 +225,7 @@ export class FloatEditor implements Editor { if (fieldName !== undefined) { - if (item && fieldName !== undefined) { + if (item && fieldName !== undefined && this._input) { // is the field a complex object, "address.streetNumber" const isComplexObject = fieldName?.indexOf('.') > 0; const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; @@ -246,8 +255,8 @@ export class FloatEditor implements Editor { } serializeValue() { - const elmValue = this._input.value; - if (elmValue === '' || isNaN(+elmValue)) { + const elmValue = this._input?.value; + if (elmValue === undefined || elmValue === '' || isNaN(+elmValue)) { return elmValue; } @@ -271,7 +280,7 @@ export class FloatEditor implements Editor { return { valid: true, msg: '' }; } - const elmValue = (inputValue !== undefined) ? inputValue : this._input && this._input.value; + const elmValue = (inputValue !== undefined) ? inputValue : this._input?.value; return floatValidator(elmValue, { editorArgs: this.args, errorMessage: this.columnEditor.errorMessage, diff --git a/packages/common/src/editors/integerEditor.ts b/packages/common/src/editors/integerEditor.ts index 5b474fbd5..588e3b744 100644 --- a/packages/common/src/editors/integerEditor.ts +++ b/packages/common/src/editors/integerEditor.ts @@ -12,7 +12,7 @@ declare const Slick: SlickNamespace; */ export class IntegerEditor implements Editor { private _lastInputKeyEvent: KeyboardEvent; - private _input: HTMLInputElement; + private _input: HTMLInputElement | null; private _originalValue: number | string; /** is the Editor disabled? */ @@ -78,7 +78,7 @@ export class IntegerEditor implements Editor { cellContainer.appendChild(this._input); } - this._input.onfocus = () => this._input.select(); + this._input.onfocus = () => this._input?.select(); this._input.onkeydown = ((event: KeyboardEvent) => { this._lastInputKeyEvent = event; if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { @@ -106,7 +106,12 @@ export class IntegerEditor implements Editor { this._input.removeEventListener('keyup', this.handleOnKeyUp); this._input.removeEventListener('change', this.handleOnChange); this._input.removeEventListener('wheel', this.handleOnChange); - setTimeout(() => this._input.remove()); + setTimeout(() => { + if (this._input) { + this._input.remove(); + this._input = null; + } + }); } } @@ -131,7 +136,9 @@ export class IntegerEditor implements Editor { } focus(): void { - this._input.focus(); + if (this._input) { + this._input.focus(); + } } show() { @@ -143,19 +150,21 @@ export class IntegerEditor implements Editor { } getValue(): string { - return this._input.value || ''; + return this._input?.value || ''; } setValue(value: number | string, isApplyingValue = false) { - this._input.value = `${value}`; + if (this._input) { + this._input.value = `${value}`; - if (isApplyingValue) { - this.applyValue(this.args.item, this.serializeValue()); + if (isApplyingValue) { + this.applyValue(this.args.item, this.serializeValue()); - // if it's set by a Composite Editor, then also trigger a change for it - const compositeEditorOptions = this.args.compositeEditorOptions; - if (compositeEditorOptions) { - this.handleChangeOnCompositeEditor(null, compositeEditorOptions); + // if it's set by a Composite Editor, then also trigger a change for it + const compositeEditorOptions = this.args.compositeEditorOptions; + if (compositeEditorOptions) { + this.handleChangeOnCompositeEditor(null, compositeEditorOptions); + } } } } @@ -179,7 +188,7 @@ export class IntegerEditor implements Editor { } isValueChanged(): boolean { - const elmValue = this._input.value; + const elmValue = this._input?.value; const lastKeyEvent = this._lastInputKeyEvent && this._lastInputKeyEvent.keyCode; if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastKeyEvent === KeyCode.ENTER) { return true; @@ -190,7 +199,7 @@ export class IntegerEditor implements Editor { loadValue(item: any) { const fieldName = this.columnDef && this.columnDef.field; - if (item && fieldName !== undefined) { + if (item && fieldName !== undefined && this._input) { // is the field a complex object, "address.streetNumber" const isComplexObject = fieldName?.indexOf('.') > 0; @@ -215,8 +224,8 @@ export class IntegerEditor implements Editor { } serializeValue() { - const elmValue = this._input.value; - if (elmValue === '' || isNaN(+elmValue)) { + const elmValue = this._input?.value; + if (elmValue === undefined || elmValue === '' || isNaN(+elmValue)) { return elmValue; } const output = isNaN(+elmValue) ? elmValue : parseInt(elmValue, 10); diff --git a/packages/common/src/editors/longTextEditor.ts b/packages/common/src/editors/longTextEditor.ts index 7d7b93c6b..1dfa23c46 100644 --- a/packages/common/src/editors/longTextEditor.ts +++ b/packages/common/src/editors/longTextEditor.ts @@ -163,14 +163,23 @@ export class LongTextEditor implements Editor { } destroy() { - this._$wrapper.remove(); + if (this._$textarea) { + this._$textarea.off('keydown'); + this._$textarea.off('keyup'); + } + if (this._$wrapper) { + this._$wrapper.find('.btn-save').off('click'); + this._$wrapper.find('.btn-cancel').off('click'); + this._$wrapper.remove(); + } + this._$wrapper = null; } disable(isDisabled = true) { const prevIsDisabled = this.disabled; this.disabled = isDisabled; - if (this._$textarea) { + if (this._$textarea && this._$wrapper) { if (isDisabled) { this._$textarea.attr('disabled', 'disabled'); this._$wrapper.addClass('disabled'); diff --git a/packages/common/src/editors/selectEditor.ts b/packages/common/src/editors/selectEditor.ts index 177028a30..2b4da29ad 100644 --- a/packages/common/src/editors/selectEditor.ts +++ b/packages/common/src/editors/selectEditor.ts @@ -383,11 +383,11 @@ export class SelectEditor implements Editor { this._destroying = true; if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') { this.$editorElm.multipleSelect('destroy'); + this.$editorElm.remove(); const elementClassName = this.elementName.toString().replace('.', '\\.'); // make sure to escape any dot "." from CSS class to avoid console error $(`[name=${elementClassName}].ms-drop`).remove(); - } - if (this.$editorElm && typeof this.$editorElm.remove === 'function') { this.$editorElm.remove(); + this.$editorElm = null; } } diff --git a/packages/common/src/editors/sliderEditor.ts b/packages/common/src/editors/sliderEditor.ts index 5c2ab1db2..3c9c51f25 100644 --- a/packages/common/src/editors/sliderEditor.ts +++ b/packages/common/src/editors/sliderEditor.ts @@ -123,7 +123,10 @@ export class SliderEditor implements Editor { } destroy() { - this._$editorElm.off('input change mouseup touchend').remove(); + if (this._$editorElm) { + this._$editorElm.off('input change mouseup touchend').remove(); + this._$editorElm = null; + } } disable(isDisabled = true) { diff --git a/packages/common/src/editors/textEditor.ts b/packages/common/src/editors/textEditor.ts index cfad15572..54147fe9a 100644 --- a/packages/common/src/editors/textEditor.ts +++ b/packages/common/src/editors/textEditor.ts @@ -12,7 +12,7 @@ declare const Slick: SlickNamespace; */ export class TextEditor implements Editor { private _lastInputKeyEvent: KeyboardEvent; - private _input: HTMLInputElement; + private _input: HTMLInputElement | null; private _originalValue: string; /** is the Editor disabled? */ @@ -75,7 +75,7 @@ export class TextEditor implements Editor { cellContainer.appendChild(this._input); } - this._input.onfocus = () => this._input.select(); + this._input.onfocus = () => this._input?.select(); this._input.onkeydown = ((event: KeyboardEvent) => { this._lastInputKeyEvent = event; if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { @@ -98,7 +98,12 @@ export class TextEditor implements Editor { if (this._input) { this._input.removeEventListener('focusout', this.save); this._input.removeEventListener('keyup', this.handleOnKeyUp); - setTimeout(() => this._input.remove()); + setTimeout(() => { + if (this._input) { + this._input.remove(); + this._input = null; + } + }); } } @@ -123,7 +128,9 @@ export class TextEditor implements Editor { } focus(): void { - this._input.focus(); + if (this._input) { + this._input.focus(); + } } show() { @@ -135,19 +142,21 @@ export class TextEditor implements Editor { } getValue(): string { - return this._input.value || ''; + return this._input?.value || ''; } setValue(value: string, isApplyingValue = false) { - this._input.value = value; + if (this._input) { + this._input.value = value; - if (isApplyingValue) { - this.applyValue(this.args.item, this.serializeValue()); + if (isApplyingValue) { + this.applyValue(this.args.item, this.serializeValue()); - // if it's set by a Composite Editor, then also trigger a change for it - const compositeEditorOptions = this.args.compositeEditorOptions; - if (compositeEditorOptions) { - this.handleChangeOnCompositeEditor(null, compositeEditorOptions); + // if it's set by a Composite Editor, then also trigger a change for it + const compositeEditorOptions = this.args.compositeEditorOptions; + if (compositeEditorOptions) { + this.handleChangeOnCompositeEditor(null, compositeEditorOptions); + } } } } @@ -171,7 +180,7 @@ export class TextEditor implements Editor { } isValueChanged(): boolean { - const elmValue = this._input.value; + const elmValue = this._input?.value; const lastKeyEvent = this._lastInputKeyEvent && this._lastInputKeyEvent.keyCode; if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastKeyEvent === KeyCode.ENTER) { return true; @@ -182,7 +191,7 @@ export class TextEditor implements Editor { loadValue(item: any) { const fieldName = this.columnDef && this.columnDef.field; - if (item && fieldName !== undefined) { + if (item && fieldName !== undefined && this._input) { // is the field a complex object, "address.streetNumber" const isComplexObject = fieldName?.indexOf('.') > 0; const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : (item.hasOwnProperty(fieldName) && item[fieldName] || ''); @@ -207,7 +216,7 @@ export class TextEditor implements Editor { } serializeValue() { - return this._input.value; + return this._input?.value; } validate(_targetElm?: null, inputValue?: any): EditorValidationResult { diff --git a/packages/common/src/extensions/autoTooltipExtension.ts b/packages/common/src/extensions/autoTooltipExtension.ts index c3be699d3..4406b03c2 100644 --- a/packages/common/src/extensions/autoTooltipExtension.ts +++ b/packages/common/src/extensions/autoTooltipExtension.ts @@ -14,6 +14,7 @@ export class AutoTooltipExtension implements Extension { dispose() { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/cellMenuExtension.ts b/packages/common/src/extensions/cellMenuExtension.ts index a31f017d8..1cdd455f6 100644 --- a/packages/common/src/extensions/cellMenuExtension.ts +++ b/packages/common/src/extensions/cellMenuExtension.ts @@ -42,6 +42,7 @@ export class CellMenuExtension implements Extension { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/checkboxSelectorExtension.ts b/packages/common/src/extensions/checkboxSelectorExtension.ts index 8b1a3ab04..0e67e6650 100644 --- a/packages/common/src/extensions/checkboxSelectorExtension.ts +++ b/packages/common/src/extensions/checkboxSelectorExtension.ts @@ -14,6 +14,7 @@ export class CheckboxSelectorExtension implements Extension { dispose() { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/columnPickerExtension.ts b/packages/common/src/extensions/columnPickerExtension.ts index bc05734b1..bf7d0861c 100644 --- a/packages/common/src/extensions/columnPickerExtension.ts +++ b/packages/common/src/extensions/columnPickerExtension.ts @@ -23,6 +23,7 @@ export class ColumnPickerExtension implements Extension { this._eventHandler.unsubscribeAll(); if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/contextMenuExtension.ts b/packages/common/src/extensions/contextMenuExtension.ts index c0e60afbd..011b7ef54 100644 --- a/packages/common/src/extensions/contextMenuExtension.ts +++ b/packages/common/src/extensions/contextMenuExtension.ts @@ -44,6 +44,7 @@ export class ContextMenuExtension implements Extension { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } if (this.sharedService.gridOptions && this.sharedService.gridOptions.contextMenu && this.sharedService.gridOptions.contextMenu.commandItems) { this.sharedService.gridOptions.contextMenu = this._userOriginalContextMenu; diff --git a/packages/common/src/extensions/draggableGroupingExtension.ts b/packages/common/src/extensions/draggableGroupingExtension.ts index 015c371f5..648411b16 100644 --- a/packages/common/src/extensions/draggableGroupingExtension.ts +++ b/packages/common/src/extensions/draggableGroupingExtension.ts @@ -24,6 +24,7 @@ export class DraggableGroupingExtension implements Extension { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/gridMenuExtension.ts b/packages/common/src/extensions/gridMenuExtension.ts index 4fe33bec1..6f4fca8bf 100644 --- a/packages/common/src/extensions/gridMenuExtension.ts +++ b/packages/common/src/extensions/gridMenuExtension.ts @@ -52,6 +52,7 @@ export class GridMenuExtension implements Extension { this._eventHandler.unsubscribeAll(); if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } if (this.sharedService.gridOptions && this.sharedService.gridOptions.gridMenu && this.sharedService.gridOptions.gridMenu.customItems) { this.sharedService.gridOptions.gridMenu = this._userOriginalGridMenu; @@ -147,7 +148,7 @@ export class GridMenuExtension implements Extension { if (this.sharedService.slickGrid && typeof this.sharedService.slickGrid.autosizeColumns === 'function') { // make sure that the grid still exist (by looking if the Grid UID is found in the DOM tree) const gridUid = this.sharedService.slickGrid.getUID(); - if (this._areVisibleColumnDifferent && gridUid && $(`.${gridUid}`).length > 0) { + if (this._areVisibleColumnDifferent && gridUid && document.querySelector(`.${gridUid}`) !== null) { if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableAutoSizeColumns) { this.sharedService.slickGrid.autosizeColumns(); } diff --git a/packages/common/src/extensions/groupItemMetaProviderExtension.ts b/packages/common/src/extensions/groupItemMetaProviderExtension.ts index 1b7ef4a9b..018fdb6a7 100644 --- a/packages/common/src/extensions/groupItemMetaProviderExtension.ts +++ b/packages/common/src/extensions/groupItemMetaProviderExtension.ts @@ -9,6 +9,7 @@ export class GroupItemMetaProviderExtension implements Extension { dispose() { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/headerButtonExtension.ts b/packages/common/src/extensions/headerButtonExtension.ts index 3728bd3c8..5946d0150 100644 --- a/packages/common/src/extensions/headerButtonExtension.ts +++ b/packages/common/src/extensions/headerButtonExtension.ts @@ -24,6 +24,7 @@ export class HeaderButtonExtension implements Extension { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/headerMenuExtension.ts b/packages/common/src/extensions/headerMenuExtension.ts index b129cb039..f730f406e 100644 --- a/packages/common/src/extensions/headerMenuExtension.ts +++ b/packages/common/src/extensions/headerMenuExtension.ts @@ -51,6 +51,7 @@ export class HeaderMenuExtension implements Extension { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/rowMoveManagerExtension.ts b/packages/common/src/extensions/rowMoveManagerExtension.ts index 723826a25..47f856de8 100644 --- a/packages/common/src/extensions/rowMoveManagerExtension.ts +++ b/packages/common/src/extensions/rowMoveManagerExtension.ts @@ -33,6 +33,7 @@ export class RowMoveManagerExtension implements Extension { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/extensions/rowSelectionExtension.ts b/packages/common/src/extensions/rowSelectionExtension.ts index d2691bfa2..9362c5dd1 100644 --- a/packages/common/src/extensions/rowSelectionExtension.ts +++ b/packages/common/src/extensions/rowSelectionExtension.ts @@ -15,6 +15,7 @@ export class RowSelectionExtension implements Extension { dispose() { if (this._addon && this._addon.destroy) { this._addon.destroy(); + this._addon = null; } } diff --git a/packages/common/src/filters/__tests__/selectFilter.spec.ts b/packages/common/src/filters/__tests__/selectFilter.spec.ts index a3f0fc502..6af5f03b2 100644 --- a/packages/common/src/filters/__tests__/selectFilter.spec.ts +++ b/packages/common/src/filters/__tests__/selectFilter.spec.ts @@ -168,6 +168,23 @@ describe('SelectFilter', () => { expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: ['male'], shouldTriggerQuery: true }); }); + it('should trigger multiple select change event without choosing an option and expect the callback to be called without search terms and also expect the dropdown list to not have "filled" css class', () => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); + mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; + + filter.init(filterArguments); + const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice'); + const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`); + const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`); + filterBtnElm.click(); + filterOkElm.click(); + + const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled'); + expect(filterListElm.length).toBe(2); + expect(filterFilledElms.length).toBe(0); + expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: [], shouldTriggerQuery: true }); + }); + it('should trigger multiple select change event and expect this to work with a regular array of strings', () => { const spyCallback = jest.spyOn(filterArguments, 'callback'); diff --git a/packages/common/src/filters/autoCompleteFilter.ts b/packages/common/src/filters/autoCompleteFilter.ts index 8776d5a09..3a0539dc4 100644 --- a/packages/common/src/filters/autoCompleteFilter.ts +++ b/packages/common/src/filters/autoCompleteFilter.ts @@ -186,7 +186,9 @@ export class AutoCompleteFilter implements Filter { */ destroy() { if (this.$filterElm) { + this.$filterElm.autocomplete('destroy'); this.$filterElm.off('keyup').remove(); + this.$filterElm = null; } } diff --git a/packages/common/src/filters/compoundDateFilter.ts b/packages/common/src/filters/compoundDateFilter.ts index f249a329e..08351cdbe 100644 --- a/packages/common/src/filters/compoundDateFilter.ts +++ b/packages/common/src/filters/compoundDateFilter.ts @@ -123,9 +123,11 @@ export class CompoundDateFilter implements Filter { destroy() { if (this.$filterElm) { this.$filterElm.off('keyup').remove(); + this.$filterElm = null; } if (this.flatInstance && typeof this.flatInstance.destroy === 'function') { this.flatInstance.destroy(); + this.flatInstance = null; } } diff --git a/packages/common/src/filters/compoundInputFilter.ts b/packages/common/src/filters/compoundInputFilter.ts index 248ec131b..4926224c0 100644 --- a/packages/common/src/filters/compoundInputFilter.ts +++ b/packages/common/src/filters/compoundInputFilter.ts @@ -121,6 +121,8 @@ export class CompoundInputFilter implements Filter { if (this.$filterElm && this.$selectOperatorElm) { this.$filterElm.off('keyup input change').remove(); this.$selectOperatorElm.off('change'); + this.$filterElm = null; + this.$selectOperatorElm = null; } } diff --git a/packages/common/src/filters/compoundSliderFilter.ts b/packages/common/src/filters/compoundSliderFilter.ts index 78964a4e8..064da4093 100644 --- a/packages/common/src/filters/compoundSliderFilter.ts +++ b/packages/common/src/filters/compoundSliderFilter.ts @@ -150,6 +150,10 @@ export class CompoundSliderFilter implements Filter { destroy() { if (this.$filterInputElm) { this.$filterInputElm.off('input change').remove(); + this.$selectOperatorElm.off('change').remove(); + this.$filterInputElm = null; + this.$filterElm = null; + this.$selectOperatorElm = null; } } diff --git a/packages/common/src/filters/dateRangeFilter.ts b/packages/common/src/filters/dateRangeFilter.ts index c4b0599c2..104792daf 100644 --- a/packages/common/src/filters/dateRangeFilter.ts +++ b/packages/common/src/filters/dateRangeFilter.ts @@ -115,9 +115,11 @@ export class DateRangeFilter implements Filter { destroy() { if (this.$filterElm) { this.$filterElm.off('keyup').remove(); + this.$filterElm = null; } if (this.flatInstance && typeof this.flatInstance.destroy === 'function') { this.flatInstance.destroy(); + this.flatInstance = null; } } diff --git a/packages/common/src/filters/inputFilter.ts b/packages/common/src/filters/inputFilter.ts index b4419e6fe..e73929126 100644 --- a/packages/common/src/filters/inputFilter.ts +++ b/packages/common/src/filters/inputFilter.ts @@ -120,6 +120,7 @@ export class InputFilter implements Filter { destroy() { if (this.$filterElm) { this.$filterElm.off('keyup input change').remove(); + this.$filterElm = null; } } diff --git a/packages/common/src/filters/nativeSelectFilter.ts b/packages/common/src/filters/nativeSelectFilter.ts index 5310a5e2d..a4140f880 100644 --- a/packages/common/src/filters/nativeSelectFilter.ts +++ b/packages/common/src/filters/nativeSelectFilter.ts @@ -120,6 +120,7 @@ export class NativeSelectFilter implements Filter { destroy() { if (this.$filterElm) { this.$filterElm.off('change').remove(); + this.$filterElm = null; } } diff --git a/packages/common/src/filters/selectFilter.ts b/packages/common/src/filters/selectFilter.ts index 296758aee..72359417f 100644 --- a/packages/common/src/filters/selectFilter.ts +++ b/packages/common/src/filters/selectFilter.ts @@ -174,11 +174,12 @@ export class SelectFilter implements Filter { * destroy the filter */ destroy() { - if (this.$filterElm) { - // remove event watcher - this.$filterElm.off().remove(); + if (this.$filterElm && typeof this.$filterElm.multipleSelect === 'function') { + this.$filterElm.multipleSelect('destroy'); + this.$filterElm.remove(); const elementClassName = this.elementName.toString().replace('.', '\\.'); // make sure to escape any dot "." from CSS class to avoid console error $(`[name=${elementClassName}].ms-drop`).remove(); + this.$filterElm = null; } } @@ -461,21 +462,23 @@ export class SelectFilter implements Filter { } private onTriggerEvent() { - const selectedItems = this.getValues(); - - if (Array.isArray(selectedItems) && selectedItems.length > 1 || (selectedItems.length === 1 && selectedItems[0] !== '')) { - this.isFilled = true; - this.$filterElm.addClass('filled').siblings('div .search-filter').addClass('filled'); - } else { - this.isFilled = false; - this.$filterElm.removeClass('filled'); - this.$filterElm.siblings('div .search-filter').removeClass('filled'); - } + if (this.$filterElm) { + const selectedItems = this.getValues(); - this.searchTerms = selectedItems; - this.callback(undefined, { columnDef: this.columnDef, operator: this.operator, searchTerms: selectedItems, shouldTriggerQuery: this._shouldTriggerQuery }); - // reset flag for next use - this._shouldTriggerQuery = true; + if (Array.isArray(selectedItems) && selectedItems.length > 1 || (selectedItems.length === 1 && selectedItems[0] !== '')) { + this.isFilled = true; + this.$filterElm?.addClass('filled').siblings('div .search-filter').addClass('filled'); + } else { + this.isFilled = false; + this.$filterElm.removeClass('filled'); + this.$filterElm.siblings('div .search-filter').removeClass('filled'); + } + + this.searchTerms = selectedItems; + this.callback(undefined, { columnDef: this.columnDef, operator: this.operator, searchTerms: selectedItems, shouldTriggerQuery: this._shouldTriggerQuery }); + // reset flag for next use + this._shouldTriggerQuery = true; + } } protected async renderOptionsAsync(collectionAsync: Promise): Promise { diff --git a/packages/common/src/filters/sliderFilter.ts b/packages/common/src/filters/sliderFilter.ts index 735af8a28..330c95cab 100644 --- a/packages/common/src/filters/sliderFilter.ts +++ b/packages/common/src/filters/sliderFilter.ts @@ -137,7 +137,9 @@ export class SliderFilter implements Filter { */ destroy() { if (this.$filterInputElm) { - this.$filterInputElm.off('change').remove(); + this.$filterInputElm.off('input change').remove(); + this.$filterInputElm = null; + this.$filterElm = null; } } diff --git a/packages/common/src/filters/sliderRangeFilter.ts b/packages/common/src/filters/sliderRangeFilter.ts index 4300d8f47..9b0c240a4 100644 --- a/packages/common/src/filters/sliderRangeFilter.ts +++ b/packages/common/src/filters/sliderRangeFilter.ts @@ -116,7 +116,11 @@ export class SliderRangeFilter implements Filter { */ destroy() { if (this.$filterElm) { + this.$filterElm.slider('destroy'); this.$filterElm.off('change').remove(); + this.$filterContainerElm.remove(); + this.$filterElm = null; + this.$filterContainerElm = null; } } diff --git a/packages/common/src/services/extension.service.ts b/packages/common/src/services/extension.service.ts index 9212b4efb..c1468209d 100644 --- a/packages/common/src/services/extension.service.ts +++ b/packages/common/src/services/extension.service.ts @@ -64,7 +64,9 @@ export class ExtensionService { } } } - this._extensionList = {} as ExtensionList; + for (const key of Object.keys(this._extensionList)) { + delete this._extensionList[key]; + } } /** Get all columns (includes visible and non-visible) */ diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts index 59eee621f..03bace715 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts @@ -867,7 +867,7 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () }); it('should destroy customElement and its DOM element when requested', () => { - const spy = jest.spyOn(component, 'destroyGridContainerElm'); + const spy = jest.spyOn(component, 'emptyGridContainerElm'); component.initialization(divContainer, slickEventHandler); component.dispose(true); diff --git a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts index d8425be61..8733c663e 100644 --- a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts +++ b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts @@ -66,6 +66,7 @@ import { // utilities convertParentChildArrayToHierarchicalView, + emptyElement, GetSlickEventType, } from '@slickgrid-universal/common'; @@ -357,7 +358,7 @@ export class SlickVanillaGridBundle { } } - destroyGridContainerElm() { + emptyGridContainerElm() { const gridContainerId = this.gridOptions && this.gridOptions.gridContainerId || 'grid1'; $(gridContainerId).empty(); } @@ -366,15 +367,8 @@ export class SlickVanillaGridBundle { dispose(shouldEmptyDomElementContainer = false) { this._eventPubSubService.publish('onBeforeGridDestroy', this.slickGrid); this._eventHandler?.unsubscribeAll(); - this.slickGrid?.destroy(); - this._gridOptions = {}; this._eventPubSubService.publish('onAfterGridDestroyed', true); - // we could optionally also empty the content of the grid container DOM element - if (shouldEmptyDomElementContainer) { - this.destroyGridContainerElm(); - } - // dispose the Services this.extensionService?.dispose(); this.filterService?.dispose(); @@ -392,6 +386,15 @@ export class SlickVanillaGridBundle { this.slickPagination?.dispose(); this._eventPubSubService?.unsubscribeAll(); + this.dataView?.setItems([]); + this.slickGrid?.destroy(); + emptyElement(this._gridContainerElm); + emptyElement(this._gridParentContainerElm); + + // we could optionally also empty the content of the grid container DOM element + if (shouldEmptyDomElementContainer) { + this.emptyGridContainerElm(); + } } initialization(gridContainerElm: HTMLElement, eventHandler: SlickEventHandler) { @@ -934,7 +937,7 @@ export class SlickVanillaGridBundle { // local grid, check if we need to show the Pagination // if so then also check if there's any presets and finally initialize the PaginationService // a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total - if (this._gridOptions && this._gridOptions.enablePagination && this._isLocalGrid) { + if (this._gridOptions.enablePagination && this._isLocalGrid) { this.showPagination = true; this.loadLocalGridPagination(dataset); }