Skip to content

Commit

Permalink
Fully expose and document GRMustacheContext
Browse files Browse the repository at this point in the history
  • Loading branch information
groue committed Dec 7, 2010
1 parent 5c6ebaa commit df2bdc8
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 38 deletions.
1 change: 1 addition & 0 deletions Classes/GRMustacheContext.h
Expand Up @@ -29,4 +29,5 @@
+ (id)contextWithObject:(id)object;
+ (id)contextWithObjects:(id)object, ...;
- (GRMustacheContext *)contextByAddingObject:(id)object;
- (id)valueForKey:(NSString *)key;
@end
2 changes: 1 addition & 1 deletion Classes/GRMustacheContext.m
Expand Up @@ -193,7 +193,7 @@ - (void)dealloc {
- (id)valueForKeyComponent:(NSString *)key foundInContext:(GRMustacheContext **)outContext {
// value by selector

SEL renderingSelector = NSSelectorFromString([NSString stringWithFormat:@"%@Section:withObject:", key]);
SEL renderingSelector = NSSelectorFromString([NSString stringWithFormat:@"%@Section:withContext:", key]);
if ([object respondsToSelector:renderingSelector]) {
return [GRMustacheLambdaSelectorWrapper helperWithObject:object selector:renderingSelector];
}
Expand Down
3 changes: 2 additions & 1 deletion Classes/GRMustacheLambda.h
Expand Up @@ -21,9 +21,10 @@
// THE SOFTWARE.

#import "GRMustacheSection.h"
#import "GRMustacheContext.h"

#if NS_BLOCKS_AVAILABLE
typedef NSString *(^GRMustacheRenderingBlock)(GRMustacheSection *, id);
typedef NSString *(^GRMustacheRenderingBlock)(GRMustacheSection*, GRMustacheContext*);
id GRMustacheLambdaBlockMake(GRMustacheRenderingBlock block);

typedef NSString *(^GRMustacheRenderer)(id object) __attribute__((deprecated));
Expand Down
79 changes: 53 additions & 26 deletions README.md
Expand Up @@ -357,7 +357,7 @@ Imagine that, in the following template, you wish the `link` sections to be rend

<ul>
{{#people}}
<li>{{#link}}{{name}}{{#/link}}</li>
<li>{{#link}}{{name}}{{/link}}</li>
{{/people}}
</ul>

Expand All @@ -372,42 +372,36 @@ GRMustache provides you with two ways in order to achieve this behavior. The fir

### Lambda blocks

**NB: Lambda blocks are not available until MacOS 10.6, and iOS 4.0.**
*Note that lambda blocks are not available until MacOS 10.6, and iOS 4.0.*

You will provide in the context an object built with the GRMustacheLambdaBlockMake function. This function takes a block which returns the string that should be rendered, as in the example below:

// prepare our link lambda block
id linkLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, id context) {
id linkLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
return [NSString stringWithFormat:
@"<a href=\"/people/%@\">%@</a>",
[context valueForKey:@"id"], // id of person comes from current context
[section renderObject:context]] // link text comes from the natural rendering of the inner section
});

- `section` is an object which is able to render the section, provided with a context object.
- `section` is an object which represent the rendered section.
- `context` is the current rendering context.

Note that, in case you would need it, the `section` object has a `templateString` property, which contains the litteral inner section, unrendered (`{{tags}}` will not have been expanded).
The `[section renderObject:context]` expression evaluates with the rendering of the section with the given context.

The rendering now goes as usual, by providing objects for template keys:
In case you would need it, the `section` object has a `templateString` property, which contains the litteral inner section, unrendered (`{{tags}}` will not have been expanded).

The final rendering now goes as usual, by providing objects for template keys:

NSArray *people = ...;
[template renderObject:[NSDictionary dictionaryWithObjectsAndKeys:
linkLambda, @"link",
people, @"people",
nil]];

Note that lambda blocks can be used for whatever you may find relevant. You may, for instance, implement caching:

__block NSString *cache = nil;
id cacheLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, id context) {
if (cache == nil) { cache = [section renderObject:context]; }
return cache;
});

### Helper methods

Another way to execute code when rendering the `link` sections is to have the context implement the `linkSection:withObject:` selector (generally, implement a method whose name is the name of the section, to which you append `Section:withObject:`).
Another way to execute code when rendering the `link` sections is to have the context implement the `linkSection:withContext:` selector (generally, implement a method whose name is the name of the section, to which you append `Section:withContext:`).

No block is involved, and this technique works before MacOS 10.6, and iOS 4.0.

Expand All @@ -428,27 +422,27 @@ If your model object is designed as such:
@property NSString *id;
@end

You can declare the `linkSection:withObject` as a category of an object that will be used as a context.
You can declare the `linkSection:withContext` as a category of an object that will be used as a context.

You may use the Person object itself:

@implementation Person(GRMustache)
- (NSString*)linkSection:(GRMustacheSection *)section withObject:(id)object {
- (NSString*)linkSection:(GRMustacheSection *)section withContext:(GRMustacheContext *)context {
return [NSString stringWithFormat:
@"<a href=\"/people/%@\">%@</a>",
self.id, // id comes from self
[section renderObject:object]]; // link text comes from the natural rendering of the inner section
self.id, // id comes from self
[section renderObject:context]]; // link text comes from the natural rendering of the inner section
}
@end

You may also use the root DataModel object:

@implementation DataModel(GRMustache)
- (NSString*)linkSection:(GRMustacheSection *)section withObject:(id)object {
- (NSString*)linkSection:(GRMustacheSection *)section withContext:(GRMustacheContext *)context {
return [NSString stringWithFormat:
@"<a href=\"/people/%@\">%@</a>",
[object valueForKey:@"id"], // id of person comes from current context
[section renderObject:object]]; // link text comes from the natural rendering of the inner section
[object valueForKey:@"id"], // id of person comes from current context
[section renderObject:context]]; // link text comes from the natural rendering of the inner section
}
@end

Expand All @@ -470,11 +464,11 @@ GRMustache allows you to do that, too. First declare a container for your helper
@end

@implementation RenderingHelper
+ (NSString*)linkSection:(GRMustacheSection *)section withObject:(id)object {
+ (NSString*)linkSection:(GRMustacheSection *)section withContext:(GRMustacheContext *)context {
return [NSString stringWithFormat:
@"<a href=\"/people/%@\">%@</a>",
[object valueForKey:@"id"], // id of person comes from current context
[section renderObject:object]]; // link text comes from the natural rendering of the inner section
[object valueForKey:@"id"], // id of person comes from current context
[section renderObject:context]]; // link text comes from the natural rendering of the inner section
}
@end

Expand All @@ -483,12 +477,45 @@ Here we have written class methods because our helper doesn't carry any state. Y
Now let's introduce the GRMustacheContext class. Its role is to help you provide to the template a context which contains both data, and helper methods:

id dataModel = ...;
id context = [GRMustacheContext contextWithObjects: [RenderingHelper class], dataModel, nil];
GRMustacheContext *context = [GRMustacheContext contextWithObjects: [RenderingHelper class], dataModel, nil];

And now we can render:

[template renderObject:context];

#### Usages of lambdas and helpers

Lambdas and helpers can be used for whatever you may find relevant. We'll base our examples on lambda blocks, but the same patterns apply to helper methods.

You may, for instance, implement caching:

__block NSString *cache = nil;
GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
if (cache == nil) { cache = [section renderObject:context]; }
return cache;
});

You may render another context:

GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
return [section renderObject:[NSDictionary ...]];
});

