DoneJS StealJS jQuery++ FuncUnit DocumentJS
5.33.3
6.0.0 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-compat
      • can-map-define
      • can-observable-array
      • can-observable-object
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Views
      • can-attribute-observable
      • can-component
      • can-stache
        • Tags
          • {{expression}}
          • {{{expression}}}
          • {{#expression}}
          • {{/expression}}
          • {{else}}
          • {{<partialName}}
          • {{!expression}}
          • {{-expression-}}
        • Helpers
          • and
          • console
          • debugger
          • domData
          • eq
          • for(of)
          • portal
          • if
          • joinBase
          • let
          • not
          • or
          • switch
          • case
          • default
        • Expressions
          • Bracket Expression
          • Call Expression
          • Hash Expression
          • KeyLookup Expression
          • Literal Expression
        • Methods
          • addBindings
          • addConverter
          • addHelper
          • addLiveHelper
          • from
          • safeString
        • Key Operators
          • ~compute
          • ./current
          • ../parent
          • scope
          • scope/key
          • this
          • key
        • Pages
          • Expressions
          • Helpers
        • Types
          • getterSetter
          • helper
          • helperOptions
          • sectionRenderer
          • simpleHelper
          • view
        • Deprecated
          • Helper Expression
          • scope.vars
          • {{data name}}
          • {{#each(expression)}}
          • {{#is(expressions)}}
          • {{#unless(expression)}}
          • {{#with(expression)}}
          • registerConverter
          • registerHelper
          • registerPartial
          • Legacy Scope Behavior
          • {{^expression}}
          • {{>key}}
      • can-stache-bindings
      • can-stache-converters
      • can-stache-element
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • steal-stache
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-connect-ndjson
      • can-connect-tag
      • can-fixture
      • can-fixture-socket
      • can-local-store
      • can-memory-store
      • can-ndjson-stream
      • can-query-logic
      • can-realtime-rest-model
      • can-rest-model
      • can-set-legacy
      • can-super-model
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-type
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-stache

  • npm package badge
  • Star
  • Edit on GitHub

Live binding templates.

stache([name,] template)

Processes the template string and returns a view function that can be used to create HTML elements with data.

import {stache} from "can";

// parses the template string and returns a view function:
const view = stache(`<h1>Hello {{this.subject}}</h1>`);

// Calling the view function returns HTML elements:
const documentFragment = view({subject: "World"});

// Adds those elements to the page
document.body.appendChild( documentFragment );

console.log(document.body.innerHTML) //-> "<h1>Hello World</h1>";

stache is most commonly used by can-component to define a component's view:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
  tag: "my-demo",
  view: `<h1>Hello {{this.subject}}</h1>`,
  ViewModel: {
    subject: {default: "World"}
  }
});
</script>

Use steal-stache to import template view functions with StealJS.

Use can-stache-loader to import template view functions with webpack.

Parameters

  1. name {String}:

    Provides an optional name for this type that will show up nicely in errors. Files imported with steal-stache will use their filename.

  2. template {String}:

    The text of a stache template.

Returns

{view(data, helpers, nodeList)}:

A view function that returns a live document fragment that can be inserted in the page.

Purpose

Stache templates are used to:

  • Convert data into HTML.
  • Update the HTML when observable data changes.
  • Enable custom elements and event and data bindings.

Stache is designed to be:

  • Safe. It does not use eval in any form of its use.
  • Easy for beginners to understand - It looks a lot like JavaScript.
    {{# for( item of this.items ) }}
       <li>
         <span>{{ item.name }}</span>
         <label>{{ this.getLabelFor(item) }}</label>
       </li>
    {{/ }}
    
  • Limited - Complex logic should be done in the ViewModel where it is more easily tested. Stache only supports a subset of JavaScript expressions.
  • Powerful (where you want it) - Stache adds a few things JavaScript doesn't support but are very useful for views:
    • Stache tolerates undefined property values - The following will not error. Instead stache will simply warn:
      {{this.property.does.not.exist}}
      
    • Stache is able to read from promises and other observables directly:
      {{# if(promise.isPending) }} Pending {{/ if }}
      {{# if(promise.isRejected) }}
        {{ promise.reason.message }}
      {{/ if }}
      {{# if(promise.isResolved) }}
        {{ promise.value.message }}
      {{/ if}}
      
    • Stache has an {{else}} case for empty lists:
      {{# for( item of this.items ) }}
         <li>{{ item.name }}</li>
      {{ else }}
         <li>There are no items</li>
      {{/ }}
      

Basic Use

The following sections show you how to:

  • Load templates so they be processed into views.
  • Writing values within HTML to the page.
  • Writing some HTML to the page or some other HTML to the page with branch logic.
  • Loop over a list of values and writing some HTML out for each value.
  • Listen to events on elements.
  • Read and write to element properties and attributes.
  • Simplifying your templates with:
    • Variables
    • Helpers
    • Partials

Loading templates

There are several ways to load a stache template:

  • As a component's view.

    can-component automatically processes strings passed to the view property as can-stache templates.

    <my-demo></my-demo>
    <script type="module">
    import {Component} from "can";
    
    Component.extend({
      tag: "my-demo",
      view: `<h1>Hello {{ this.subject }}</h1>`,
      ViewModel: {
        subject: {default: "World"}
      }
    });
    </script>
    
  • Programmatically.

    Create a view function by importing stache and passing it a string.

    import {stache} from "can";
    
    // parses the template string and returns a view function:
    const view = stache(`<h1>Hello {{ this.subject }}</h1>`);
    
    // Calling the view function returns HTML elements:
    const documentFragment = view({subject: "World"});
    
    // Adds those elements to the page
    document.body.appendChild( documentFragment );
    
    console.log(document.body.innerHTML) //-> "<h1>Hello World</h1>";
    
  • Imported and pre-parsed.

    If you are using StealJS use steal-stache or if you are using webpack use can-stache-loader to create .stache file and import them like:

    import {Component} from "can";
    import view from "./my-component.stache";
    
    Component.extend({
      tag: "my-component"
      view,
      ViewModel: { ... }
    });
    

Writing values

Use {{expression}} to write out values into the page. The following uses {{expression}} to write out the ViewModel's subject:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `<h1>Hello {{ this.subject }}</h1>`,
    ViewModel: {
        subject: {default: "World"}
    }
});
</script>

You can use {{expression}} on any part of an HTML element except the tag name:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <h1 class='{{this.className}}' {{this.otherAttributes}}>
            Hello {{ this.subject }}
        </h1>`,
    ViewModel: {
        subject: {default: "World"},
        className: {default: "bigger"},
        otherAttributes: {default: "id='123'"}
    }
});
</script>

You can call methods within {{expression}} too:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `<h1>Hello {{ this.caps( this.subject ) }}</h1>`,
    ViewModel: {
        subject: {default: "World"},
        caps( text ){
            return text.toUpperCase();
        }
    }
});
</script>

{{expression}} will escape the value being inserted into the page. This is critical to avoiding cross-site scripting attacks. However, if you have HTML to insert and you know it is safe, you can use {{{expression}}} to insert it.

Branching Logic

Stache provides severals helpers that help render logic conditionally. For example, the following renders a sun if the time property equals "day":

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <p on:click="this.toggle()">
            Time:
            {{# eq(this.time,"day") }}
                SUN 🌞
            {{ else }}
                MOON 🌚
            {{/ eq }}
        </p>
    `,
    ViewModel: {
        time: {default: "day"},
        toggle(){
            this.time = (this.time === "day" ? "night" : "day");
        }
    }
});
</script>

Notice that branching is performed using the {{#expression}}, {{else}} and {{/expression}} magic tags. These define "sections" of content to render depending on what the helper does. We call these the TRUTHY and FALSY sections. In the example above, the eq helper renders the TRUTHY section (SUN 🌞) if this.time equals "day". If this.time is not equal to "day", the FALSY section (MOON 🌚) is rendered.

The following helpers are used to render conditionally:

  • if - Renders the TRUTHY section if the value is truthy.
    EXAMPLE
    
  • not - Renders the TRUTHY section if the value is falsy.
  • eq - Renders the TRUTHY section all values are equal.
  • and - Renders the TRUTHY section if all values are truthy.
  • or - Renders the TRUTHY section if any value is truthy.
  • switch with case - Renders the case section that matches the value.
  • {{else}} - Renders the FALSY section if the value is falsy.

These helpers (except for switch) can be combined. For example, we can show the sun if this.time equals "day" or "afternoon" as follows:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <p on:click="this.toggle()">
            Time:
            {{# or( eq(this.time,"day"), eq(this.time, "afternoon") ) }}
                SUN 🌞
            {{ else }}
                MOON 🌚
            {{/ eq }}
        </p>
    `,
    ViewModel: {
        time: {default: "day"},
        toggle(){
            this.time = (this.time === "day" ? "night" :
                (this.time === "night" ? "afternoon" : "day"));
        }
    }
});
</script>

NOTE: One of stache's goals is to keep your templates as simple as possible. It might be better to create a isSunUp method in the ViewModel and use that instead.

Looping

Use for(of) to loop through values. The following writes out the name of each todo:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <ul>
            {{# for(todo of this.todos) }}
                <li>{{ todo.name }}</li>
            {{/ for }}
        </ul>
    `,
    ViewModel: {
        todos: {
            default(){
                return [
                    {name: "Writing"},
                    {name: "Branching"},
                    {name: "Looping"}
                ]
            }
        }
    }
});
</script>

Use scope.index to access the index of a value in the array. The following writes out the index with each todo's name:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <ul>
            {{# for(todo of this.todos) }}
                <li>{{scope.index}} {{ todo.name }}</li>
            {{/ for }}
        </ul>
    `,
    ViewModel: {
        todos: {
            default(){
                return [
                    {name: "Writing"},
                    {name: "Branching"},
                    {name: "Looping"}
                ]
            }
        }
    }
});
</script>

Use for(of) to loop through key-value objects.

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <ul>
            {{# for(value of this.object) }}
                <li>{{scope.key}} {{ value }}</li>
            {{/ for }}
        </ul>
    `,
    ViewModel: {
        object: {
            default(){
                return {
                    first: "FIRST",
                    value: "VALUE"
                };
            }
        }
    }
});
</script>

Listening to events

on:event documents how you can listen to events on elements or ViewModels. The following listens to clicks on a button:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <button on:click="this.increment()">+1</button>
        Count: {{this.count}}
    `,
    ViewModel: {
        count: {default: 0},
        increment(){
            this.count++;
        }
    }
});
</script>

Binding to properties and attributes

can-stache-bindings provides directional bindings to connect values in stache to element or ViewModel properties or attributes.

This makes it easy to:

  • Write out property values.

    The following updates the checkboxes checked property if the status is not equal to 'critical':

    <my-demo></my-demo>
    <script type="module">
    import {Component} from "can";
    
    Component.extend({
      tag: "my-demo",
      view: `
          <input type="checkbox"
              checked:from="not( eq(this.status, 'critical') )" />
              Can ignore?
    
          <button on:click="this.status = 'critical'">Critical</button>
          <button on:click="this.status = 'medium'">Medium</button>
          <button on:click="this.status = 'low'">Low</button>
      `,
      ViewModel: {
          status: {default: "low"}
      }
    });
    </script>
    
  • Update a value when an element property changes.

    The following updates the ViewModel's name when the <input/> changes:

    <my-demo></my-demo>
    <script type="module">
    import {Component} from "can";
    
    Component.extend({
      tag: "my-demo",
      view: `
          <input value:to="this.name" placeholder="name"/>
          Name: {{ this.name }}
      `,
      ViewModel: {
          name: {default: ""}
      }
    });
    </script>
    

can-stache-bindings supports a wide variety of different bindings. Please checkout its documentation.

Creating variables

The let helper lets you create local variables. For example, we can create a name variable and write to that:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        {{ let name='' }}
        <input value:to="name" placeholder="name"/>
        Name: {{ name }}
    `
});
</script>

Variables can help you avoid unnecessary ViewModel properties like above. This is very handy when wiring Components within a for(of) loop as follows:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        {{# for(todo of this.todos) }}
            {{ let locked=true }}
            <div>
                <p>
                    Locked:
                    <input type='checkbox' checked:bind="locked"/>
                </p>
                <p>
                    <input type='value' value:bind="todo.name" disabled:from="locked"/>
                </p>
            </div>
        {{/ for }}
    `,
    ViewModel: {
        todos: {
            default(){
                return [
                    {name: "Writing"},
                    {name: "Branching"},
                    {name: "Looping"}
                ];
            }
        }
    }
});
</script>

Currently, you can only create variables with let for the entire template or within for(of). If there are other blocks where you would find this useful, please let us know!

Creating helpers

Helpers can simplify your stache code. While CanJS comes with many helpers, adding your own can reduce code. There are several different types of helpers, each with different benefits.

Global Helpers

Use addHelper to create a helper function that can be called from every template. The following makes an upperCase helper:

<my-demo></my-demo>
<script type="module">
import {stache, Component} from "can";

stache.addHelper("upperCase", function(value){
    return value.toUpperCase();
})

Component.extend({
    tag: "my-demo",
    view: `
        <h1>Hello {{ upperCase(this.subject) }}</h1>
    `,
    ViewModel: {
        subject: {default: "World"}
    }
});
</script>

Global helpers are easy to create and understand, but they might create conflicts if another CanJS library defines a similar helper.

Component Methods

Instead of creating a global helper, add your helper functions on your component ViewModel. The following adds the upperCase method to the ViewModel.

<my-demo></my-demo>
<script type="module">
import {stache, Component} from "can";

function upperCase(value){
    return value.toUpperCase();
}

Component.extend({
    tag: "my-demo",
    view: `
        <h1>Hello {{ this.upperCase(this.subject) }}</h1>
    `,
    ViewModel: {
        subject: {default: "World"},

        // View Helpers
        upperCase: upperCase
    }
});
</script>

Importing Functions (older method)

If you are using a module loader to import stache files, can-view-import can be used to import a function to a scope.vars variable:

<can-import from="app/helpers/upperCase"  module.default:to="scope.vars.myModule"/>

A replacement for this technique is being designed here.

Creating partials

Partials are snippets of HTML that might be used several places. There are a few ways of reusing HTML.

Using Components

You can always define and use can-component. The following defines and uses an <address-view> component:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "address-view",
    view: `
        <address>{{this.street}}, {{this.city}}</address>
    `
});

Component.extend({
    tag: "my-demo",
    view: `
        <h2>{{this.user1.name}}</h2>
        <address-view street:from="user1.street" city:from="user1.city"/>
        <h2>{{this.user2.name}}</h2>
        <address-view street:from="user2.street" city:from="user2.city"/>
    `,
    ViewModel: {
        user1: {
            default(){
                return {name: "Ramiya", street: "Stave", city: "Chicago"}
            }
        },
        user2: {
            default(){
                return {name: "Bohdi", street: "State", city: "Chi-city"}
            }
        }
    }
});
</script>

Calling views

You can create views programmatically with stache, make those views available to another view (typically through the ViewModel). The following creates an addressView and makes it available to <my-demo>'s view through the addressView property on the ViewModel:

<my-demo></my-demo>
<script type="module">
import {stache, Component} from "can";

const addressView = stache(`<address>{{this.street}}, {{this.city}}</address>`);

Component.extend({
    tag: "my-demo",
    view: `
        <h2>{{this.user1.name}}</h2>
        {{ addressView(street=user1.street city=user1.city) }}
        <h2>{{this.user2.name}}</h2>
        {{ addressView(street=user2.street city=user2.city) }}
    `,
    ViewModel: {
        addressView: {
            default(){
                return addressView;
            }
        },
        user1: {
            default(){
                return {name: "Ramiya", street: "Stave", city: "Chicago"}
            }
        },
        user2: {
            default(){
                return {name: "Bohdi", street: "State", city: "Chi-city"}
            }
        }
    }
});
</script>

Inline Partials

If a single template needs the same HTML multiple places, use {{<partialName}} to create an inline partial:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        {{< addressView }}
            <address>{{ this.street}}, {{ this.city }}</address>
        {{/ addressView }}
        <h2>{{ this.user1.name }}</h2>
        {{ addressView(user1) }}
        <h2>{{ this.user2.name }}</h2>
        {{ addressView(user2) }}
    `,
    ViewModel: {
        user1: {
            default(){
                return {name: "Ramiya", street: "Stave", city: "Chicago"}
            }
        },
        user2: {
            default(){
                return {name: "Bohdi", street: "State", city: "Chi-city"}
            }
        }
    }
});
</script>

Other uses

Reading promises

Stache can read "virtual" properties from Promises and other types configured to work with getKeyValue.

The following "virtual" keys can be read from promises:

  • isPending - true if the promise has not been resolved or rejected.
  • isResolved - true if the promise has resolved.
  • isRejected - true if the promise was rejected.
  • value - the resolved value.
  • reason - the rejected value.
<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <div>
            {{# if(promise.isPending) }} Pending... {{/ if }}
            {{# if(promise.isRejected) }}
                Rejected! {{ promise.reason }}
            {{/ if }}
            {{# if(promise.isResolved) }}
                Resolved: {{ promise.value }}
            {{/ if}}
        </div>
        <button on:click="resolve('RESOLVED',2000)">Resolve in 2s</button>
        <button on:click="reject('REJECTED',2000)">Reject in 2s</button>
    `,
    ViewModel: {
        promise: "any",
        resolve(value, time){
            this.promise = new Promise((resolve)=>{
                setTimeout(()=>{
                    resolve(value);
                },time)
            });
        },
        reject(value, time){
            this.promise = new Promise((resolve, reject)=>{
                setTimeout(()=>{
                    reject(value);
                },time)
            });
        },
        connectedCallback(){
            this.resolve("RESOLVED", 2000);
        }
    }
});
</script>

Animation

Use on:event to listen to an event and call an animation library.

The following listens to when a todo's complete event is fired and calls this.shake. this.shake uses anime to animate the <div>:

<my-demo></my-demo>
<script src="//cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.min.js"></script>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        {{# for(todo of todos) }}
            <div on:complete:by:todo="this.shake(scope.element)">
                <input type="checkbox" checked:bind="todo.complete"/>
                {{todo.name}}
            </div>
        {{/ for }}
    `,
    ViewModel: {
        todos: {
            default: ()=> [
                {name: "animate", complete: false},
                {name: "celebrate", complete: true}
            ]
        },
        shake(element){
            anime({
                targets: element,
                translateX: [ 10,-10,0 ],
                easing: 'linear'
            });
        }
    }
});
</script>

Syntax Highlighting

Stache is very similar to handlebars and mustache. Most editors have plugins for one of these formats.

Spacing and formatting

Stache tolerates spacing similar to JavaScript. However, we try to following the following spacing in the following example:

{{# if( this.check ) }}
    {{ this.value }}
{{ else }}
    {{ this.method( arg1 ) }}
{{/ if}}

You can use the following regular expressions to create this spacing:

  • replace \{\{([^ #\/\^!]) with {{ $1
  • replace \{\{([#\/\^!])([^ ]) with {{$1 $2
  • replace ([^ ])\}\} with $1 }}

Accessing a helper if your property overwrites

Sometimes you have data with properties that conflict with stache's helpers, but you still need to access those helpers. To do this, you can access all those helpers on scope.helpers like scope.helpers.eq.

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <p on:click="this.toggle()">
            Time:
            {{# scope.helpers.eq(this.eq,"day") }}
                SUN 🌞
            {{ else }}
                MOON 🌚
            {{/ }}
        </p>
    `,
    ViewModel: {
        eq: {default: "day"},
        toggle(){
            this.eq = (this.eq === "day" ? "night" : "day");
        }
    }
});
</script>

Removing whitespace

Stache renders whitespace. For example, the following will render the space between the <h1> tags and the {{this.message}} magic tag:

import {stache} from "can";

var view = stache(`<h1>
    {{this.message}}
</h1>`);

var fragment = view({message: "Hi"});

console.log( fragment.firstChild.innerHTML ) //-> "\n\tHi\n"

You can use {{-expression-}} to remove this whitespace like:

import {stache} from "can";

var view = stache(`<h1>
    {{-this.message-}}
</h1>`);

var fragment = view({message: "Hi"});

console.log( fragment.firstChild.innerHTML ) //-> "Hi"

Understanding the stache language

Stache has a variety of magic tags and expressions that control the behavior of the DOM it produces. Furthermore, you are able to customize this behavior to a large extent.

The following sections outline stache's formal syntax and grammar. This knowledge can be useful when attempting to combine features into advanced functionality.

  • Magic tags - Magic tags like {{expression}} and {{{expression}}} control control how stache operates on the DOM.
  • Expression types - This is the valid semantics within a magic tag. For example, you can call functions like {{ this.callSomeMethod() }}.
  • Scope and context - How variables and this get looked up.

Magic tags

Rendering behavior is controlled with magic tags that look like {{}}. There are several forms of magic tags:

  • Insertion tags
    • {{expression}} - Insert escaped content into the DOM.
    • {{{expression}}} - Insert unescaped content into the DOM.
    • {{!expression}} - Make a comment.
  • Section tags - optional render a sub-section.
    • {{#expression}}TRUTHY{{else}}FALSY{{/expression}} - Optionally render the TRUTHY or FALSY section.
    • {{^expression}}FALSY{{else}}TRUTHY{{/expression}} - Optionally render the TRUTHY or FALSY section.
  • Special
    • {{<partialName}}...{{/partialName}} - Create an inline partial.
    • {{-expression-}} - Remove whitespace.

Magic tags are valid in the following places in HTML:

  • Between a open and closed tag:
    <div> {{magic}} </div>
    <div> {{#magic}} {{/magic}} </div>
    
  • Wrapping a series of opening and closing tags:
    <div> {{#magic}} <label></label> {{/magic}} </div>
    <div> {{#magic}} <label></label><span></span> {{/magic}} </div>
    
  • Within an attribute:
    <div class="selected {{magic}}"></div>
    <div class="{{#magic}}selected{{/magic}}"></div>
    
  • Within a tag:
    <div {{magic}}></div>
    
  • Within a tag, wrapping attributes:
    <div {{#magic}}class="selected"{{/magic}}></div>
    <input {{#magic}}checked{{/magic}}></div>
    

The following places are not supported:

  • Defining the tag name:
    <{{tagName}}></{{tagName}}>
    
  • Wrapping an opening or closing tag:
    <div> {{#magic}} <label> {{/magic}} </label></div>
    <div> <label> {{#magic}} </label><span></span> {{/magic}} </div>
    
  • Intersecting part of an attribute:
    <div {{attributeName}}="selected"></div>
    <div {{#magic}}class="{{/magic}}selected"></div>
    
  • Attribute values without quotes:
    <div attribute={{#magic}}"foo"{{/magic}}></div>
    <div key:raw={{#magic}}"foo"{{/magic}}></div>
    <div key:from={{#magic}}{{foo}}{{/magic}}></div>
    

Expression types

Stache supports different expression types within most of the magic tags. The following uses most of the expressions available:

<div> {{ this.method( 1, keyA=null keyB=true )[key]( "string", value ) }}

There are 6 expression types stache supports:

  • Literal expressions like {{"string"}}
  • KeyLookup expressions like {{key}}
  • Call expressions like {{method(arg)}}
  • Hash expressions like {{prop=key}}
  • Bracket expressions like {{[key]}}
  • Helper expressions like {{helper arg}} (deprecated, but will probably be supported forever)

Literal expressions

A Literal Expression specifies JS primitive values like:

  • Strings "strings"
  • Numbers 5
  • Booleans true or false
  • And null or undefined

They are usually passed as arguments to Call expressions like:

{{ task.filter( "completed", true ) }}

KeyLookup expressions

A KeyLookup Expression specifies a value in the scope that will be looked up. KeyLookup expressions can be the entire stache expression like:

{{ key }}

Or they can make up the method, arguments, bracket, and hash value parts of Call and Hash expressions:

{{ method( arg1, arg2 ) }}      Call
{{ method( prop=hashValue ) }}  Hash
{{ [key] }}                     Bracket

The value returned up by a KeyLookup depends on what the key looks like, and the scope.

Call expressions

A Call Expression calls a function looked up in the scope. It looks like:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: 'my-demo',
    view: `<h1>{{ this.pluralize(this.type, this.ages.length) }}</h1>`,
    ViewModel: {
        pluralize( type, count ) {
            return type + ( count === 1 ? "" : "s" );
        },
        ages: {default: ()=> [ 22, 32, 42 ] },
        type: {default: "age"}
    }
});
</script>

Call expression arguments are comma (,) separated. If a Hash expression is an argument, an object with the hash properties and values will be passed. For example:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: 'my-demo',
    view: `<h1>{{ this.pluralize(word=this.type count=this.ages.length) }}</h1>`,
    ViewModel: {
        pluralize( options ) {
            return options.word + ( options.count === 1 ? "" : "s" );
        },
        ages: {default: ()=> [ 22, 32, 42 ] },
        type: {default: "age"}
    }
});
</script>

Hash expressions

A Hash Expression specifies a property value on a object argument. Notice how method is called below:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: 'my-demo',
    view: `<h1>{{ this.method(a=this.aProp b=null, c=this.func() ) }}</h1>`,
    ViewModel: {
        method( arg1, arg2 ) {
            console.log(arg1, arg2) //-> {aProp: "aValue", b: null},{c:"FUNC"}
        },
        aProp: {default: "aValue" },
        func(){
            return "FUNC";
        }
    }
});
</script>

Bracket expressions

A Bracket Expression can be used to look up a dynamic property in the scope. This is very useful when looping through properties to write out on many records:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: 'my-demo',
    view: `
        <table>
            {{# for(record of records) }}
                <tr>
                    {{# for(key of keys )}}
                        <td>{{ record[key] }}</td>
                    {{/ for}}
                </tr>
            {{/ for}}
        </table>
    `,
    ViewModel: {
        records: {
            default: ()=> [
                {first: "Justin", last: "Meyer", label: "Dad"},
                {first: "Payal", last: "Meyer", label: "Mom"},
                {first: "Ramiya", last: "Meyer", label: "Babu"},
                {first: "Bohdi", last: "Meyer", label: "Baby"}
            ]
        },
        keys: {
            default: ()=> [
                "first","last","label"
            ]
        }
    }
});
</script>

This can be useful for looking up values using keys containing non-alphabetic characters:

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: 'my-demo',
    view: `<h1>{{ this.data["special:prop"] }}</h1>`,
    ViewModel: {
        data: {
            default(){
                return {"special:prop": "SPECIAL VALUE"}
            }
        }
    }
});
</script>

Bracket expressions can also be used to look up a value in the result of another expression:

{{ this.getPerson()[key] }}

Helper expressions

Helper Expressions are supported but deprecated. It's unlikely they will be dropped for a long time.

Scope and context

Stache maintains a scope similar to the one maintained in JavaScript. For example, the inner function is able to access the message, last, and first variables:

const message = "Hello";
function outer() {
    const last = "Meyer";

    function inner() {
        const first = "Bohdi";
        console.log( message + " " + first + " " + last );
    }
    inner();
}
outer();

Stache was originally built with a handlebars and mustache-type scope. This scope is still supported, but deprecated. If you are supporting templates in this style, please read Legacy Scope Behavior.

The modern style of stache works much more like JavaScript. A view is rendered with a context accessible as this. For example:

import {stache} from "can";
var view = stache(`<h1>Hello {{ this.subject }}</h1>`);

var context = {
    message: "World"
};

var fragment = view(context);

console.log(fragment.firstChild.innerHTML)
//-> Hello World

The for(of) helper creates variables local to the section. In the following example todo is only available between {{# for(...)}} and {{/ for }}.

<my-demo></my-demo>
<script type="module">
import {Component} from "can";

Component.extend({
    tag: "my-demo",
    view: `
        <ul>
            {{# for(todo of this.todos) }}
                <li>{{ todo.name }}</li>
            {{/ for }}
        </ul>
    `,
    ViewModel: {
        todos: {
            default: () => [
                {name: "Writing"},
                {name: "Branching"},
                {name: "Looping"}
            ]
        }
    }
});
</script>

When a variable like todo is looked up, it will look for variables in its scope and then walk to parent scopes until it finds a value.

See also

can-view-scope is used by stache internally to hold and lookup values. This is similar to how JavaScript’s closures hold variables, except you can use it programmatically.

can-component and can-view-callbacks.tag allow you to define custom elements for use within a stache template. can-view-callbacks.attr allow you to define custom attributes.

can-stache-bindings sets up element and bindings between a stache template’s can-view-scope, component viewModels, or an element’s attributes.

How it works

Coming soon!

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 5.33.3.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news