TempLang

Web template framework. Bring event routing and element creation together quickly and completely.

view the code on github.

The Example Code

The Template

These are elements in the page inside a regular HTML element that does not display. Use the browser debugger to find them in this page.

    <div templ="color-menu">
        <h2>Set The Background Color!</h2>
        <menu vars="value=bg,items=colors" on:set="change-bg(color=value)" func:change-bg>
    </div>
<div templ="color-menu">
    <h2>Set The Background Color!</h2>
    <menu vars="value=bg,items=colors"
        on:set="change-bg(color=value)"
        func:change-bg>
</div>

Base Templates

    <div templ="menu" class="menu collapsed" on:init="style(collapsed)"
            on:set="unhover" on:hover="style(expanded/collapsed)" vars="value">
        <div class="menu-wrapper">
            <ul class="menu-item-container">
                <menu-item for="items"/>
            </ul>
        </div>
    </div>
    <li templ="menu-item" base-style="background-color: ${key}"
        class="menu-item" class-if="key=^value:active"
        on:click="^set(value=key)"
        vars="key,name"
    >${name}</li>
    <div templ="menu" class="menu collapsed"
            on:set="unhover"
            on:hover="style(expanded/collapsed)" vars="value">
        <div class="menu-wrapper">
            <ul class="menu-item-container">
                <menu-item for="items"/>
            </ul>
        </div>
    </div>
    <li templ="menu-item" base-style="background-color: ${key}"
        class="menu-item" class-if="key=^value:active"
        on:click="^set(value=key)"
        vars="key,name"
    >${name}</li>

JavaScript

    templang.funcs['change-bg'] = (event_ev) => {
        const color = event_ev.vars['color'];
        templang.ChangeStyle(null, "body", "background", color);
        templang.ChangeStyle('style.css', ".menu-item.active", "color", color);
    };

    const data = {bg: "#009", colors: [
        {name: "Red", key: "#B00"},
        {name: "Green", key: "#070"},
        {name: "Blue", key: "#009"},
        {name: "Brown", key: "#840"}
    ]};

    templang.Make("color-menu", root_el, data);
    templang.funcs['change-bg'] = (event_ev) => {
        const color = event_ev.vars['color'];
        templang.ChangeStyle(null,
            "body", "background", color);
        templang.ChangeStyle('style.css',
            ".menu-item.active", "color", color);
    };

    const data = {bg: "#009", colors: [
        {name: "Red", key: "#B00"},
        {name: "Green", key: "#070"},
        {name: "Blue", key: "#009"},
        {name: "Brown", key: "#840"}
    ]};

    templang.Make("color-menu", root_el, data);

Setup JavaScript

    const templang = {};
    const templates_el = document.getElementById('templates');
    TempLang_Init(templates_el, templang);
    const root_el = document.getElementById('main');
    const templang = {};
    const templates_el = document.getElementById('templates');
    TempLang_Init(templates_el, templang);
    const root_el = document.getElementById('main');

The Big Idea

Create a Domain Specific Langauge for selectors which wires up most of a front-ends behaviour and element population.

Similar to how shorthand was an abbreviated langauge for note-taking during speaches, this DSL is an abreviated version of the common tasks in front end development.

Here are the characters used in the selectors and what they represent.

Objectives

The objectives of the framework are as follows:

Ledger:

Under The Hood

There are four main workhorses:

Destination Keys/Specifications

options=@menu.main, map a property to a data souce.

    {
        // this is the end variable name 
        dest_key: "options",
        dest_direction: DIRECTION_SELF, 
        // this is inforatmion about how to retrieve it 
        key:  "main",
        key_props: Array ["menu", "main"],
        key_direction: DIRECTION_GLOBAL
    }
            

^#author.content query an element and get a property from it.

    {

        // in this case it will be the `content` variable
        dest_direction: DIRECTION_PARENT,
        dest_key: "content",
        // from a parent element of type `author` with a `content` var value
        key: "content",
        key_source: "author"
        source_type: TYPE_ELEM,
        key_direction: DIRECTION_SELF,
    }
            
^doStuff(value=key)specifies a call to a function on a parent element.
    {
        // Send the value of `key` as the property named `value` 
        // to a `doStuff` function on a parent element
        key: "doStuff"
        direction: DIRECTION_PARENT,
        mapVars: {
            value: {
                key: "key",
                dest_key: "value",
            }
        }
        type: TYPE_VAR,
    }
    
    {
        // this is the end variable name 
        dest_key: "options",
        dest_direction: DIRECTION_SELF, 
        // this is inforatmion about how to retrieve it 
        key:  "main",
        key_props: Array ["menu", "main"],
        key_direction: DIRECTION_GLOBAL
    }
    

^#author.content query an element and get a property from it.

    {

        // in this case it will be the `content` variable
        dest_direction: DIRECTION_PARENT,
        dest_key: "content",
        // from a parent element of type `author` with a `content` var value
        key: "content",
        key_source: "author"
        source_type: TYPE_ELEM,
        key_direction: DIRECTION_SELF,
    }
    
    {

        // in this case it will be the `content` variable
        dest_direction: DIRECTION_PARENT,
        dest_key: "content",
        // from a parent element of type `author` with a `content` var value
        key: "content",
        key_source: "author"
        source_type: TYPE_ELEM,
        key_direction: DIRECTION_SELF,
    }
    
