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:
parent
a5deff0b8d
commit
1cc5e8ea63
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue