Skip to content

Instantly share code, notes, and snippets.

@bmomberger-bitovi
Last active October 21, 2017 18:38
Show Gist options
  • Save bmomberger-bitovi/5794b2b6ef876fd3d92f9ca4fcb5a731 to your computer and use it in GitHub Desktop.
Save bmomberger-bitovi/5794b2b6ef876fd3d92f9ca4fcb5a731 to your computer and use it in GitHub Desktop.
Blog post proposal for Stache magic
title author
Using can-import to Explain The Special Features of Stache and Stache Bindings
Bradley Momberger

Recently on Bitovi's internal Slack, one of our developers wanted to have a better understanding of a can-import tag that had some extra fancy sauce applied to it. Lately at Bitovi we've been working hard on reducing both special cases and arcane syntax in Stache, to create a clear and consistent experience for template creators. Even with these efforts, it can sometimes not be clear what something does when using all of the available features. So to help motivate better understanding, here's an illustrative example of how can-import is consistent with other tags and references in Stache. For the can-import tag in question, I've highlighted things with special meaning.

<can-import from="./example.stache" @value:to="*example" />{{>*example}}

Overall, the purpose of this tag is to import a Stache file to be used as a partial, then render the partial along with the rest of the script. The relative path to the Stache file is "./example.stache", which explains the from attribute. The rest of the tag is decorated with a few odd extra characters. So let's explore those funky bits and what they mean, and why they're all essential to making this work correctly.

@value:to

When a tag or template uses its own view model rather than one from its parent, Stache uses bindings to connect data between parent and child. The bindings have a sytnax that determine which direction the data goes (whether setting A sets B, or setting B sets A, or both). That is the meaning of :to here: the value of value from the child (the can-import tag) gets pushed to the attribute value on the parent (the template that contains the tag).

The attribute is called value and it's reasonable to ask at this point whether value has a special meaning. The answer is yes... kind of. What's important to know here, is that the view model for a can-import tag is the Promise that resolves when the import has been loaded. With can-reflect-promise, all Promises get special keys that can be read in Staches:

  • isResolved/isRejected/isPending (state-related predicate functions)
  • state (one of "resolved"/"rejected"/"pending")
  • value (the resolved value, if resolved)
  • reason (the reason for rejection, if rejected)

So value in @value:to is reading from the resolved value of the Promise, and it has a binding sending it somewhere, but why does the @ have to be there?

In most JavaScript implementations of Mustache, including Stache, the default behavior when encountering a function is to call the function to get its return value. However, here we need the rendering function to be a function, so we can use it later as a partial renderer. That @ indicates to Stache that, even though what's in value is a function, it shouldn't be executed for its return value; rather, it should be the return value.

*example

Now that we've established what's being sent out from the can-import tag, let's look at where it goes. The Stache expression *example is pretty basic; there are no path separators, and we can be reasonably certain that it's going to be stored in a property named *example on the scope, but why that leading character?

That asterisk (*) is a special marker for Scope lookup. Remember that a can-view-scope is a stack of context objects that Stache looks at to find the values for different items. Normally, every context object on the Scope is a meaningful object outside of it. Maybe the current context is a data model, and quite likely it's a sealed DefineMap that can't accept new properties. But here in the Stache template we might need to push a new value into the scope from somewhere like a can-import's view model, without disturbing any of the existing contexts. To solve this, can-view-scope provides a reference scope, a special Scope that has its own private context, and any *-prefixed key is assumed to be in it. So sending a value to *element means that it gets put on the reference scope with the key *element.

>*example

Finally, the > operator in Stache magic tags renders partials, which are other renderer functions whose output gets placed in the parent template at the location where called. We read the partial from the reference scope, using the key *example, and the Stache template that initially came from importing "example.stache" gets rendered with the current context. Easy, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment