Skip to content

Latest commit

 

History

History
421 lines (284 loc) · 17.4 KB

runtime.md

File metadata and controls

421 lines (284 loc) · 17.4 KB

up, next

GRMustache runtime

You'll learn here how GRMustache renders your data. The loading of templates is covered in the Templates Guide. Common patterns for feeding templates are described in the ViewModel Guide.

Variable tags

Variable tags {{ name }} and {{{ name }}} look for the name key in the object you provide:

id data = @{ @"name": @"Arthur" };

// Renders "Hello Arthur!"
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:@"Hello {{name}}!"
                                                 error:NULL];

Any Key-Value Coding compliant object that responds to the valueForKey: method can be used.

Dictionaries are such objects. So are, generally speaking, your custom models:

// The Person class defines the `name` property:
Person *barbara = [Person personWithName:@"Barbara"];

// Renders "Hello Barbara!"
NSString *rendering = [GRMustacheTemplate renderObject:barbara
                                            fromString:@"Hello {{name}}!"
                                                 error:NULL];

Remember that {{ name }} renders HTML-escaped values, when {{{ name }}} and {{& name }} render unescaped values.

Objects are usually rendered with the standard description method, with two exceptions:

A variable tag renders all items of enumerable objects:

id data = @{ @"voyels": @[@"A", @"E", @"I", @"O", @"U"] };

// Renders "AEIOU"
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:@"{{voyels}}"
                                                 error:NULL];

This especially comes handy with your custom rendering objects: you may think of Ruby on Rails' <%= render @items %>.

Expressions

Variable tags render simple keys as seen above, and, more generally, expressions, such as the key path person.name and the filtered expression uppercase(person.name).

Person *craig = [Person personWithName:@"Craig"];
id data = @{ @"person": craig };

// Renders "Hello CRAIG!"
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:@"Hello {{ uppercase(person.name) }}!"
                                                 error:NULL];

GRMustache first looks for the person key, extracts its name, and applies the uppercase built-in filter of the standard library. The variable tag eventually renders the resulting string.

Section tags

