From 70697be8c01ef7cbc361951fad730f8181826718 Mon Sep 17 00:00:00 2001 From: Alexander Skvortsov Date: Sun, 9 Aug 2020 19:03:10 -0400 Subject: [PATCH] infrastructure: (mostly) force re-calling oninit when a route change is handled by the same component - Due to mithril 2.0, setting a route will not re-call oninit if the component handling the route has not changed. - Mithril allows us to provide an options parameter to m.route.set, which, if has the state.key parameter changed, will force a re-oninit. However, manually implementing this on every button and component is both tedious, and will make further changes in functionality difficult - To that end, we can add in this patch here, which will take care of most cases. Code that explicitly calls m.route.set will still need to include this options parameter. --- js/src/common/utils/patchMithril.js | 33 ++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/js/src/common/utils/patchMithril.js b/js/src/common/utils/patchMithril.js index 8cdc852ac..bed246240 100644 --- a/js/src/common/utils/patchMithril.js +++ b/js/src/common/utils/patchMithril.js @@ -3,18 +3,47 @@ import Stream from 'mithril/stream'; export default function patchMithril(global) { const defaultMithril = global.m; + /** + * Due to mithril 2.0, setting a route will not re-call oninit if the component handling the route has not changed. + * We, however, want to keep this behavior since it's more intuitive to users (clicking home should refresh home). + * Mithril allows us to provide an options parameter to m.route.set, which, if has the state.key parameter changed, + * will force a re-oninit. See: + * + * - https://github.com/MithrilJS/mithril.js/blob/ff3e12e5d398f653b2d0ea182a1fc963473d47f6/api/router.js#L217 + * - https://mithril.js.org/route.html#mrouteset + * - https://mithril.js.org/keys.html + * + * However, manually implementing this on every button and component is both tedious, and will make further changes in + * functionality difficult to implement at scale, so we patch it here. The original behavior can be replicated by passing + * an empty object as the "options" attr to a link component. + * + * Please note that any code that manually calls m.route.set will need to provide something like this to the + * options parameter itself. Patching m.route.set would be more convenient, but would too severely restrict flexibility + */ + const defaultLinkView = defaultMithril.route.Link.view; + const modifiedLink = { + view: function (vnode) { + if (!vnode.attrs.options) vnode.attrs.options = { state: { key: Date.now() } }; + + return defaultLinkView(vnode); + } + }; + const modifiedMithril = function (comp, ...args) { const node = defaultMithril.apply(this, arguments); if (!node.attrs) node.attrs = {}; + // Allows the use of the bidi attr. if (node.attrs.bidi) { modifiedMithril.bidi(node, node.attrs.bidi); } + // Allows us to use a "route" attr on links, which will automatically convert the link to one which + // supports linking to other pages in the SPA without refreshing the document. if (node.attrs.route) { node.attrs.href = node.attrs.route; - node.tag = defaultMithril.route.Link; + node.tag = modifiedLink; delete node.attrs.route; } @@ -24,6 +53,8 @@ export default function patchMithril(global) { Object.keys(defaultMithril).forEach((key) => (modifiedMithril[key] = defaultMithril[key])); + modifiedMithril.route.Link = modifiedLink; + modifiedMithril.stream = Stream; global.m = modifiedMithril;