From 415d866838e111e986f7ec94f400b8633268a39f Mon Sep 17 00:00:00 2001 From: TSV Date: Mon, 28 Aug 2023 15:16:51 +0300 Subject: [PATCH 1/9] Resolved https://github.com/surveyjs/survey-creator/issues/4484 - memory leaks in survey creator --- .../components/action-container-view-model.ts | 1 + .../page-navigator/page-navigator.ts | 32 +++++++++++++------ .../src/components/question.ts | 1 + .../src/components/side-bar/side-bar-model.ts | 14 +++++--- .../src/components/tabs/test-plugin.ts | 1 + .../src/components/tabs/test.ts | 12 +++++++ .../src/adorners/question.ts | 3 +- .../src/page-navigator/page-navigator.ts | 3 +- packages/survey-creator-knockout/src/page.ts | 4 +++ .../src/string-editor.ts | 2 ++ 10 files changed, 57 insertions(+), 16 deletions(-) diff --git a/packages/survey-creator-core/src/components/action-container-view-model.ts b/packages/survey-creator-core/src/components/action-container-view-model.ts index a866bb3580..661c0ace69 100644 --- a/packages/survey-creator-core/src/components/action-container-view-model.ts +++ b/packages/survey-creator-core/src/components/action-container-view-model.ts @@ -69,6 +69,7 @@ export class SurveyElementAdornerBase e public dispose(): void { super.dispose(); this.detachElement(this.surveyElement); + this.actionContainer.dispose(); } protected onElementSelectedChanged(isSelected: boolean): void { if (!isSelected) return; diff --git a/packages/survey-creator-core/src/components/page-navigator/page-navigator.ts b/packages/survey-creator-core/src/components/page-navigator/page-navigator.ts index 146d97f639..d4128cb7a1 100644 --- a/packages/survey-creator-core/src/components/page-navigator/page-navigator.ts +++ b/packages/survey-creator-core/src/components/page-navigator/page-navigator.ts @@ -17,6 +17,16 @@ export class PageNavigatorViewModel extends Base { }; private _resizeObserver: ResizeObserver; + private pcPropertyChangedHandler = (sender, options) => { + if (options.name === "toolboxLocation") { + if (this.pagesController.creator["toolboxLocation"] == "sidebar") { + this.popupModel.horizontalPosition = "right"; + } else { + this.popupModel.horizontalPosition = this.pagesController.creator["toolboxLocation"]; + } + } + } + constructor(private pagesController: PagesController, private pageEditMode: string) { super(); this.icon = "icon-select-page"; @@ -38,15 +48,7 @@ export class PageNavigatorViewModel extends Base { }; this.popupModel.onHide = () => { this.isPopupOpened = false; }; if (!!this.pagesController.creator["onPropertyChanged"]) { - this.pagesController.creator["onPropertyChanged"].add((sender, options) => { - if (options.name === "toolboxLocation") { - if (this.pagesController.creator["toolboxLocation"] == "sidebar") { - this.popupModel.horizontalPosition = "right"; - } else { - this.popupModel.horizontalPosition = this.pagesController.creator["toolboxLocation"]; - } - } - }); + this.pagesController.creator["onPropertyChanged"].add(this.pcPropertyChangedHandler); } this.buildItems(); } @@ -55,6 +57,18 @@ export class PageNavigatorViewModel extends Base { this.stopItemsContainerHeightObserver(); this.pagesController.onPagesChanged.remove(this.pagesChangedFunc); this.pagesController.onCurrentPageChanged.remove(this.currentPagesChangedFunc); + if (!!this.pagesController.creator["onPropertyChanged"]) { + this.pagesController.creator["onPropertyChanged"].remove(this.pcPropertyChangedHandler); + this.pcPropertyChangedHandler = undefined; + } + if(this.pageListModel) { + this.pageListModel.dispose(); + } + if(this.popupModel) { + this.popupModel.dispose(); + } + this._scrollableContainer = undefined; + this._itemsContainer = undefined; } @propertyArray() items: Array; diff --git a/packages/survey-creator-core/src/components/question.ts b/packages/survey-creator-core/src/components/question.ts index 8ac0ca1740..74237697fa 100644 --- a/packages/survey-creator-core/src/components/question.ts +++ b/packages/survey-creator-core/src/components/question.ts @@ -168,6 +168,7 @@ export class QuestionAdornerViewModel extends SurveyElementAdornerBase { if (!!this.surveyElement["setCanShowOptionItemCallback"]) { (this.surveyElement).setCanShowOptionItemCallback(undefined); } + super.dispose(); } get isDraggable() { return true; diff --git a/packages/survey-creator-core/src/components/side-bar/side-bar-model.ts b/packages/survey-creator-core/src/components/side-bar/side-bar-model.ts index c51397deff..35ad3a4108 100644 --- a/packages/survey-creator-core/src/components/side-bar/side-bar-model.ts +++ b/packages/survey-creator-core/src/components/side-bar/side-bar-model.ts @@ -91,6 +91,11 @@ export class SidebarModel extends Base { private getCurrentHandles(): string { return this.creator.sidebarLocation == "right" ? "w" : "e"; } + private sidebarLocationChangedHandler = (sender, options) => { + if (options.name === "sidebarLocation" && !!this.resizeManager) { + this.resizeManager.setHandles(this.getCurrentHandles()); + } + }; constructor(private creator: CreatorBase) { super(); @@ -99,11 +104,7 @@ export class SidebarModel extends Base { this.visible = options.show; }; this.creator.onShowSidebarVisibilityChanged.add(this.onSidebarVisibilityChanged); - this.creator.onPropertyChanged.add((sender, options) => { - if (options.name === "sidebarLocation" && !!this.resizeManager) { - this.resizeManager.setHandles(this.getCurrentHandles()); - } - }); + this.creator.onPropertyChanged.add(this.sidebarLocationChangedHandler); this.visible = this.creator.showSidebar; this.createActions(); } @@ -131,7 +132,10 @@ export class SidebarModel extends Base { public dispose() { if (!!this.creator && !this.isDisposed) { this.creator.onShowSidebarVisibilityChanged.remove(this.onSidebarVisibilityChanged); + this.creator.onPropertyChanged.remove(this.sidebarLocationChangedHandler); + this.sidebarLocationChangedHandler = undefined; } + this.resetResizeManager(); super.dispose(); } public initResizeManager(container: HTMLDivElement): void { diff --git a/packages/survey-creator-core/src/components/tabs/test-plugin.ts b/packages/survey-creator-core/src/components/tabs/test-plugin.ts index bb7cc926f9..74768d9ecf 100644 --- a/packages/survey-creator-core/src/components/tabs/test-plugin.ts +++ b/packages/survey-creator-core/src/components/tabs/test-plugin.ts @@ -111,6 +111,7 @@ export class TabTestPlugin implements ICreatorPlugin { if (this.model) { this.simulatorTheme = this.model.simulator.survey.css; this.model.onSurveyCreatedCallback = undefined; + this.model.dispose(); this.model = undefined; } this.languageSelectorAction.visible = false; diff --git a/packages/survey-creator-core/src/components/tabs/test.ts b/packages/survey-creator-core/src/components/tabs/test.ts index af6a3a3208..59aefc253d 100644 --- a/packages/survey-creator-core/src/components/tabs/test.ts +++ b/packages/survey-creator-core/src/components/tabs/test.ts @@ -317,4 +317,16 @@ export class TestSurveyTabViewModel extends Base { this.onScrollCallback(); return true; } + public dispose(): void { + if(this.prevPageAction) { + this.prevPageAction.dispose(); + } + if(this.nextPageAction) { + this.nextPageAction.dispose(); + } + if(this.selectPageAction) { + this.selectPageAction.dispose(); + } + this.simulator.dispose(); + } } diff --git a/packages/survey-creator-knockout/src/adorners/question.ts b/packages/survey-creator-knockout/src/adorners/question.ts index 3d77e56fa7..d2c182faa2 100644 --- a/packages/survey-creator-knockout/src/adorners/question.ts +++ b/packages/survey-creator-knockout/src/adorners/question.ts @@ -27,8 +27,9 @@ export function createQuestionViewModel( return model.isEmptyElement; }); model["adornerComponent"] = undefined; - new ImplementorBase(model); + const implementor = new ImplementorBase(model); ko.utils.domNodeDisposal.addDisposeCallback(componentInfo.element, () => { + implementor.dispose(); model.dispose(); }); return model; diff --git a/packages/survey-creator-knockout/src/page-navigator/page-navigator.ts b/packages/survey-creator-knockout/src/page-navigator/page-navigator.ts index a7d744f041..8932f5e1ba 100644 --- a/packages/survey-creator-knockout/src/page-navigator/page-navigator.ts +++ b/packages/survey-creator-knockout/src/page-navigator/page-navigator.ts @@ -27,7 +27,7 @@ ko.components.register("svc-page-navigator", { createViewModel: (params: any, componentInfo: any) => { const model = new PageNavigatorView(params.controller, params.pageEditMode); model.setItemsContainer(componentInfo.element.parentElement); - new ImplementorBase(model); + const implementor = new ImplementorBase(model); const scrollableViewPort = componentInfo.element.parentElement.parentElement.parentElement; model.setScrollableContainer(scrollableViewPort); if (params.pageEditMode !== "bypage") { @@ -38,6 +38,7 @@ ko.components.register("svc-page-navigator", { ko.utils.domNodeDisposal.addDisposeCallback(componentInfo.element, () => { scrollableViewPort.onscroll = undefined; model.dispose(); + implementor.dispose(); }); return model; }, diff --git a/packages/survey-creator-knockout/src/page.ts b/packages/survey-creator-knockout/src/page.ts index 6c37ec6ae1..d932f65ca6 100644 --- a/packages/survey-creator-knockout/src/page.ts +++ b/packages/survey-creator-knockout/src/page.ts @@ -33,6 +33,10 @@ export class CreatorSurveyPageComponent extends PageAdorner { fixedDispose(): void { this.pageUpdater && this.pageUpdater.dispose(); super.dispose(); + if(ko.isWritableObservable(this._page)) { + (this._page as ko.Observable)(undefined); + } + this._page = undefined; } dispose(): void { diff --git a/packages/survey-creator-knockout/src/string-editor.ts b/packages/survey-creator-knockout/src/string-editor.ts index 7f72baaf6a..c2534ed83c 100644 --- a/packages/survey-creator-knockout/src/string-editor.ts +++ b/packages/survey-creator-knockout/src/string-editor.ts @@ -120,6 +120,8 @@ export class StringEditorViewModel { this.baseModel.blurEditor = undefined; this.implementor.dispose(); this.implementor = undefined; + this.baseModel.getEditorElement = undefined; + this.baseModel.dispose(); } } From 6f4082d98d26381434da5f0d4852cb282ac7550b Mon Sep 17 00:00:00 2001 From: TSV Date: Tue, 29 Aug 2023 10:17:04 +0300 Subject: [PATCH 2/9] Work for https://github.com/surveyjs/survey-creator/issues/4484 - memory leaks in survey creator --- .../src/components/action-container-view-model.ts | 3 +++ .../src/components/tabs/designer-plugin.ts | 8 ++------ packages/survey-creator-react/src/adorners/Page.tsx | 1 + 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/survey-creator-core/src/components/action-container-view-model.ts b/packages/survey-creator-core/src/components/action-container-view-model.ts index 661c0ace69..0f6b317b37 100644 --- a/packages/survey-creator-core/src/components/action-container-view-model.ts +++ b/packages/survey-creator-core/src/components/action-container-view-model.ts @@ -70,6 +70,9 @@ export class SurveyElementAdornerBase e super.dispose(); this.detachElement(this.surveyElement); this.actionContainer.dispose(); + this.creator.sidebar.onPropertyChanged.remove(this.sidebarFlyoutModeChangedFunc); + this.selectedPropPageFunc = undefined; + this.sidebarFlyoutModeChangedFunc = undefined; } protected onElementSelectedChanged(isSelected: boolean): void { if (!isSelected) return; diff --git a/packages/survey-creator-core/src/components/tabs/designer-plugin.ts b/packages/survey-creator-core/src/components/tabs/designer-plugin.ts index 88fce2cdc6..879dfc6856 100644 --- a/packages/survey-creator-core/src/components/tabs/designer-plugin.ts +++ b/packages/survey-creator-core/src/components/tabs/designer-plugin.ts @@ -125,12 +125,8 @@ export class TabDesignerPlugin implements ICreatorPlugin { action: () => { this.selectSurvey(); }, - active: new ComputedUpdater(() => { - return notShortCircuitAnd(this.creator.sidebar.activeTab === this.propertyGridTab.id, this.isSurveySelected); - }), - pressed: new ComputedUpdater(() => { - return notShortCircuitAnd(this.creator.sidebar.activeTab === this.propertyGridTab.id, this.isSurveySelected); - }), + active: this.isSettingsActive, + pressed: this.isSettingsActive, visible: this.createVisibleUpdater(), locTitleName: "ed.surveySettings", locTooltipName: "ed.surveySettingsTooltip", diff --git a/packages/survey-creator-react/src/adorners/Page.tsx b/packages/survey-creator-react/src/adorners/Page.tsx index 60a0333247..61bc755e4c 100644 --- a/packages/survey-creator-react/src/adorners/Page.tsx +++ b/packages/survey-creator-react/src/adorners/Page.tsx @@ -56,6 +56,7 @@ export class CreatorSurveyPageComponent extends CreatorModelElement< } componentWillUnmount() { super.componentWillUnmount(); + this.model.onPageSelectedCallback = undefined; this.model.dispose(); } protected canRender(): boolean { From 96f844e4e5ee0ec7b75ba23d23b3391622b14d53 Mon Sep 17 00:00:00 2001 From: tsv2013 Date: Wed, 30 Aug 2023 19:07:22 +0300 Subject: [PATCH 3/9] Resolved https://github.com/surveyjs/survey-creator/issues/4484 --- .../src/components/page.ts | 2 +- .../src/adorners/question.ts | 1 - .../src/string-editor.ts | 27 ++++++++++--------- .../survey-creator-react/src/StringEditor.tsx | 16 ++++++----- .../src/adorners/Question.tsx | 3 +++ 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/survey-creator-core/src/components/page.ts b/packages/survey-creator-core/src/components/page.ts index 9ce3f929ff..13a0dc9a18 100644 --- a/packages/survey-creator-core/src/components/page.ts +++ b/packages/survey-creator-core/src/components/page.ts @@ -93,8 +93,8 @@ export class PageAdorner extends SurveyElementAdornerBase { }; } public dispose(): void { - super.dispose(); this.detachElement(this.page); + super.dispose(); this.onPropertyValueChangedCallback = undefined; } public get isGhost(): boolean { diff --git a/packages/survey-creator-knockout/src/adorners/question.ts b/packages/survey-creator-knockout/src/adorners/question.ts index d2c182faa2..2009aa5812 100644 --- a/packages/survey-creator-knockout/src/adorners/question.ts +++ b/packages/survey-creator-knockout/src/adorners/question.ts @@ -30,7 +30,6 @@ export function createQuestionViewModel( const implementor = new ImplementorBase(model); ko.utils.domNodeDisposal.addDisposeCallback(componentInfo.element, () => { implementor.dispose(); - model.dispose(); }); return model; } diff --git a/packages/survey-creator-knockout/src/string-editor.ts b/packages/survey-creator-knockout/src/string-editor.ts index c2534ed83c..ad053101d2 100644 --- a/packages/survey-creator-knockout/src/string-editor.ts +++ b/packages/survey-creator-knockout/src/string-editor.ts @@ -4,25 +4,23 @@ import { LocalizableString } from "survey-core"; import { ImplementorBase } from "survey-knockout-ui"; const template = require("./string-editor.html"); +function getEditorElement(element: HTMLElement) { + return (element.nextSibling as any).getElementsByClassName("sv-string-editor")[0]; +}; + export class StringEditorViewModel { private implementor = undefined; private baseModel: StringEditorViewModelBase; - getEditorElement = (element) => { - return element.nextSibling.getElementsByClassName( - "sv-string-editor" - )[0]; - }; - constructor(public locString: any, private creator: CreatorBase, element: any) { this.baseModel = new StringEditorViewModelBase(locString, creator); - this.baseModel.getEditorElement = () => this.getEditorElement(element); + this.baseModel.getEditorElement = () => getEditorElement(element); this.implementor = new ImplementorBase(this.baseModel); this.focusEditor = () => { - this.getEditorElement(element).focus(); + getEditorElement(element).focus(); }; this.baseModel.blurEditor = () => { - const editorElement = this.getEditorElement(element); + const editorElement = getEditorElement(element); editorElement.blur(); editorElement.spellcheck = false; }; @@ -34,7 +32,7 @@ export class StringEditorViewModel { return locString; } - public afterRender = ()=>{ + public afterRender = () => { this.baseModel.afterRender(); } @@ -52,7 +50,7 @@ export class StringEditorViewModel { return this.baseModel.placeholder; } public get contentEditable(): string { - return this.baseModel.contentEditable?"true":"false"; + return this.baseModel.contentEditable ? "true" : "false"; } public get characterCounter(): any { return this.baseModel.characterCounter; @@ -116,10 +114,12 @@ export class StringEditorViewModel { public focusEditor: () => void; public dispose(): void { this.locString.onSearchChanged = undefined; + if (!!this.implementor) { + this.implementor.dispose(); + this.implementor = undefined; + } this.focusEditor = undefined; this.baseModel.blurEditor = undefined; - this.implementor.dispose(); - this.implementor = undefined; this.baseModel.getEditorElement = undefined; this.baseModel.dispose(); } @@ -182,6 +182,7 @@ ko.components.register(editableStringRendererName, { ko.utils.domNodeDisposal.addDisposeCallback(componentInfo.element, () => { subscrib.dispose(); + model.dispose(); }); return model; }, diff --git a/packages/survey-creator-react/src/StringEditor.tsx b/packages/survey-creator-react/src/StringEditor.tsx index f48cee6dce..92f355e125 100644 --- a/packages/survey-creator-react/src/StringEditor.tsx +++ b/packages/survey-creator-react/src/StringEditor.tsx @@ -11,14 +11,12 @@ export class SurveyLocStringEditor extends CreatorModelElement { super(props); this.state = { changed: 0 }; this.svStringEditorRef = React.createRef(); - this.baseModel.getEditorElement = () => this.svStringEditorRef.current; } protected createModel(): void { + if (this.baseModel) { + this.baseModel.dispose(); + } this.baseModel = new StringEditorViewModelBase(this.locString, this.creator); - this.baseModel.blurEditor = () => { - this.svStringEditorRef.current.blur(); - this.svStringEditorRef.current.spellcheck = false; - }; } protected getUpdatedModelProps(): string[] { return ["creator", "locString"]; @@ -46,7 +44,11 @@ export class SurveyLocStringEditor extends CreatorModelElement { public componentDidMount() { super.componentDidMount(); if (!this.locString) return; - const self: SurveyLocStringEditor = this; + this.baseModel.getEditorElement = () => this.svStringEditorRef.current; + this.baseModel.blurEditor = () => { + this.svStringEditorRef.current.blur(); + this.svStringEditorRef.current.spellcheck = false; + }; this.baseModel.afterRender(); this.locString.onStringChanged.add(this.onChangedHandler); if (this.locString["__isEditing"]) { @@ -60,6 +62,8 @@ export class SurveyLocStringEditor extends CreatorModelElement { } public componentWillUnmount() { super.componentWillUnmount(); + this.baseModel.getEditorElement = undefined; + this.baseModel.blurEditor = undefined; if (!this.locString) return; this.locString.onStringChanged.remove(this.onChangedHandler); } diff --git a/packages/survey-creator-react/src/adorners/Question.tsx b/packages/survey-creator-react/src/adorners/Question.tsx index 35c0c98f5d..cc8b8dfec3 100644 --- a/packages/survey-creator-react/src/adorners/Question.tsx +++ b/packages/survey-creator-react/src/adorners/Question.tsx @@ -26,6 +26,9 @@ export class QuestionAdornerComponent extends CreatorModelElement< protected rootRef: React.RefObject; protected createModel(): void { + if (this.modelValue) { + this.modelValue.dispose(); + } this.modelValue = this.createQuestionViewModel(); } protected createQuestionViewModel(): QuestionAdornerViewModel { From 85b9a84daffdac7bd357b33da7402fcb84b59075 Mon Sep 17 00:00:00 2001 From: tsv2013 Date: Mon, 4 Sep 2023 16:07:16 +0300 Subject: [PATCH 4/9] Work for #4484 - fixed leaks in Angular --- .../src/adorners/item-value.component.ts | 4 ++++ .../src/adorners/matrix-cell.component.ts | 4 ++++ .../src/adorners/question-rating.component.ts | 4 ++++ packages/survey-creator-angular/src/page.component.ts | 1 - packages/survey-creator-angular/src/question.component.ts | 4 ++++ packages/survey-creator-angular/src/row.component.ts | 5 +++++ .../survey-creator-angular/src/string-editor.component.ts | 3 +++ .../src/toolbox/toolbox-tool.component.ts | 4 ++++ .../survey-creator-core/src/components/question-image.ts | 7 ++++++- 9 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/survey-creator-angular/src/adorners/item-value.component.ts b/packages/survey-creator-angular/src/adorners/item-value.component.ts index 03e47d657a..de6e733cad 100644 --- a/packages/survey-creator-angular/src/adorners/item-value.component.ts +++ b/packages/survey-creator-angular/src/adorners/item-value.component.ts @@ -37,6 +37,10 @@ export class ItemValueDesignerComponent extends CreatorModelComponent { public adorner!: PageAdorner; protected createModel(): void { if (this.model) { - this.previousModel?.dispose(); this.adorner = new PageAdorner(this.creator, this.model); } } diff --git a/packages/survey-creator-angular/src/question.component.ts b/packages/survey-creator-angular/src/question.component.ts index 49f71740bc..902676b636 100644 --- a/packages/survey-creator-angular/src/question.component.ts +++ b/packages/survey-creator-angular/src/question.component.ts @@ -47,6 +47,10 @@ export class QuestionDesignerComponent extends CreatorModelComponent { getPropertiesToTrack(): string[] { return ["creator", "row"]; } + override ngOnDestroy(): void { + super.ngOnDestroy(); + this.model.dispose(); + } + } AngularComponentFactory.Instance.registerComponent("svc-row", CreatorRowComponent); \ No newline at end of file diff --git a/packages/survey-creator-angular/src/string-editor.component.ts b/packages/survey-creator-angular/src/string-editor.component.ts index 533b15c448..3d406b310f 100644 --- a/packages/survey-creator-angular/src/string-editor.component.ts +++ b/packages/survey-creator-angular/src/string-editor.component.ts @@ -101,6 +101,9 @@ export class StringEditorComponent extends CreatorModelComponent { protected override getPropertiesToUpdateSync(): string[] { return ["mode"]; } + override ngOnDestroy(): void { + super.ngOnDestroy(); + this.model.dispose(); + } } diff --git a/packages/survey-creator-core/src/components/question-image.ts b/packages/survey-creator-core/src/components/question-image.ts index 1376d95981..a66ffbb002 100644 --- a/packages/survey-creator-core/src/components/question-image.ts +++ b/packages/survey-creator-core/src/components/question-image.ts @@ -21,7 +21,7 @@ export class QuestionImageAdornerViewModel extends QuestionAdornerViewModel { this.filePresentationModel.storeDataAsText = false; surveyModel.onUploadFiles.add((s, o) => { const fileToUpload = o.files[0]; - if(!!fileToUpload) { + if (!!fileToUpload) { this.creator.uploadFiles(o.files, this.question, (status, link) => { this.question.imageLink = link; }); @@ -78,4 +78,9 @@ export class QuestionImageAdornerViewModel extends QuestionAdornerViewModel { public get chooseImageText(): string { return getLocString("ed.imageChooseImage"); } + + public dispose() { + super.dispose(); + this.questionRoot = undefined; + } } \ No newline at end of file From d81ee2f282d950978cfba6aa22e6966c685ce557 Mon Sep 17 00:00:00 2001 From: tsv2013 Date: Mon, 4 Sep 2023 17:38:28 +0300 Subject: [PATCH 5/9] Fixed lint --- packages/survey-creator-knockout/src/string-editor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/survey-creator-knockout/src/string-editor.ts b/packages/survey-creator-knockout/src/string-editor.ts index ad053101d2..cf7d488d30 100644 --- a/packages/survey-creator-knockout/src/string-editor.ts +++ b/packages/survey-creator-knockout/src/string-editor.ts @@ -6,7 +6,7 @@ const template = require("./string-editor.html"); function getEditorElement(element: HTMLElement) { return (element.nextSibling as any).getElementsByClassName("sv-string-editor")[0]; -}; +} export class StringEditorViewModel { private implementor = undefined; From af2b7095a9a50a10569edda97ec21b44a8028b4c Mon Sep 17 00:00:00 2001 From: tsv2013 Date: Tue, 5 Sep 2023 09:50:38 +0300 Subject: [PATCH 6/9] Work for #4484 - fixed f-tests in react --- .../src/components/string-editor.ts | 27 +++++++++++-------- .../survey-creator-react/src/StringEditor.tsx | 5 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/survey-creator-core/src/components/string-editor.ts b/packages/survey-creator-core/src/components/string-editor.ts index cb72c43051..bec7098c6c 100644 --- a/packages/survey-creator-core/src/components/string-editor.ts +++ b/packages/survey-creator-core/src/components/string-editor.ts @@ -25,7 +25,7 @@ export abstract class StringItemsNavigatorBase { protected addNewItems(items: any, startIndex: number, itemsToAdd: string[]) { const createNewItem = (val: any): ItemValue => { - if(this.question.createItemValue) return this.question.createItemValue(val); + if (this.question.createItemValue) return this.question.createItemValue(val); return new ItemValue(val); }; let newItems = items.slice(0, startIndex).concat(itemsToAdd.map(text => createNewItem(text))).concat(items.slice(startIndex + 1)); @@ -117,7 +117,7 @@ class StringItemsNavigatorSelectBase extends StringItemsNavigatorBase { } protected addNewItem(creator: CreatorBase, items: any, text: string = null) { const itemValue = creator.createNewItemValue(this.question); - if(!!text) itemValue.value = text; + if (!!text) itemValue.value = text; } protected getItemsPropertyName(items: any) { return "choices"; @@ -217,9 +217,7 @@ export class StringEditorViewModelBase extends Base { constructor(private locString: LocalizableString, private creator: CreatorBase) { super(); - this.connector = StringEditorConnector.get(locString); - this.connector.onDoActivate.add(() => { this.activate(); }); - this.checkMarkdownToTextConversion(this.locString.owner, this.locString.name); + this.setLocString(locString); } public afterRender() { @@ -228,7 +226,12 @@ export class StringEditorViewModelBase extends Base { } } - public activate() { + public dispose() { + super.dispose(); + this.connector.onDoActivate.remove(this.activate); + } + + public activate = () => { const element = this.getEditorElement(); if (element && element.offsetParent != null) { element.focus(); @@ -239,9 +242,11 @@ export class StringEditorViewModelBase extends Base { } public setLocString(locString: LocalizableString) { + this.connector?.onDoActivate.clear(); this.locString = locString; this.connector = StringEditorConnector.get(locString); - this.connector.onDoActivate.add(() => { this.activate(); }); + this.connector.onDoActivate.add(this.activate); + this.checkMarkdownToTextConversion(this.locString.owner, this.locString.name); } public checkConstraints(event: any) { if (this.maxLength > 0 && event.keyCode >= 32) { @@ -373,12 +378,12 @@ export class StringEditorViewModelBase extends Base { this.creator.inplaceEditForValues && ["noneText", "otherText", "selectAllText"].indexOf(this.locString.name) == -1) { const itemValue = this.locString.owner; - if(itemValue.value !== clearedText) { - if(!!itemValue.locOwner && !!itemValue.ownerPropertyName) { + if (itemValue.value !== clearedText) { + if (!!itemValue.locOwner && !!itemValue.ownerPropertyName) { const choices = itemValue.locOwner[itemValue.ownerPropertyName]; - if(Array.isArray(choices) && !!ItemValue.getItemByValue(choices, clearedText)) { + if (Array.isArray(choices) && !!ItemValue.getItemByValue(choices, clearedText)) { clearedText = getNextItemValue(clearedText, choices); - if(!!event && !!event.target) { + if (!!event && !!event.target) { event.target.innerText = clearedText; } } diff --git a/packages/survey-creator-react/src/StringEditor.tsx b/packages/survey-creator-react/src/StringEditor.tsx index 92f355e125..0e21bfbdd3 100644 --- a/packages/survey-creator-react/src/StringEditor.tsx +++ b/packages/survey-creator-react/src/StringEditor.tsx @@ -58,12 +58,14 @@ export class SurveyLocStringEditor extends CreatorModelElement { } public componentDidUpdate(prevProps: any, prevState: any): void { super.componentDidUpdate(prevProps, prevState); + this.baseModel.setLocString(this.locString); this.baseModel.afterRender(); } public componentWillUnmount() { super.componentWillUnmount(); this.baseModel.getEditorElement = undefined; this.baseModel.blurEditor = undefined; + this.baseModel.dispose(); if (!this.locString) return; this.locString.onStringChanged.remove(this.onChangedHandler); } @@ -123,9 +125,6 @@ export class SurveyLocStringEditor extends CreatorModelElement { if (!this.locString) { return null; } - else { - this.baseModel.setLocString(this.locString); - } let control = null; if (this.locString.hasHtml) { const htmlValue = { __html: this.baseModel.focused && this.baseModel.editAsText && this.locString.text || this.locString.renderedHtml }; From d083d581899a2fb6900fbb76fab237047823071a Mon Sep 17 00:00:00 2001 From: tsv2013 Date: Tue, 5 Sep 2023 12:30:51 +0300 Subject: [PATCH 7/9] Work for #4484 - fixed f-tests in knockout --- packages/survey-creator-core/src/components/page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/survey-creator-core/src/components/page.ts b/packages/survey-creator-core/src/components/page.ts index 13a0dc9a18..9f3a32cb3d 100644 --- a/packages/survey-creator-core/src/components/page.ts +++ b/packages/survey-creator-core/src/components/page.ts @@ -15,7 +15,7 @@ export class PageAdorner extends SurveyElementAdornerBase { @property({ defaultValue: "" }) currentAddQuestionType: string; @property({ defaultValue: null }) dragTypeOverMe: DragTypeOverMeEnum; private updateDragTypeOverMe() { - this.dragTypeOverMe = this.page.dragTypeOverMe; + this.dragTypeOverMe = this.page?.dragTypeOverMe; } constructor(creator: CreatorBase, page: PageModel) { super(creator, page); From e6dfa81f3452be57e506a9fe64a1fb0024ce0f85 Mon Sep 17 00:00:00 2001 From: tsv2013 Date: Tue, 5 Sep 2023 18:20:55 +0300 Subject: [PATCH 8/9] Work for #4484 - fixed f-test - test tab for mobile devices --- packages/survey-creator-core/src/components/tabs/test.ts | 8 +------- .../src/components/tabs/theme-plugin.ts | 1 + .../src/tabbed-menu/tabbed-menu.ts | 9 +++++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/survey-creator-core/src/components/tabs/test.ts b/packages/survey-creator-core/src/components/tabs/test.ts index 074c6bbe0c..5da9a64c3d 100644 --- a/packages/survey-creator-core/src/components/tabs/test.ts +++ b/packages/survey-creator-core/src/components/tabs/test.ts @@ -308,13 +308,7 @@ export class TestSurveyTabViewModel extends Base { return true; } public dispose(): void { - if(this.prevPageAction) { - this.prevPageAction.dispose(); - } - if(this.nextPageAction) { - this.nextPageAction.dispose(); - } - if(this.selectPageAction) { + if (this.selectPageAction) { this.selectPageAction.dispose(); } this.simulator.dispose(); diff --git a/packages/survey-creator-core/src/components/tabs/theme-plugin.ts b/packages/survey-creator-core/src/components/tabs/theme-plugin.ts index 5459143cb8..d3c2ef1b83 100644 --- a/packages/survey-creator-core/src/components/tabs/theme-plugin.ts +++ b/packages/survey-creator-core/src/components/tabs/theme-plugin.ts @@ -90,6 +90,7 @@ export class TabThemePlugin implements ICreatorPlugin { if (this.model) { this.simulatorTheme = this.model.simulator.survey.css; this.model.onSurveyCreatedCallback = undefined; + this.model.dispose(); this.model = undefined; } this.sidebarTab.visible = false; diff --git a/packages/survey-creator-knockout/src/tabbed-menu/tabbed-menu.ts b/packages/survey-creator-knockout/src/tabbed-menu/tabbed-menu.ts index d7f7d22eee..f64e991cd7 100644 --- a/packages/survey-creator-knockout/src/tabbed-menu/tabbed-menu.ts +++ b/packages/survey-creator-knockout/src/tabbed-menu/tabbed-menu.ts @@ -12,16 +12,17 @@ ko.components.register("svc-tabbed-menu", { createViewModel: (params: any, componentInfo: any) => { const model = params.model; const container: HTMLDivElement = componentInfo.element.nextElementSibling; - new ActionContainerImplementor(model); + const reactivityImplementor = new ActionContainerImplementor(model); const manager: ResponsivityManager = new ResponsivityManager( container, model, ".svc-tabbed-menu-item-container:not(.sv-dots)>.sv-action__content" ); - ko.utils.domNodeDisposal.addDisposeCallback(container, () => - manager.dispose() - ); + ko.utils.domNodeDisposal.addDisposeCallback(container, () => { + manager.dispose(); + reactivityImplementor.dispose(); + }); return model; } }, From 71370fe1d8d1bfaefae3576d9e3e134467a7ff9d Mon Sep 17 00:00:00 2001 From: tsv2013 Date: Tue, 5 Sep 2023 18:37:41 +0300 Subject: [PATCH 9/9] Work for #4484 - dispose theme tab model --- packages/survey-creator-core/src/components/tabs/theme.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/survey-creator-core/src/components/tabs/theme.ts b/packages/survey-creator-core/src/components/tabs/theme.ts index fb606300e6..a1d7c6fff1 100644 --- a/packages/survey-creator-core/src/components/tabs/theme.ts +++ b/packages/survey-creator-core/src/components/tabs/theme.ts @@ -1077,4 +1077,11 @@ export class ThemeSurveyTabViewModel extends Base { return themeEditorSurveyJSON; } + public dispose(): void { + this.themeEditorSurveyValue?.dispose(); + if (this.selectPageAction) { + this.selectPageAction.dispose(); + } + this.simulator.dispose(); + } }