The rendering of section tags such as {{# name }}...{{/ name }} and {{^ name }}...{{/ name }} depend on the value attached to the name expression.

Generally speaking, inverted sections {{^ name }}...{{/ name }} render when regular sections {{# name }}...{{/ name }} do not. You can think of the caret ^ as the Mustache "unless".

Precisely speaking:

False sections

If the value is false, regular sections are omitted, and inverted sections rendered:

id data = @{ @"red": @NO, @"blue": @YES };

// Renders "Not red"
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:@"{{#red}}Red{{/red}}{{^red}}Not red{{/red}}"
                                                 error:NULL];

// Renders "Blue"
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:@"{{#blue}}Blue{{/blue}}{{^blue}}Not blue{{/blue}}"
                                                 error:NULL];

When an inverted sections follows a regular section with the same expression, you can use the short {{# name }}...{{^ name }}...{{/ name }} form, avoiding the closing tag for {{# name }}. Think of "if ... else ... end". For brevity's sake, you can also omit the expression after the opening tag: {{#name}}...{{^}}...{{/}} is valid.

The full list of false values are:

  • nil and missing keys
  • [NSNull null]
  • NSNumber instances whose boolValue method returns NO
  • empty strings @""
  • empty enumerables.

They all prevent Mustache sections {{# name }}...{{/ name }} rendering.

They all trigger inverted sections {{^ name }}...{{/ name }} rendering.

Enumerable sections

If the value attached to a section conforms to the NSFastEnumeration protocol (except NSDictionary), regular sections are rendered as many times as there are items in the enumerable object:

NSArray *friends = @[
    [Person personWithName:@"Dennis"],
    [Person personWithName:@"Eugene"],
    [Person personWithName:@"Fiona"]];
id data = @{ @"friends": friends };

NSString *templateString = @"My friends are:\n"
                           @"{{# friends }}"
                           @"- {{ name }}\n"
                           @"{{/ friends }}";

// My friends are:
// - Dennis
// - Eugene
// - Fiona
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

Each item in the collection gets, each on its turn, available for the key lookup: that is how the {{ name }} tag renders each of my friend's name.

Inverted sections render if and only if the collection is empty:

id data = @{ @"friends": @[] }; // empty array

NSString *templateString = @"{{^ friends }}"
                           @"I have no friend, sob."
                           @"{{/ friends }}";

// I have no friend, sob.
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

Rendering a collection of strings

You may render a collection of strings with the dot expression ., aka "implicit iterator":

id data = @{ @"items": @[@"Ham", @"Jam"] };

NSString *templateString = @"{{# items }}"
                           @"- {{ . }}"
                           @"{{/ items }}";

// - Ham
// - Jam
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

Rendering a section once when a collection contains several items

Sections render as many times as they contain items.

However, you may want to render a section once if and only if a collection is not empty. For example, when rendering a single <ul> HTML element that wraps several <li>.

A template that is compatible with other Mustache implementations needs an extra boolean key that states whether the collection is empty or not:

NSArray *friends = ...;
id data = @{
    @"hasFriends": @(friends.count > 0),
    @"friends": friends };

NSString *templateString = @"{{# hasFriends }}"
                           @"<ul>"
                           @"  {{# friends }}"
                           @"  <li>{{ name }}</li>"
                           @"  {{/ friends }}";
                           @"</ul>"
                           @"{{/ hasFriends }}";

// <ul>
//   <li>Dennis</li>
//   <li>Eugene</li>
//   <li>Fiona</li>
// </ul>
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

If you do not care about compatibility, you can simply use the count property of NSArray, and use the fact that GRMustache considers zero numbers as false:

NSArray *friends = ...;
id data = @{ @"friends": friends };

NSString *templateString = @"{{# friends.count }}"
                           @"<ul>"
                           @"  {{# friends }}"
                           @"  <li>{{ name }}</li>"
                           @"  {{/ friends }}";
                           @"</ul>"
                           @"{{/ friends.count }}";

// <ul>
//   <li>Dennis</li>
//   <li>Eugene</li>
//   <li>Fiona</li>
// </ul>
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

Lambda sections

Mustache defines lambda sections, that is, sections that execute your own application code, and allow you to extend the core Mustache engine.

Such sections are fully documented in the Rendering Objects Guide, but here is a preview:

id data = @{
    @"name1": @"Gustave",
    @"name2": @"Henriett" };

NSString *templateString = @"{{#localize}}Hello {{name1}}, do you know {{name2}}?{{/localize}}";

// Assuming a Spanish locale:
// Hola Gustave, sabes Henriett?
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

The localize key is attached to a rendering object that is built in the standard library shipped with GRMustache.

Other sections

When a section renders a value that is not false, not enumerable, not a rendering object, it renders once, making the value available for the key lookup inside the section:

Person *ignacio = [Person personWithName:@"Ignacio"];
id data = @{ @"person": ignacio };

// Renders "Hello Ignacio!"
NSString *templateString = @"{{# person }}Hello {{ name }}!{{/ person }}";
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

Expressions in sections

Just as variable tags, section tags render any well-formed expressions:

id data = @{
    @"person": @{
        @"friends": @[
            [Person personWithName:@"José"],
            [Person personWithName:@"Karl"],
            [Person personWithName:@"Lubitza"]]
    }
};

NSString *templateString = @"{{# withPosition(person.friends) }}"
                           @"    {{ position }}: {{ name }}"
                           @"{{/}}";

// 1: José
// 2: Karl
// 3: Lubitza
NSString *rendering = [GRMustacheTemplate renderObject:data
                                            fromString:templateString
                                                 error:NULL];

The withPosition filter returns a rendering object that makes the position key available inside the section. It is described in the Collection Indexes Sample Code.

The Context Stack

We have seen that values rendered by sections are made available for the key lookup inside the section.

As a matter of fact, objects that were the context of enclosing sections are still available: the latest object has just entered the top of the context stack.

An example should make this clearer. Let's consider the template below:

{{# title }}
    {{ length }}
{{/ title }}

{{# title }}
    {{ title }}
{{/ title }}

In the first section, the length key will be fetched from the title string which has just entered the context stack: it will be rendered as "6" if the title is "Hamlet".

In the last section, the title string is still the context. However it has no title key. Thus GRMustache looks for it in the enclosing context, finds again the title string, and renders it:

6
Hamlet

This technique allows, for example, the conditional rendering of a <h1> HTML tag if and only if the title is not empty (empty strings are considered false, see "false sections" above):

{{# title }}
  <h1>{{ title }}</h1>  {{! rendered if there is a title }}
{{/ title }}

Fine tuning of key lookup

Focus on the current context

These three template snippets are quite similar, but not stricly equivalent:

  • ...{{# foo }}{{ bar }}{{/ foo }}...
  • ...{{# foo }}{{ .bar }}{{/ foo }}...
  • ...{{ foo.bar }}...

The first will look for bar anywhere in the context stack, starting with the foo object.

The two others are identical: they ensure the bar key comes from the very foo object. If foo is not found, the bar lookup will fail as well, regardless of bar keys defined by enclosing contexts.

Protected contexts

Protected contexts let you make sure some keys get always evaluated to the same value, regardless of objects that enter the context stack. Check the Protected Contexts Guide.

Tag delegates

Values extracted from the context stack are directly rendered unless you had some tag delegates enter the game. They help you render default values for missing keys, for example. See the Tag Delegates Guide for a full discussion.

Detailed description of GRMustache handling of valueForKey:

As seen above, GRMustache looks for a key in your data objects with the valueForKey: method. With some extra bits.

NSUndefinedKeyException Handling

NSDictionary never complains when asked for an unknown key. However, the default NSObject implementation of valueForKey: raises an NSUndefinedKeyException.

GRMustache catches those exceptions, so that the key lookup can continue down the context stack.

NSUndefinedKeyException Prevention

When debugging your project, NSUndefinedKeyException raised by template rendering may become a real annoyance, because it's likely you've told your debugger to stop on every Objective-C exceptions.

You can avoid that: make sure you invoke once, early in your application, the following method:

[GRMustache preventNSUndefinedKeyExceptionAttack];

Depending on the number of NSUndefinedKeyException that get prevented, you will experience a slight performance hit, or a performance improvement.

Since the main use case for this method is to avoid Xcode breaks on rendering exceptions, the best practice is to conditionally invoke this method, using the NS_BLOCK_ASSERTIONS that helps identifying the Debug configuration of your targets:

#if !defined(NS_BLOCK_ASSERTIONS)
// Debug configuration: keep GRMustache quiet
[GRMustache preventNSUndefinedKeyExceptionAttack];
#endif

NSArray, NSSet, NSOrderedSet

GRMustache shunts the valueForKey: implementation of Foundation collections to NSObject's one.

It is little know that the implementation of valueForKey: of Foundation collections return another collection containing the results of invoking valueForKey: using the key on each of the collection's objects.

This is very handy, but this clashes with the rule of least surprise in the context of Mustache template rendering.

First, {{collection.count}} would not render the number of objects in the collection. {{#collection.count}}...{{/}} would not conditionally render if and only if the array is not empty. This has bitten at least one GRMustache user, and this should not happen again.

Second, {{#collection.name}}{{.}}{{/}} would render the same as {{#collection}}{{name}}{{/}}. No sane user would ever try to use the convoluted first syntax. But sane users want a clean and clear failure when their code has a bug, leading to GRMustache not render the object they expect. When object resolves to an unexpected collection, object.name should behave like a missing key, not like a key that returns an unexpected collection with weird and hard-to-debug side effects.

Based on this rationale, GRMustache uses the implementation of valueForKey: of NSObject for arrays, sets, and ordered sets. As a consequence, the count key can be used in templates, and no unexpected collections comes messing with the rendering.

Compatibility with other Mustache implementations

The Mustache specification does not enforce the list of false values, the values that trigger or prevent the rendering of sections and inverted sections:

There is no guarantee that {{# value }}...{{/ value }} and {{^ value }}...{{/ value }} will render the same, provided with the exact same input, in all Mustache implementations.

That's unfortunate. Anyway, for the record, here is a reminder of all false values in GRMustache:

  • nil and missing keys
  • [NSNull null]
  • NSNumber instances whose boolValue method returns NO
  • empty strings @""
  • empty enumerables.

up, next