Package Exports
- trans-render
- trans-render/init.js
- trans-render/interpolate.js
- trans-render/repeatInit.js
- trans-render/update.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (trans-render) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
trans-render
trans-render provides an alternative way of instantiating a template. It draws inspiration from the (least) popular features of xslt. Like xslt, trans-render performs transforms on elements by matching tests on elements. Whereas xslt uses xpath for its tests, trans-render uses css patth tests via the element.matches() and element.querySelector() methods.
XSLT can take pure XML with no formatting instructions as its input. Generally speaking, the XML that XSLT acts on isn't a bunch of semantically meaningless div tags, but rather a nice semantic document, whose intrinsic structure is enough to go on, in order to formulate a "transform" that doesn't feel like a hack.
Likewise, with the advent of custom elements, the template markup will tend to be much more semantic, like XML. trans-render tries to rely as much as possible on this intrinisic semantic nature of the template markup, to give enough clues on how to fill in the needed "potholes" like textContent and property setting. But trans-render is completely extensible, so it can certainly accommodate custom markup (like string interpolation, or common binding attributes) by using additional, optional helper libraries.
This leaves the template markup quite pristine, but it does mean that the separation between the template and the binding instructions will tend to require looking in two places, rather than one.
Advantages
By keeping the binding separate, the same template can thus be used to bind with different object structures.
Providing the binding transform in JS form inside the init function signature has the advantage that one can benefit from TypeScript typing of Custom and Native DOM elements with no additional IDE support.
Another advantage of separating the binding like this, is that one can insert comments, console.log's and/or breakpoints, in order to walk through the binding process.
Workflow
trans-render provides helper functions for cloning a template, and then walking through the DOM, applying rules in document order. Note that the document can grow, as processing takes place (due, for example, to cloning sub templates). It's critical, therefore, that the processing occur in a logical order, and that order is down the document tree. That way it is fine to append nodes before continuing processing.
For each matching element, after modifying the node, you can instruct the processor to move to the next element sibling and/or the first child of the current one, where processing can continue. You can also "cut to the chase" by "drilling" inside based on querySelector, but there's no going back to previous elements once that's done. The syntax for the third option is shown below for the simplest example. If you select the drill option, that trumps instructing trans-render to process the first child.
It is deeply unfortunate that the DOM Query Api doesn't provide a convenience function for finding the next sibling that matches a query, similar to querySelector. Just saying. But some support for "cutting to the chase" laterally is planned [TODO].
At this point, only a synchronous workflow is provided.
Syntax:
<template id="test">
<detail>
<summary></summary>
</detail>
</template>
<div id="target"></div>
<script type="module">
import { init } from '../init.js';
const model = {
summaryText: 'hello'
}
const transform = {
detail: x => {
return {
drill: {
summary: ({ target }) => {
target.textContent = model.summaryText;
}
}
}
},
};
init(test, { transform }, target);
</script>Produces
<div id="target">
<detail>
<summary>hello</summary>
</detail>
</div>"target" is the HTML element we are populating.
By design, trans-render is loathe to do any unnessary work. As mentioned earlier, each transform can specify whether to proceed to the next sibling, thusly:
matchNextSib: true;And/or it can specify to match the first child:
matchFirstChild: true;And, as we've seen, you can drill down until the first matching element is found (via querySelector)
return {
drill: {
'myCssQuery':{
...
}
}
}
The first two match statements above can either be booleans, as illustrated above, or they can provide a new transform match:
transform: {
div: x => {
return {
matchNextSib: true,
matchFirstChild: {
'*': x => {
return {
matchNextSib: true
}
},
'[x-d]': ({ target}) => {
interpolate(target, 'textContent', model);
},
'[data-init]': ({ target, ctx }) => {
if (ctx.update !== undefined) {
return {
matchFirstChild: true
}
} else {
init(self[target.dataset.init], {
transform: ctx.transform
}, target);
}
},
}
}
},
}Use Case 1: Applying the DRY principle to (post) punk rock lyrics
Example 1a (only viewable at webcomponents.org )
Example 1b (only viewable at webcomponents.org )
Reapplying (some) of the transform
Often, we want to reapply a transform, after something changes -- typically the source data.
The ability to do this is illustrated in the previous example. Critical syntax shown below:
<script type="module">
import { init } from '../init.js';
import { interpolate } from '../interpolate.js';
import {update} from '../update.js';
const ctx = init(Main, {
model:{
Day1: 'Monday', Day2: 'Tuesday', Day3: 'Wednesday', Day4: 'Thursday', Day5: 'Friday',
Day6: 'Saturday', Day7: 'Sunday',
},
interpolate: interpolate,
$: id => window[id],
}, target);
changeDays.addEventListener('click', e=>{
ctx.model = {
Day1: 'måndag', Day2: 'tisdag', Day3: 'onsdag', Day4: 'torsdag', Day5: 'fredag',
Day6: 'lördag', Day7: 'söndag',
}
update(ctx, target);
})
</script>Loop support (NB: Not yet optimized.)
The next big use case for this library is using it in conjunction with a virtual scroller. As far as I can see, the performance of this library should work quite well in that scenario.
However, no self respecting rendering library would be complete without some internal support for repeating lists. This library is no exception. While the performance of the initial list is likely to be acceptable, no effort has yet been made to utilize state of the art tricks to make list updates keep the number of DOM changes at a minimum.
Anyway the syntax is shown below:
<div>
<template id="itemTemplate">
<li></li>
</template>
<template id="list">
<ul id="container"></ul>
</template>
<div id="target"></div>
<button id="addItems">Add items</button>
<script type="module">
import { init } from '../init.js';
import {update} from '../update.js';
import {repeatInit } from '../repeatInit.js';
import {repeatUpdate} from '../repeatUpdate.js';
const ctx = init(list, {
transform: {
'ul': ({target, ctx}) =>{
if(ctx.update !== undefined){
repeatInit(10, itemTemplate, target);
}
ctx.matchFirstChild = {
'li': ({target, ctx, idx}) =>{
target.textContent = 'Hello ' + idx;
ctx.matchNextSib = true;
}
}
}
}
}, target);
addItems.addEventListener('click', e =>{
repeatUpdate(15, itemTemplate, container);
update(ctx, target);
});
</script>
</div>