Permalink
Browse files

Update helpers and delegate documentation so that they better fit wit…

…h filters.
  • Loading branch information...
1 parent 2fc7943 commit fda93a5b90932011d56fc0d1e998c65344e870a9 @groue committed Aug 4, 2012
View
@@ -16,10 +16,6 @@ While rendering a template, several objects may get messages from GRMustache:
The template's delegate can observe the full template rendering. However, sections delegates can only observe the rendering of their inner content. As sections get nested, a template gets more and more delegates.
-Template delegates are illustrated in the [indexes sample code](sample_code/indexes.md), where NSArray instances are caught before their rendering, in order to let GRMustache render indexes and other collection-related information.
-
-Section delegates are used in the [number formatting sample code](sample_code/number_formatting.md), where the NSNumberFormatter class is given the opportunity to render formatted numbers.
-
Observe the template rendering
------------------------------
@@ -64,14 +60,15 @@ The *interpretation* parameter tells you how the return value of the invocation
typedef enum {
GRMustacheInterpretationSection,
GRMustacheInterpretationVariable,
+ GRMustacheInterpretationFilterArgument,
} GRMustacheInterpretation;
```
`GRMustacheInterpretationVariable` tells you that the return value is rendered by a Mustache variable tag such as `{{name}}`. Basically, GRMustache simply invokes its `description` method. See [Guides/runtime.md](runtime.md) for more information.
`GRMustacheInterpretationSection` tells you that the return value is used by a Mustache section such as `{{#name}}...{{/name}}`. Mustache sections are versatile: there are boolean sections, loop sections, and lambda sections, and this depends solely on the rendered value, that is to say: the return value of the invocation. Again, see [Guides/runtime.md](runtime.md) for more information.
-You will find an actual use of this *interpretation* parameter in the [number formatting sample code](sample_code/number_formatting.md).
+`GRMustacheInterpretationFilterArgument` tells you that the return value is about to be processed by a filter such as `{{ f(name) }}`. See [Guides/filters.md](filters.md) for more information.
### A practical use: debugging templates
@@ -112,9 +109,18 @@ The `returnValue` property of the *invocation* parameter can be written. If you
{
// When returnValue is nil, GRMustache could not find any value to render.
if (invocation.returnValue == nil) {
- invocation.returnValue = @"[DEFAULT]";
+ invocation.returnValue = @"DEFAULT";
}
}
```
+### Relationship with filters and helpers
+
+Usually, [filters](filters.md) and [helpers](helpers.md) should do the trick when you want to alter a template's rendering.
+
+However, they both require to be explicited invoked from the template: `{{#helper}}...{{/helper}}`, and `{{ filter(...) }}`.
+
+GRMustacheTemplateDelegate will help you when you can not, or do not want, to embed your extra behaviors right into the template.
+
+
[up](../../../../GRMustache), [next](../../../tree/master/Guides/sample_code)
View
@@ -1,3 +1,5 @@
+[up](../../../../GRMustache), [next](delegate.md)
+
Filters
=======
@@ -137,3 +139,5 @@ GRMustache helps you debugging by providing the exact place where the error occu
Missing filter for key `f` in tag `{{ f(foo) }}` at line 13 of /path/to/teplate.
Object for key `f` in tag `{{ f(foo) }}` at line 13 of /path/to/template does not conform to GRMustacheFilter protocol: "blah"
+
+[up](../../../../GRMustache), [next](delegate.md)
View
@@ -10,7 +10,7 @@ You'll find below some useful information on each of those topics.
### Classes in a glance
-The library features are described in the guides. This section describes the classes that implement those features. They are organized in a few big domains:
+The library features are described in the [guides](templates.md). This section describes the classes that implement those features. They are organized in a few big domains:
- **Parsing**
- `GRMustacheParser`
View
@@ -0,0 +1,175 @@
+[up](../../../../GRMustache), [next](filters.md)
+
+Helpers
+=======
+
+GRMustache helpers allow you to implement "Mustache lambdas", that is to say have your own code executed when GRMustache renders a section such as `{{#name}}...{{/name}}`.
+
+## Overview
+
+When GRMustache renders a section `{{#name}}...{{/name}}`, it looks for the `name` key in the [context stack](runtime/context_stack.md), using the standard Key-Value Coding `valueForKey:` method. GRMustache may find a string, an [array](runtime/loops.md), a [boolean](runtime/booleans.md), whatever, or a helper. It's here a matter of attaching code, instead of regular values, to the keys of your data objects.
+
+GRMustache recognizes a helper when it finds an object that conforms to the `GRMustacheHelper` protocol.
+
+
+## Helpers in action: rendering alternate template strings
+
+For the purpose of demonstration, we'll implement a helper that turns a portion of a template into a HTML link.
+
+For instance, let's focus on the following template snippets:
+
+ {{#movie}}
+ {{#link}}{{title}}{{/link}}
+ {{#director}}
+ by {{#link}}{{firstName}} {{lastName}}{{/link}}
+ {{/director}}
+ {{/movie}}
+
+One will expect it to render as:
+
+ <a href="/movies/123">Citizen Kane</a> by <a href="/people/321">Orson Welles</a>
+
+We see here that the `link` sections were able to output HTML links that were not visible in the template. The content of the links, however, namely the title of the movie, and the names of the director, were defined in the template.
+
+### Declaring our linking helper
+
+We need an object that conforms to the `GRMustacheHelper` protocol, so we'll declare a new class.
+
+```objc
+@interface LinkHelper: NSObject<GRMustacheHelper>
+@end
+
+@implementation LinkHelper
+- (NSString *)renderSection:(GRMustacheSection *)section
+{
+ return ...;
+}
+@end
+
+id linkHelper = [[LinkHelper alloc] init];
+```
+
+That `renderSection:withContext:` method will be invoked when GRMustache renders the sections attached to our helper. It returns the raw string that should be rendered.
+
+Starting iOS4 and MacOS 10.6, the Objective-C language provides us with blocks. This can relieve the burden of declaring a full class for each helper:
+
+```objc
+id linkHelper = [GRMustacheHelper helperWithBlock:^(GRMustacheSection *section) {
+ return ...;
+}];
+```
+
+The two techniques are equivalent, though.
+
+### The rendering
+
+Now up to the implementation. The _section_ argument is a `GRMustacheSection` object, which represents the section being rendered: `{{#link}}{{title}}{{/link}}`, or `{{#link}}{{firstName}} {{lastName}}{{/link}}`.
+
+This _section_ object has a `innerTemplateString` property, which returns the literal inner content of the section. It will return `{{title}}` or `{{firstName}} {{lastName}}` in our specific example.
+
+Let's wrap this inner template string with `<a href="{{url}}">` and `</a>` in order to build a new template string, and ask the section to render it instead of its regular content:
+
+```objc
+id linkHelper = [GRMustacheHelper helperWithBlock:^(GRMustacheSection *section) {
+ // Build a new template string with the section's inner string...
+ NSString *templateString = [NSString stringWithFormat:@"<a href=\"{{url}}\">%@</a>", section.innerTemplateString];
+
+ // ...and render it:
+ return [section renderTemplateString:templateString error:NULL];
+}];
+```
+
+Assuming the movies and the people have a `url` property, this will work like a charm.
+
+### Attaching the helper to the `link` key
+
+Now we have to have GRMustache find our helper when rendering the `{{#link}}` sections.
+
+We could have one of our model objects implement a `link` property. But this feature is not really tied to any of them. Instead, we'll provide our helper aside, thanks to the `renderObjectsInArray:` method.
+
+```objc
+// Prepare data
+id data = ...;
+
+// Prepare helper
+id linkHelper = ...;
+NSDictionary *helpers = [NSDictionary dictionaryWithObject:localizeHelper forKey:@"link"];
+
+// Render
+GRMustacheTemplate *template = [GRMustacheTemplate templateFrom...];
+NSArray *renderedObjects = [NSArray arrayWithObjects:helpers, data, nil];
+NSString *rendering = [template renderObjectsInArray:renderedObjects];
+```
+
+
+## Helpers in action: implementing a localizing helper
+
+Let's now implement a helper that translates, via `NSLocalizedString`, the content of the section.
+
+ {{#items}}
+ {{quantity}} × {{name}}
+ {{#localize}}Delete{{/localize}}
+ {{/items}}
+
+One will expect `{{#localize}}Delete{{/localize}}` to output `Effacer` when the locale is French.
+
+### Declaring our localizing helper
+
+We again need an object that conforms to the `GRMustacheHelper` protocol, but let's keep using the easier block syntax:
+
+```objc
+id localizedStringHelper = [GRMustacheHelper helperWithBlock:^(GRMustacheSection *section) {
+ return ...;
+}];
+```
+
+Our first implementation will use the `innerTemplateString` property of the section, described above. It would return `Delete` in our specific example. This looks like a perfect argument for `NSLocalizedString`:
+
+```objc
+id localizedStringHelper = [GRMustacheHelper helperWithBlock:^(GRMustacheSection *section) {
+ return NSLocalizedString(section.innerTemplateString, nil);
+}];
+@end
+```
+
+So far, so good, this would work as expected.
+
+#### Rendering the inner content
+
+Yet the application keeps on evolving, and it appears that the item names should also be localized. The template snippet now reads:
+
+ {{#items}}
+ {{quantity}} × {{#localize}}{{name}}{{/localize}} <-- OMG
+ {{#localize}}Delete{{/localize}}
+ {{/items}}
+
+Now the strings we have to localize may be:
+
+- literal strings from the template: `Delete`
+- strings coming from items : `{{name}}`
+
+Our first implementation will fail, since it would return `NSLocalizedString(@"{{name}}", nil)` instead of localizing item names.
+
+Fortunately, we have the `render` method of `GRMustacheSection`, which returns the rendering of the receiver's inner content. It would return `Delete`, or an item name, in our specific case.
+
+Now we have our final implementation:
+
+```objc
+id localizedStringHelper = [GRMustacheHelper helperWithBlock:^(GRMustacheSection *section) {
+ return NSLocalizedString([section render], nil);
+}];
+```
+
+
+## GRMustache helpers vs. Mustache lambdas
+
+**Warning: If your goal is to design GRMustache helpers that remain compatible with Mustache lambdas of [other Mustache implementations](https://github.com/defunkt/mustache/wiki/Other-Mustache-implementations), read the following with great care.**
+
+The strings returned by GRMustache helpers are directly inserted in the final rendering, without any further processing.
+
+However, the specification [states](https://github.com/mustache/spec/blob/v1.1.2/specs/%7Elambdas.yml#L90) that "Lambdas used for sections should have their results parsed" (read, processed as a Mustache template, and rendered in the current context).
+
+In order to comply with the genuine Mustache behavior, a helper must return the result of the `renderTemplateString:` method of the section, as the linking helper seen above. The localizing helper, however, is not specification-compliant.
+
+
+[up](../../../../GRMustache), [next](filters.md)
View
@@ -41,10 +41,6 @@ Mustache does a little more than rendering plain `{{name}}` tags. Let's review M
- [booleans.md](runtime/booleans.md)
Control whether a Mustache section should render or not.
-
-- [helpers.md](runtime/helpers.md)
-
- Mustache has "lambda sections". These are sections that allow you to execute custom code, and implement nifty features like caching, filtering, whatever, on portions of your templates.
[up](../../../../GRMustache), [next](runtime/context_stack.md)
@@ -1,4 +1,4 @@
-[up](../runtime.md), [next](helpers.md)
+[up](../runtime.md), [next](../helpers.md)
# Booleans
@@ -62,4 +62,4 @@ They all prevent Mustache sections `{{#name}}...{{/name}}` rendering.
They all trigger inverted sections `{{^name}}...{{/name}}` rendering.
-[up](../runtime.md), [next](helpers.md)
+[up](../runtime.md), [next](../helpers.md)
Oops, something went wrong.

0 comments on commit fda93a5

Please sign in to comment.