1.2.3: Evaluation Context
Evaluation Context
The variable names that can be referenced in Handlebars expressions depend on the current evaluation context.
When we've seen references like theme_settings.show_product_swatch_names
, these are relative to what we could consider
the "top level" context for the page. Block helpers and partials can, however, change the evaluation context for the content within
them.
Let's start with the example of context switching we've already seen. Consider this example data again:
items = [
{
id: 1,
name: "Brian"
},
{
id: 2,
name: "Joe"
}
]
We observed this loop of the above array:
<ul>
{{#each items}}
<li>{{id}}: {{name}}</li>
{{/each}}
</ul>
The variable items
is available in the context where this snippet appears, but it's clear from the example that within
the {{#each}}
block, the context has changed. Now id
and name
can be referenced directly, because the current item
in the array is the new evaluation context.
Note that the current context (the current array object in this case) is also available to reference as this
. This is also the
case for any of the other means of switching contexts that we'll cover next.
Explicitly Changing Context
The block helper {{#with}}
can be used to explicitly change the context for its contents. Let's say the variable
product
is available in the template and has this value:
{
"sku": "ED209",
"name": "Your Friendly Robot Companion",
"description": "You'll have a blast with your robot buddy."
}
You could write a snippet of content like this:
<dl>
<dt>SKU</dt>
<dd>{{product.sku}}</dd>
<dt>Name</dt>
<dd>{{product.name}}</dd>
<dt>Description</dt>
<dd>{{product.description}}</dd>
</dl>
But you could also wrap your content in {{#with}}
to make the property references terser:
{{#with product}}
<dl>
<dt>SKU</dt>
<dd>{{sku}}</dd>
<dt>Name</dt>
<dd>{{name}}</dd>
<dt>Description</dt>
<dd>{{description}}</dd>
</dl>
{{/with}}
Passing Context to Other Templates
As a refresher, this is the expression for injecting one template into another:
{{> components/my-custom-view}}
With a simple expression like this, the content of my-custom-view.html
will be evaluated with the same context as
the template that injected it, as if the former had simply been pasted into the latter.
On the other hand, you have the option of passing a parameter that will establish a different context for the template you're injecting.
{{> components/my-custom-view product}}
If my-custom-view.html
is injected as shown above, the value of product
becomes the new context. Within my-custom-view.html
,
values like sku
and description
should be referenced directly, rather than using product.sku
and product.description
.
You can also establish a custom context by passing one or more named parameters:
{{> components/my-custom-view
product=review.product
show_details=theme_settings.show_product_details_tabs}}
Using this syntax, you should expect to have product
and show_details
available in my-custom-view.html
.
An Aside
In the Cornerstone templates, you will often see instances like
{{> components/products/grid products=category.products theme_settings=theme_settings}}
. The explicit "passthrough" oftheme_settings
seems to suggest that if you pass named parameters, you must then pass all values you need in the injected template. In my experience, however, named parameters only add to the current context. (In other words,theme_settings
would be available for use ingrid.html
even without the explicit parameter.)
Let's consider one more example combined with a loop. Assume we have the following data available:
blogPosts = [
{
slug: "my-batty-life",
title: "My Batty Life",
author: "Bruce Wayne"
},
{
slug: "scars-never-heal",
title: "Some Scars Never Heal",
author: "Harry Potter"
}
]
In one template we loop through blogPosts
and inject a template for each post:
{{#each blogPosts}}
{{> components/blog-post post=this}}
{{/each}}
Here we've combined this
, referencing the context within the loop, with a named parameter. The result is that
blog-post.html
can contain references like this:
<h2>{{post.title}}</h2>
<aside>By {{post.author}}</aside>
Using Parent Context
When context switching occurs, this doesn't mean that values from outside the new context can no longer be used.
Values from the "parent" context can be referenced by prefixing a name with ../
.
Let's refactor our previous blog posts example a bit:
blog = {
name: "A Hero's Journey",
posts: [
{
slug: "my-batty-life",
title: "My Batty Life",
author: "Bruce Wayne"
},
{
slug: "scars-never-heal",
title: "Some Scars Never Heal",
author: "Harry Potter"
}
]
}
We now have blog
, with two properties: name
and posts
. When looping on blog.posts
, we have drilled down into the
context of a single object in that array, but we can still include a reference to the blog.name
property.
{{#each blog.posts}}
{{> components/blog-post post=this blogName=../blog.name}}
{{/each}}
Referencing the parent context like this works whether a context switch was due to a template injected with a new context
object, a loop, or any other block helper like {{#with}}
. And multiple nesting levels can be handled with syntax like
../../blog.name
(for accessing a value two context levels "up").