Web template framework. Bring event routing and element creation together quickly and completely.
view the code on github.
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>
<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>
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);
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');
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.
The objectives of the framework are as follows:
Attributes such as children, or children-data indicate what data to use when populating the interface. Any attribute or element body which contains a variable is pulled from the elements data, such as ${name}.
Prefix with an underscore _ for children, or carret ^ for parents so that events can be quickly routed and handled. Elements will recieve a seperate target or destination property for each event giving them access to what the user-interface needs.
Any element that has a state which is always tied to it's existance can send that data along when an event is triggered from it.
Outside of an element the data is often used under a different name, such as myFancySetting=key, for a value of myFancySetting represented by an elements key.
Such as drag-and-drop, drop-down, navigation or selection behaviour.
There are four main workhorses:
Change a simple key definition into an understandable set of directives.
Make a set of objects used for populated nested or iterative elements.
Elements are populated based on the data provided, and can be queried by tagname or their present var data.
Events are dispatched through a series of listeners that are automatically created based on the template variables/event handler definitions.
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, }
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>
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.
<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});
^#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>
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.
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.