MutationObserver Promise made easy
When adding a new feature to my Nifty Sitecore Userscript I noticed a lot of similar code to initiate one-time MutationObserver
s and disbanding them again after the Mutation was Observed. Clearly, a refactor was in order.
I found this Gist that was already half the way to what I needed, including someone asking for a Timeout
feature in the comments. I just needed more flexibility on what to Observe and on when to resolve immediately.
This resulted in mop()
, a MutationObserver Promise implementation. With mop()
it becomes very easy to make a function that triggers some Mutation and returns a Promise
that resolves when the Mutation is Observed or rejects if after a set Timeout the Mutation still hasn’t happened.
This mop()
function accepts the following arguments:
trigger
Logic to perform the action that triggers the Mutation
(If this function returnstrue
, the Promise immediately resolves without waiting for a Mutation)watch
Element to Observe Mutation onquery
Query selector to search for in the added nodes
(optional, defaults to'*'
for everything)options
MutationObserver options about how deep to observe
(optional, defaults to only childList)timeout
Milliseconds after which to fail
(optional, defaults to never timing out)
An example use of this function would be the following function to click a node in the Sitecore Content Editor. Utilizing mop()
the function clickTreeNode()
contains simply the logic for clicking the element and what to watch for. All logic of building a MutationObserver
, Timeout
and removing those again is taken care of.
As well this function is an example of using return true;
to skip clicking and Observing a Mutation entirely if the node to click was never found in the first place.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function clickTreeNode(itemId) {
return mop(function() {
let nodeToClick = document.querySelector(`a#Tree_Node_${itemId}.scContentTreeNodeNormal`);
if (!nodeToClick) {
document.getElementById('TreeSearch').value = itemId;
return true;
}
nodeToClick.click();
},
document.getElementById('ContentEditor'),
'#EditorTabs .scEditorHeaderVersionsLanguage',
{attributes:false, childList: true, subtree: true},
2000);
}
clickTreeNode()
and similar functions using mop()
is then used in the code like so:
1
2
3
4
5
6
7
8
9
10
11
search.expandTo
.split('!')
.map(id => `#Tree_Glyph_${id}[src*=treemenu_collapsed]`)
.map(itemId => () => expandTreeNode(itemId))
.reduce((prom, fn) => prom.then(fn), Promise.resolve())
.then(() => clickTreeNode(search.clickTo))
.then((nodes) => openLangMenu(search.langTo, !!nodes.length))
.then((nodes) => clickLang(search.langTo, !!nodes.length))
.then(() => scrollTree(search.scrollTreeTo))
.then(() => scrollPanel(search.scrollPanelTo))
.then(() => hideSpinner());