From d71632aea41fbbdf8b63cc1f70375af06c5cc5a3 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Wed, 22 Feb 2023 21:26:07 +0100 Subject: [PATCH] fix: render ipywidgets property as a vue child component There were two things that could go wrong when rendering an ipywidget as a child of the vue component using the createObjectForNestedModel. First, since create_child_view is async, the vue component could be destroyed before the child view is created. In that case, we should not attach the view, but directory remove it again. Second, child views were not removed properly, since we did not listen to the destroyed/beforeDestroy life cycled events of the vue component. We now remove the ipywidget view when the vue component is destroyed, but only when it's already created. --- js/src/VueRenderer.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/js/src/VueRenderer.js b/js/src/VueRenderer.js index a84c3f8..55f1da9 100644 --- a/js/src/VueRenderer.js +++ b/js/src/VueRenderer.js @@ -6,11 +6,35 @@ import { VueTemplateModel } from './VueTemplateModel'; import Vue from './VueWithCompiler'; export function createObjectForNestedModel(model, parentView) { + let currentView = null; + let destroyed = false; return { mounted() { parentView .create_child_view(model) - .then(view => JupyterPhosphorWidget.attach(view.pWidget, this.$el)); + .then(view => { + currentView = view; + // since create view is async, the vue component might be destroyed before the view is created + if(!destroyed) { + JupyterPhosphorWidget.attach(view.pWidget, this.$el); + } else { + currentView.remove(); + } + }); + }, + beforeDestroy() { + if (currentView) { + // In vue 3 we can use the beforeUnmount, which is called before the node is removed from the DOM + // In vue 2, we are already disconnected from the document at this stage, which phosphor does not like. + // In order to avoid an error in phosphor, we add the node to the body before removing it. + // (current.remove triggers a phosphor detach) + // To be sure we do not cause any flickering, we hide the node before moving it. + currentView.pWidget.node.style.display = "none"; + document.body.appendChild(currentView.pWidget.node) + currentView.remove(); + } else { + destroyed = true; + } }, render(createElement) { return createElement('div', { style: { height: '100%' } });