DEV: gives didRender and willRerender hooks to widgets (#10496)

DEV: gives didRender and willRerender hooks to widgets (#10496)

didRender will be called each time the widget is rendered willRerender will be called the second time a widget is rendered to give an opportunity to clean some state before the tree is replaced

diff --git a/app/assets/javascripts/discourse/app/components/mount-widget.js b/app/assets/javascripts/discourse/app/components/mount-widget.js
index 3701b32..1a91e57 100644
--- a/app/assets/javascripts/discourse/app/components/mount-widget.js
+++ b/app/assets/javascripts/discourse/app/components/mount-widget.js
@@ -2,7 +2,7 @@ import { cancel, scheduleOnce } from "@ember/runloop";
 import Component from "@ember/component";
 import { diff, patch } from "virtual-dom";
 import { WidgetClickHook } from "discourse/widgets/hooks";
-import { queryRegistry } from "discourse/widgets/widget";
+import { queryRegistry, traverseCustomWidgets } from "discourse/widgets/widget";
 import { getRegister } from "discourse-common/lib/get-owner";
 import DirtyKeys from "discourse/lib/dirty-keys";
 import { camelize } from "@ember/string";
@@ -124,12 +124,18 @@ export default Component.extend({
       newTree._emberView = this;
       const patches = diff(this._tree || this._rootNode, newTree);
 
+      if (this._tree) {
+        traverseCustomWidgets(this._tree, w => w.willRerenderWidget());
+      }
+
       this.beforePatch();
       this._rootNode = patch(this._rootNode, patches);
       this.afterPatch();
 
       this._tree = newTree;
 
+      traverseCustomWidgets(newTree, w => w.didRenderWidget());
+
       if (this._renderCallback) {
         this._renderCallback();
         this._renderCallback = null;
diff --git a/app/assets/javascripts/discourse/app/widgets/glue.js b/app/assets/javascripts/discourse/app/widgets/glue.js
index 976aed9..408879a 100644
--- a/app/assets/javascripts/discourse/app/widgets/glue.js
+++ b/app/assets/javascripts/discourse/app/widgets/glue.js
@@ -1,6 +1,6 @@
 import { cancel, scheduleOnce } from "@ember/runloop";
 import { diff, patch } from "virtual-dom";
-import { queryRegistry } from "discourse/widgets/widget";
+import { queryRegistry, traverseCustomWidgets } from "discourse/widgets/widget";
 import DirtyKeys from "discourse/lib/dirty-keys";
 import { isTesting } from "discourse-common/config/environment";
 
@@ -47,23 +47,19 @@ export default class WidgetGlue {
     });
     const patches = diff(this._tree || this._rootNode, newTree);
 
+    if (this._tree) {
+      traverseCustomWidgets(this._tree, w => w.willRerenderWidget());
+    }
+
     newTree._rerenderable = this;
     this._rootNode = patch(this._rootNode, patches);
     this._tree = newTree;
+
+    traverseCustomWidgets(newTree, w => w.didRenderWidget());
   }
 
   cleanUp() {
-    const widgets = [];
-    const findWidgets = widget => {
-      widget.vnode.children.forEach(child => {
-        if (child.constructor.name === "CustomWidget") {
-          widgets.push(child);
-          findWidgets(child);
-        }
-      });
-    };
-    findWidgets(this._tree);
-    widgets.reverse().forEach(widget => widget.destroy());
+    traverseCustomWidgets(this._tree, w => w.destroy());
 
     cancel(this._timeout);
   }
diff --git a/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js b/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js
index 7992daf..3e44736 100644
--- a/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js
+++ b/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js
@@ -1,6 +1,5 @@
 import I18n from "I18n";
 import { createWidget } from "discourse/widgets/widget";
-import { schedule } from "@ember/runloop";
 import hbs from "discourse/widgets/hbs-compiler";
 
 /*
@@ -239,40 +238,45 @@ export const WidgetDropdownClass = {
     }
   },
 
-  _onTrigger() {
-    this.state.opened = !this.state.opened;
+  willRerenderWidget() {
+    this._popper && this._popper.destroy();
+  },
 
-    schedule("afterRender", () => {
+  didRenderWidget() {
+    if (this.state.opened) {
       const dropdownHeader = document.querySelector(
         `#${this.attrs.id} .widget-dropdown-header`
       );
+
+      if (!dropdownHeader) return;
+
       const dropdownBody = document.querySelector(
         `#${this.attrs.id} .widget-dropdown-body`
       );
 
-      if (this.state.opened && dropdownHeader && dropdownBody) {
-        if (this.state.popper) {
-          this.state.popper.destroy();
-        }
-
-        /* global Popper:true */
-        this.state.popper = Popper.createPopper(dropdownHeader, dropdownBody, {
-          strategy: "fixed",
-          placement: "bottom-start",
-          modifiers: [
-            {
-              name: "preventOverflow"
-            },
-            {
-              name: "offset",
-              options: {
-                offset: [0, 5]
-              }
+      if (!dropdownBody) return;
+
+      /* global Popper:true */
+      this._popper = Popper.createPopper(dropdownHeader, dropdownBody, {
+        strategy: "fixed",
+        placement: "bottom-start",
+        modifiers: [
+          {
+            name: "preventOverflow"
+          },
+          {
+            name: "offset",
+            options: {
+              offset: [0, 5]
             }
-          ]
-        });
-      }
-    });
+          }
+        ]
+      });
+    }
+  },
+
+  _onTrigger() {
+    this.state.opened = !this.state.opened;
   },
 
   template: hbs`
diff --git a/app/assets/javascripts/discourse/app/widgets/widget.js b/app/assets/javascripts/discourse/app/widgets/widget.js
index ad54150..4a2f770 100644
--- a/app/assets/javascripts/discourse/app/widgets/widget.js
+++ b/app/assets/javascripts/discourse/app/widgets/widget.js
@@ -36,6 +36,16 @@ export function decorateWidget(widgetName, cb) {
   _decorators[widgetName].push(cb);
 }
 
+export function traverseCustomWidgets(tree, callback) {
+  if (tree.constructor.name === "CustomWidget") {
+    callback(tree);
+  }
+
+  (tree.children || (tree.vnode ? tree.vnode.children : [])).forEach(node => {
+    traverseCustomWidgets(node, callback);
+  });
+}
+
 export function applyDecorators(widget, type, attrs, state) {
   const decorators = _decorators[`${widget.name}:${type}`] || [];
 
@@ -257,6 +267,10 @@ export default class Widget {
     }
   }
 
+  didRenderWidget() {}
+
+  willRerenderWidget() {}
+
   scheduleRerender() {
     let widget = this;
     while (widget) {

GitHub sha: 1cc5e8ea

This commit appears in #10496 which was approved by eviltrout. It was merged by jjaffeux.