You may render an extended context:

GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
return [section renderObject:[context contextByAddingObject:[NSDictionary ...]]];
});

You may implement debugging sections:

GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
NSLog(section.templateString); // log the unrendered section
NSLog([section renderObject:context]); // log the rendered section
return nil; // don't render anything
});


Template loaders
----------------

Expand Down
10 changes: 5 additions & 5 deletions Tests/v1.2/GRMustacheHelperTest.m
Expand Up @@ -29,15 +29,15 @@ @interface GRMustacheHelperTestContext: NSObject

@implementation GRMustacheHelperTestContext

- (NSString*)boldSection:(GRMustacheSection *)section withObject:(id)object {
return [NSString stringWithFormat:@"<b>%@</b>", [section renderObject:object]];
- (NSString*)boldSection:(GRMustacheSection *)section withContext:(GRMustacheContext *)context {
return [NSString stringWithFormat:@"<b>%@</b>", [section renderObject:context]];
}

+ (NSString*)linkSection:(GRMustacheSection *)section withObject:(id)object {
+ (NSString*)linkSection:(GRMustacheSection *)section withContext:(GRMustacheContext *)context {
return [NSString stringWithFormat:
@"<a href=\"/people/%@\">%@</a>",
[object valueForKey:@"id"],
[section renderObject:object]];
[context valueForKey:@"id"],
[section renderObject:context]];
}

@end
Expand Down
10 changes: 5 additions & 5 deletions Tests/v1.2/GRMustacheLambdaBlockTest.m
Expand Up @@ -27,7 +27,7 @@ @implementation GRMustacheLambdaBlockTest

- (void)testDoesntExecuteWhatItDoesntNeedTo {
__block BOOL dead = NO;
id dieLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, id context) {
id dieLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
dead = YES;
return @"foo";
});
Expand All @@ -42,15 +42,15 @@ - (void)testSectionsReturningLambdasGetCalledWithText {
__block int renderedCalls = 0;
__block NSString *cache = nil;

id renderedLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, id context) {
id renderedLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
if (cache == nil) {
renderedCalls++;
cache = [section renderObject:context];
}
return cache;
});

id notRenderedLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, id context) {
id notRenderedLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
return section.templateString;
});

Expand Down Expand Up @@ -78,7 +78,7 @@ - (void)testSectionsReturningLambdasGetCalledWithText {
- (void)testSectionLambdasCanRenderCurrentContextInSpecificTemplate {
NSString *templateString = @"{{#wrapper}}{{/wrapper}}";
GRMustacheTemplate *wrapperTemplate = [GRMustacheTemplate parseString:@"<b>{{name}}</b>" error:nil];
id wrapperLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, id context) {
id wrapperLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
return [wrapperTemplate renderObject:context];
});
NSDictionary *context = [NSDictionary dictionaryWithObjectsAndKeys:
Expand All @@ -91,7 +91,7 @@ - (void)testSectionLambdasCanRenderCurrentContextInSpecificTemplate {

- (void)testSectionLambdasCanReturnNil {
NSString *templateString = @"foo{{#wrapper}}{{/wrapper}}bar";
id wrapperLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, id context) {
id wrapperLambda = GRMustacheLambdaBlockMake(^(GRMustacheSection *section, GRMustacheContext *context) {
return (NSString *)nil;
});
NSDictionary *context = [NSDictionary dictionaryWithObject:wrapperLambda forKey:@"wrapper"];
Expand Down

0 comments on commit df2bdc8

Please sign in to comment.