TILEPIECES TEMPLATE (TT)

The constructor window.TT is an HTML template processor written in javascript that implements two-way data binding.

parameters

element
    A DOM element. The content of the element is will be processed
model
    A javascript object that will serve as a model for our template
options
    Here you can set statements names, interpolation regex and templates

Return value

a tt object. The most important method is set

Basic usage

    <div id=example>${data}</div>
    <script>
    new TT(document.getElementById("example"),{data:"some data"},{ // here the default values
        interpolation : /\$\{([\s\S]+?)\}/,
        ifAttr : "if",
        foreachAttr : "foreach",
        foreachKeyNameAttr : "foreachKeyName",
        bindAttr : "bind",
        bindDOMPropAttr : "bindDomProp",
        bindEventsAttr : "bindEvents",
        setTemplateAttr : "set",
        useTemplateAttr : "use",
        useTemplateParamsAttr : "params",
        isolateAttribute : "isolate",
        srcAttribute : "src"
    });
    </script>

Interpolation

Interpolation allows to define node texts and attributes values and names ( when setting attributes names, remember that it will changed to lowercase ).

<div id="example">
  <a href="${URL}" ${hidden}>${text}</a>
</div>
<button onclick="changeHidden()">Change hidden value</button>
<script>
  let model = {
    URL : "https://github.com/tilepieces",
    text : "some text",
    hidden : "hidden"
  };
  let template = new TT(document.getElementById("example"),model);
  function changeHidden(){
    template.set("hidden",model.hidden ? "" : "hidden");
  }
</script>

See the live example

Statements

Statements are defined by data attributes

data-if

tells the parser to render or not ( the dom element will be removed ) a tag.
The attribute value is a variable that will be coerced to a boolean.

<div id=example>
  <div data-if="show">This will be shown</div>
  <div data-if="hide">This will be hidden</div>
</div>
<button id="toggle-visibility">Toggle visibility</button>
<script src="../tt.js" data-tilepieces-component="tt"></script>
<script>
  let scope = {
      show:true,
    hide:false
  };
  let template = new TT(document.getElementById("example"),scope);
  document.getElementById("toggle-visibility").onclick = ()=>{
    scope.show = !scope.show;
    scope.hide = !scope.hide;
    template.set("",scope);
  };
</script>

See the live example

data-foreach

Tells the parser to render a tag n-times according to an array of objects.
You can retrieve object property using key or setting the data-foreach-key-name attribute in the same tag ( best for nested arrays ).

<ul>
  <li data-foreach="array1">
    <div>Name <span>${key.name}</span></div>
    <div>Value <span>${key.value}</span></div>
  </li>
</ul>
<ul>
  <li data-foreach="array2" data-foreach-key-name="prop">
    <div>Name <span>${prop.name}</span></div>
    <div>Value <span>${prop.value}</span></div>
    <ul>
    <li data-foreach="prop.array" data-foreach-key-name="propNested">
    <div>Name <span>${propNested.name}</span></div>
    <div>Value <span>${propNested.value}</span></div>
  </li>
</ul>
</li>
</ul>
<script>
let array1 = [{
  name: "array1_1",
  value: 10
},
{
  name: "array1_2",
  value: 100
}];
let array2 = [{
  name: "array2_1",
  value: 0,
  array : [{
    name: "array2_1_0_nested",
    value: 0
  }]
},
{
  name: "array2_2",
  value: 10,
  array : [{
    name: "array2_2_0_nested",
    value: 1
  },{
    name: "array2_2_0_nested",
    value: 2
  }]
}]
let scope = {array1, array2};
let template = new TT(document.body,scope);
</script>

See the live example

data-bind and data-bind-events

Allows you to link the data model to user changes through the input, select, textarea tags and with the contenteditable attribute. By default it is linked with the change event, but you can override this behavior by defining the data-bind-events tag (which accepts multiple values separated by a comma).

<section>
    <input placeholder="name"
           data-bind="user.name"
           data-bind-events="input">
    <input placeholder="surname"
           data-bind="user.surname"
           data-bind-events="input">
    <fieldset>
        Male<input type="radio" data-bind="user.sex" name="user.sex" value="M"><br>
        Female<input type="radio" data-bind="user.sex" name="user.sex" value="F">
    </fieldset>
    <fieldset>
        <input type="checkbox" data-bind="user.employed">
    </fieldset>
    <fieldset data-if="user.employed">
        <select data-bind="user.job">
            <option value="developer">developer</option>
            <option value="designer">designer</option>
            <option value="manager">manager</option>
            <option value="hr">hr</option>
            <option value="other">other</option>
        </select>
    </fieldset>
    <h1 data-if="!user.name && !user.surname">Please fill inputs</h1>
    <h1 data-if="user.name || user.surname">
        Hello ${user.name} ${user.surname}!</h1>
    <pre>
    </pre>
