Extend the element widget with a hook to intercept DOM node creation

The element widget is used to render HTML elements in wikitext.
optimising-macrocalls
jeremy@jermolene.com 2020-07-02 13:13:55 +01:00
rodzic 35a842ade6
commit bd2cf5c464
2 zmienionych plików z 63 dodań i 21 usunięć

Wyświetl plik

@ -29,45 +29,47 @@ Render this widget into the DOM
ElementWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
// Neuter blacklisted elements
var tag = this.parseTreeNode.tag;
if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) {
tag = "safe-" + tag;
this.tag = this.parseTreeNode.tag;
if($tw.config.htmlUnsafeElements.indexOf(this.tag) !== -1) {
this.tag = "safe-" + this.tag;
}
// Adjust headings by the current base level
var headingLevel = ["h1","h2","h3","h4","h5","h6"].indexOf(tag);
var headingLevel = ["h1","h2","h3","h4","h5","h6"].indexOf(this.tag);
if(headingLevel !== -1) {
var baseLevel = parseInt(this.getVariable("tv-adjust-heading-level","0"),10) || 0;
headingLevel = Math.min(Math.max(headingLevel + 1 + baseLevel,1),6);
tag = "h" + headingLevel;
this.tag = "h" + headingLevel;
}
// Create the DOM node
var domNode = this.document.createElementNS(this.namespace,tag);
this.assignAttributes(domNode,{excludeEventAttributes: true});
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
/*
Compute the internal state of the widget
*/
ElementWidget.prototype.execute = function() {
// Select the namespace for the tag
var tagNamespaces = {
svg: "http://www.w3.org/2000/svg",
math: "http://www.w3.org/1998/Math/MathML",
body: "http://www.w3.org/1999/xhtml"
};
this.namespace = tagNamespaces[this.parseTreeNode.tag];
this.namespace = tagNamespaces[this.tag];
if(this.namespace) {
this.setVariable("namespace",this.namespace);
} else {
this.namespace = this.getVariable("namespace",{defaultValue: "http://www.w3.org/1999/xhtml"});
}
// Invoke the th-rendering-element hook
var parseTreeNodes = $tw.hooks.invokeHook("th-rendering-element",null,this);
this.isReplaced = !!parseTreeNodes;
if(parseTreeNodes) {
// Use the parse tree nodes provided by the hook
this.makeChildWidgets(parseTreeNodes);
this.renderChildren(this.parentDomNode,null);
return;
}
// Make the child widgets
this.makeChildWidgets();
// Create the DOM node and render children
var domNode = this.document.createElementNS(this.namespace,this.tag);
this.assignAttributes(domNode,{excludeEventAttributes: true});
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
};
/*
@ -77,8 +79,13 @@ ElementWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(),
hasChangedAttributes = $tw.utils.count(changedAttributes) > 0;
if(hasChangedAttributes) {
// Update our attributes
this.assignAttributes(this.domNodes[0],{excludeEventAttributes: true});
if(!this.isReplaced) {
// Update our attributes
this.assignAttributes(this.domNodes[0],{excludeEventAttributes: true});
} else {
// If we were replaced then completely refresh ourselves
return this.refreshSelf();
}
}
return this.refreshChildren(changedTiddlers) || hasChangedAttributes;
};

Wyświetl plik

@ -0,0 +1,35 @@
created: 20200630121235997
modified: 20200630121235997
tags: HookMechanism
title: Hook: th-rendering-element
This hook provides a notification that a DOM element is about to be rendered by the "element" widget. The hook can optionally provide an alternate parse tree that will be rendered in place of the intended element.
Note the element widget only renders those HTML elements that were parsed as plain HTML elements within wikitext (i.e. using the `<tagname>` syntax). This means that this hook is not invoked for elements created by other widgets.
Hook function parameters:
* ''newParseTreeNodes'': optional parse tree nodes provided by a previously called hook
* ''widget'': instance of the element widget invoking the hook
Return value:
* ''newParseTreeNodes'': optionally new parse tree nodes to replace the intended element, or a falsey value to leave the element untouched
Here is an example of a handler for this hook:
```js
$tw.hooks.addHook("th-rendering-element",function(parseTreeNodes,widget) {
// Return the previous mapping if there is one
if(parseTreeNodes) {
return parseTreeNodes;
}
// Detect the elements we're interested in
if(someCondition()) {
// Replace them with a parse tree
return generateParseTreeNodes();
}
// Otherwise do nothing
return null;
});
```