Skip to content
This repository has been archived by the owner on Feb 20, 2019. It is now read-only.

Templating Syntax

dzearing edited this page Oct 13, 2014 · 19 revisions

Summary

Templates allow you to define your view markup, data bindings, and event handlers declaratively. A declarative approach means that by default, they are written in an easily parseable, easily translatable format. As technology improves, the templates can be translated into new formats in the future if necessary.

Defining a view

Views should each be defined in their own directory under your project's src directory. So to make a new view, make a new directory under src that is TitleCased, indicating the class name of your view. For example, ToggleButton.

A view file is simply an html file that gets parsed and translated into a TypeScript class. The root element must have a js-type attribute, which defines the classname.

src/ToggleButton/ToggleButton.html:

<div js-type="ToggleButton"></div>

Binding to states

State data is sent to the view as a json object containing name/value pairs that will be mixed into the view's viewModel. For example:

var toggleButton = new ToggleButton();

toggleButton.setData({ isToggled: true });
console.alert(toggleButton.viewModel.isToggled); // alerts 'true'

In your template, you can use the js-bind attribute to indicate a binding of the viewModel state to the effect it has on the view. In the following example, I bind the text value of the button to the viewModel property "name", and the className "toggled" to the viewModel property "isToggled".

<button
    js-type="ToggleButton"
    js-bind="text:name, className.toggled:isToggled">
</button>

js-bind attribute options

A js-bind statement by default binds to the view's viewModel scope.

Here is an example which binds an anchor's text to the "name" property, the "href" attribute to the "url" property, and toggles the "isEnabled" class name if the "isEnabled" property is truthy:

<a js-bind="text:name, alt:altText, href:url, className.isEnabled:isEnabled"/>

The format of the js-bind syntax is:

[destinationType][.destinationSubType]:[sourcePath]<,[...]>

Where destinationType can be:

  • text - binds the text value of the element to the source value.

  • html - binds the html value of the element to the source value. Careful, this is not encoded.

  • attr - binds an attribute (provided as the subType) to the source value. This is the default binding type if no destinationType is provided. (e.g. binding "href:foo" will bind the href attribute to foo.)

  • className - toggles a given class name (provided as the subType) based on the source value's truthiness. (e.g. binding "className.foo:bar" will enable the class "foo" on the element if the source "bar" resolves as truthy.

The sourcePath defaults to the viewModel scope.

You can use a dot notation to drill into a specific value within the viewModel:

<div js-bind="text:item.name"></div>

If you want to bind to child view scopes, you can use the $ syntax, which indicates to "start at the view to resolve the scope" rather than the viewModel.

For example, you can bind to a child view's "isToggled" viewModel state:

<div js-type="MyView" js-bind="className.isHighlighted:$toggleButton.isToggled">
    <toggle-button js-name="toggleButton" />
</div>

Binding sources and converters

Often, a source needs to be massaged to make it "fit" into the experience. For example, you may want logic that multiplies a value by 3, or upper cases a value, or makes a date string friendly. For these scenarios you can bind directly to functions and you can pass the resolves values of existing properties into them.

For example, in my viewModel, I may have a toUpper helper that I want to wrap some of my binding sources within:

src/Foo/FooModel.ts:

class FooModel extends ViewModel {
    name = "david";

    toUpper(val) {
        return val ? val.toUpperCase(): '';
    }
}

src/Foo/Foo.html:

<div js-type="Foo" js-bind="text:toUpper(name)"></div>

Renders this markup:

<div>David</div>

Handling events with the js-event attribute

General DOM events can be mapped to functions either defined on the viewModel (default) or the actual view implementation when you want to hide view logic from the viewModel implementation.

Here's an example of listening to a click event on a button, which binds to the _onClick handler:

<button js-event="click:_onClick">click me</button>

When the view is "activated", the event will automatically be observed and will call the function.

Common event helpers

Repeat blocks

TBD

Conditionals

TBD

Nesting views

Clone this wiki locally