</section>
<script>
    let scope = {
        user  : {
            name : "",
            surname : "",
            sex:"M",
            employed : true,
            job : "developer"
        }
    };
    let section = document.querySelector("section");
    let template = new TT(section,scope);
    let pre = document.querySelector("pre");
    section.addEventListener("user.name",e=>{
        console.log("user.name detail ->", e.detail)
    })
    section.addEventListener("user.surname",e=>{
        console.log("user.surname detail ->", e.detail)
    })
    section.addEventListener("template-digest",e=>{
        console.log("template-digest ->", e.detail)
        pre.innerHTML = JSON.stringify(scope,null,4);
    });
</script>

See the live example

data-bind-dom-prop

Attach a variable as a property of a DOM element

<div onclick="alertData(this)" data-bind-dom-prop="__data,data" style="cursor:pointer">Click here for an alert with the data value</div>
<input data-bind="data" placeholder="change data here">
<script>
  var scope = {data:""};
  var template = new TT(document.body,scope);
  function alertData(el) {
    console.log(el);
    alert(el.__data);
  };
</script>

See the live example

data-set, data-use and data-params

Bind an HTML template ( that could be defined through data-set or in the options values ) to a tag.
data-params is used pass variables using another name.
Example using data-set ( See the live example )

<section>
    <div data-set="test-template">
        <span>${obj.name}</span>:
        <span>${obj.value}</span>
    </div>
    <ul>
        <li data-foreach="data"
            data-use="test-template"
            data-params="obj,key"></li>
    </ul>
</section>
<script>
    let scope = {
        data : [{
            name : "name1",
            value : "value1"
        },{
            name : "name2",
            value : "value2"
        }]
    }
    let el = document.body.children[0];
    let t = new TT(el,scope);
</script>

Example using the template tag ( See the live example )

<section>
    <ul>
        <li data-foreach="data"
            data-use="test-template"
            data-params="obj,key"></li>
    </ul>
</section>
<template id="test-template">
    <span>${obj.name}</span>:
    <span>${obj.value}</span>
</template>
<script>
    let scope = {
        data : [{
            name : "name1",
            value : "value1"
        },{
            name : "name2",
            value : "value2"
        },{
            name : "name3",
            value : "value3"
        }]
    };
    let template = document.getElementById("test-template");
    let el = document.body.children[0];
    let t = new TT(el,scope,{
        templates : [{
            name : "test-template",
            el : template.content
        }]
    });
</script>

data-src

Used in frame and img tags to dynamically set them without letting the browser download template placeholders
( See the live example )

data-isolate

Isolate part of the HTML. This part will not be processed.

<section>
   <div>
       <span>${name}</span>:
       <span>${value}</span>
   </div>
   <div data-isolate>
       <div>This element is isolated:</div>
       <span>${name}</span>:
       <span>${value}</span>
   </div>
</section>
<script>
   let scope = {
       name : "an",
       value : "example"
   }
   let el = document.body.children[0];
   let t = new TT(el,scope);
</script>

See the live example

set

The set method allows to update the model. You can pass the property to be changed as a string (the original object will also be updated), but if you need to make more than one change it is better to pass the whole object.

<section>
    ${name}<br>
    ${nestedExample.name}
    <ul>
        <li data-foreach="array">
            ${key.name}
        </li>
    </ul>
</section>
<script>
    let scope = {
        name : "an example",
        nestedExample : {
            name : "an example2"
        },
        array : [
            {name : "an example3"}
        ]
    }
    let el = document.body.children[0];
    let t = new TT(el,scope);
    let o = "an example changed";
    t.set("name",o);
    console.assert(scope.name == o);
    let o2 = "an example2 changed";
    t.set("nestedExample.name",o2);
    console.assert(scope.nestedExample.name == o2);
    let o3 = "an example3 changed";
    t.set("array[0].name",o3);
    console.assert(scope.array[0].name == o3);
</script>

See the live example