can-stache
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
- 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.
- template
{String}
:The text of a stache template.
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> {{/ }}
- Stache tolerates undefined property values - The following will not error. Instead
stache will simply warn:
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:
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 click
s 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
orfalse
- And
null
orundefined
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!