Advanced Custom Directives With TLX

Just like Vue, attribute directives in TLX can take “:” delimited arguments. This article shows how to create your own custom attribute directives that take arguments by implementing at-bind directive to do the same thing as v-bind.

Going beyond Vue’s capability, the article also illustrates how the arguments to the attribute directive can themselves be dynamic, i.e. you can do something like this: <div t-bind:${attributeName}="${value}">...</div>. And, even this, <div ${directiveName}:${argument}="${value}">...</div>.

Before continuing with this article, you may wish to read Custom Attribute Directives With TLX.

The v-bind directive in Vue simply takes the name of an attribute and sets its value to the value of the directive, e.g.

<a v-bind:href="url">My Server</a>

assuming url = “http://localhost"will render as

<a href="http://localhost">My Server</a>

To duplicate this functionality in tlx, add a custom directive that parses the directive itself for an attribute name and uses that in combination with the the value of the directive to set the attribute value.

The signature for attribute directive definitions functions in tlx is:

(resolvedAttributeValue,model,actions,renderFunction,directiveData)

The directiveData is an object with three properties, raw, resolved, and element. The raw property is the string that represents the directive name. The resolved property is usually the same string, except that directive names themselves can contain template strings, and in this property those template strings have been resolved (more on that later). The element is the DOM node being processed.

Below is the custom directive function that parses the directive and sets the attribute accordingly.

tlx.directives["t-bind"] =
(value,model,actions,render,{resolved,element}={}) => {
// resolved has the form t-bind:attributeName
const [_,attributeName] = resolved.split(":");
element.setAttribute(attributeName,value);
return true;
}

Note that t-bind does not bother to render the element, it just returns true. This is because it can rely on normal browser rendering to to display the element given that it has simply set a value on an attribute and unlike something like t-foris not doing anything special with nested content.

The tlx equivalent to Vue would then look as follows:

<a t-bind:href="${url}">My Server</a>

You can find a running example on JSFiddle.

Given that implementation of t-bind is so short and simple, you may wonder why it is not in tlx by default. There are two reasons:

  1. It keeps the tlx core as small as possible.
  2. It is not necessary since you can simply bind values directly to attributes, e.g. <a href=”${url}">My Server</a>

As mentioned in the introduction, tlx also supports dynamic arguments to attribute directives. So, if for some reason you are determining at runtime which attribute to bind, you could do this:

<div t-bind:${attributeName}="${attributeValue}">

If we assume a model that looks like this:

{attributeName:”checked",attributeValue:true}

Then, at runtime the value of the fourth argument to the directive definition function would have the content below:

{
element: {...},
raw: 't-bind:${attributeName}',
resolved: 't-bind:checked'
}

For reference, and a slightly more sophisticated example, here is the internal definition of t-for:

"t-for": (value,scope,actions,render,{raw,resolved,element}={}) => {
// directive is of the form "t-for:varname:looptype"
const [_,vname,looptype] = resolved.split(":");
if(looptype==="in") {
for(let key in value) {
render(Object.assign({},scope,{[vname]:key}))
}
} else if(looptype==="of") {
for(let item of value) {
render(Object.assign({},scope,{[vname]:item}))
}
} else {
throw
new TypeError(`loop type must be 'in' or 'of' for ${raw}`);
}
return element;
}

You can find tlx on GitHub.