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
This commit is contained in:
Joffrey JAFFEUX 2020-08-27 16:07:14 +02:00 committed by GitHub
parent a5deff0b8d
commit 1cc5e8ea63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 38 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
}
if (!dropdownBody) return;
/* global Popper:true */
this.state.popper = Popper.createPopper(dropdownHeader, dropdownBody, {
strategy: "fixed",
placement: "bottom-start",
modifiers: [
{
name: "preventOverflow"
},
{
name: "offset",
options: {
offset: [0, 5]
}
/* 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`

View File

@ -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) {