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" of theme_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 in grid.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").

Resources

Complete and Continue