^doStuff(value=key)specifies a call to a function on a parent element.
    {
        // Send the value of `key` as the property named `value` 
        // to a `doStuff` function on a parent element
        key: "doStuff"
        direction: DIRECTION_PARENT,
        mapVars: {
            value: {
                key: "key",
                dest_key: "value",
            }
        }
        type: TYPE_VAR,
    }
    
    {
        // Send the value of `key` as the property named `value` 
        // to a `doStuff` function on a parent element
        key: "doStuff"
        direction: DIRECTION_PARENT,
        mapVars: {
            value: {
                key: "key",
                dest_key: "value",
            }
        }
        type: TYPE_VAR,
    }
    

Template Parsing

Make a set of objects used for populated nested or iterative elements

Application template:

    <div class="main-stuff" on:update="doStuff(magicSauce=value)">
        <menu data="options=@menuOptions"></menu>
        <!-- more interace content here -->
    </div>
    
    <div class="main-stuff" on:update="doStuff(magicSauce=value)">
        <menu data="options=@menuOptions"></menu>
        <!-- more interace content here -->
    </div>
    

menu template:

    <div templ="menu" class="menu" var="options" on:set="update">
        <span class="menu-icon"> </span>
        <ul class="menu-item-container">
            <menu-item for="options"></menu-item>
        </ul>
    </div>
    
    <div templ="menu" class="menu" var="options" on:set="update">
        <span class="menu-icon"> </span>
        <ul class="menu-item-container">
            <menu-item for="options"></menu-item>
        </ul>
    </div>
    

menu-item template:

    <li templ="menu-item" class-if="key=^value:active" 
        vars="key" on:click="^set(value=key)">${name}</li>

    
    <li templ="menu-item" class-if="key=^value:active" 
        vars="key" on:click="^set(value=key)">${name}</li>

    

Element Population/Querying

Elements are populated with the El_Make function call which accets the name of the template and it's coresponding data.

All data access that is based on relationships between elements such as class-if and the as tag will bind listeners to parent elements. This makes them responsive to any variable changes that occur. For Exampe: as="^variant" will update the templ template type when the parents variant variable is changes.

Poly-Templates

poly-templates are templ attributes based on a cash variable:
        <div templ="record-album"><!-- template inner content here --></div>
        <div templ="record-song"><!-- template inner content here --></div>
        <div templ="record-genre"><!-- template inner content here --></div>
        <div templ="record-artist"><!-- template inner content here --></div>

        <div>
            <item for="records" data="records" as="record-${variant}"></item>
        </div>
    
        <div templ="record-album"><!-- template inner content here --></div>
        <div templ="record-song"><!-- template inner content here --></div>
        <div templ="record-genre"><!-- template inner content here --></div>
        <div templ="record-artist"><!-- template inner content here --></div>

        <div>
            <item for="records" data="records" as="record-${variant}"></item>
        </div>
    
Here is the JavaScript used to populate these elements:
        const records = [
            {variant: "album", artist: "God Smack", name: "Self Titled"},
            {variant: "song", artist: "God smack",  name: "Spiral"},
            {variant: "song", artist: "Taylor Swift", name: "Last Great American Dynasty"},
            {variant: "genre", name: "Acoustic Rock"},
            {variant: "genre", name: "Folk"},
            {variant: "genre", name: "Edm"},
            {variant: "artist", artist: "Skrillex"}
        ]

        templang.Injest(records);

        // make the records, each item will be created as "record-<variant>"
        // for example "record-album" or "record-genre"
        El_Make(content, {records: records});

    
        const records = [
            {variant: "album", artist: "God Smack", name: "Self Titled"},
            {variant: "song", artist: "God smack",  name: "Spiral"},
            {variant: "song", artist: "Taylor Swift", name: "Last Great American Dynasty"},
            {variant: "genre", name: "Acoustic Rock"},
            {variant: "genre", name: "Folk"},
            {variant: "genre", name: "Edm"},
            {variant: "artist", artist: "Skrillex"}
        ]

        templang.Injest(records);

        // make the records, each item will be created as "record-<variant>"
        // for example "record-album" or "record-genre"
        El_Make(content, {records: records});

    

Attributes Declared Above

An Example Selector

    ^#drawing.update(newColor=key);close
    ^#drawing.update(newColor=key);close

This is the breakdown of what this is does:

^#drawing This is finding the drawing element which is a parent.

update is the handler that will be called, assigned with on:update=, as an attribute on the drawing tag.

newColor=key newColor is the property name that the update function will recieve, for the menu elements key value.

close runs as the next function, this serves to close the color menu.

Crazy right?. It puts all the common front end things in one place. This is good for performance, both runtime and developer velocity, as long as it remains understandable... We have a lot of work to do.

 

Here is an exmaple usage of this selector inside an element's attribute.

    <menu on:click="^#drawing.update(newColor=key);close">
        <menu-item for="^#colors.options"> vars="key=rgb"
    </div>
    <menu on:click="^#drawing.update(newColor=key);close">
        <menu-item for="^#colors.options"
             vars="key=rgb">
    </div>

 

The Case for Variables on Elements

It's a good idea to keep state in as few places as possible right?

Not always... locality is the best chance a user-interface has for making sense during development. This is not a casual idea, it's because immutability is a loosing battle when there are a lot of user events involved.

Localized ownership of internal state is a more scalable solution, and it leads to a "what you see is what you get" authoring process, which is more discussable, and thereby maintainable.

Status: Experimental

TempLang is presently being used by Compare Basic who have authored it, to build web applications, starting with Email2Site. We are not yet using it in production, and recommend that everyone else hold off until we can properly test and launch a few products with it.