groue / GRMustache

Flexible and production-ready Mustache templates for MacOS Cocoa and iOS
http://mustache.github.com/
MIT License
1.44k stars 190 forks source link

Issues with GRMustacheTagDelegate #34

Closed sl1m3d closed 11 years ago

sl1m3d commented 11 years ago

Two separate but similar issues ->

Sample code:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    id data = @{ @"id" : @"101",
    @"name" : @{ @"en" : @"Apple", @"fr" : @"Pomme"},
    @"description" :  @{ @"en" : @"Apples are delicious", @"fr" : @"Les pommes sont délicieux"},
    @"sizes" : @[@"little", @"medium", @"small"],
    @"colors" : @[@{@"en" : @"Red", @"fr": @"rouge"}, @{@"en" : @"green", @"fr" : @"vert"}, @{@"en": @"yellow", @"fr" : @"jaune"}]
    };

    NSString* template = @"\n\nId:{{id}}\n\n"
                        "Name: {{#-localizeData}}{{name}}{{/-localizeData}}\n\n"
                        "Description: {{#-localizeData}}{{description}}{{/-localizeData}}\n\n"
                        "Sizes: {{#sizes}}{{.}} {{/sizes}}\n\n"
                        "Colors: {{#colors}}{{#-localizeData}}{{.}}{{/-localizeData}}{{/colors}}\n\n";

    id helpers = @{@"-localizeData" : [MustacheLocalizer new]};
    NSError *error = nil;
    GRMustacheTemplate* mustacheTemplate = [GRMustacheTemplate templateFromString:template error:&error];
    NSString *text = [mustacheTemplate renderObjectsFromArray:@[helpers,data] error:&error];

    UITextView* txtView = [[UITextView alloc] initWithFrame:CGRectInset(self.window.bounds, 20, 20)];
    txtView.text = text;
    [self.window addSubview:txtView];
    return YES;
}

and the helper

@interface MustacheLocalizer ()<GRMustacheTagDelegate>
@end
@implementation MustacheLocalizer 
-(id)mustacheTag:(GRMustacheTag *)tag willRenderObject:(id)object {
    if ([object isKindOfClass:[NSDictionary class]])
        return object[@"en"];
    return object;
}
//adding this fixes the first issue with description
//-(NSString*)description {
//  return nil;
//}
@end

When wrapping {{#-localizeData}} around {{description}}, the description of the class is returned.

When using the {{.}}, the delegate gives us the MustacheLocalizer object instead of the dictionary.

groue commented 11 years ago

I will close this issue as "not an issue", since everything works as explained in the GRMustache documentation:

Inside the {{#-localizeData}}{{description}}{{/-localizeData}} section, the localizer is at the top of the context stack. The rendering engine invokes valueForKey: on it, finds the default implementation of the description method, and renders it. When you override it so that it returns nil, the engine keeps on digging for a value, and eventually finds the description key of the dictionary.

GRMustache rules are simple. It is not an option to make them more complex just for you.

However, GRMustache won't let you down. But you need to help yourself:

I advise you to read the Rendering Objects Guide, and read carefully the explanations about how and when the context stack is extended. You will come up with something like:

@interface MustacheLocalizer ()<GRMustacheTagDelegate, GRMustacheRendering>
@end

@implementation MustacheLocalizer
- (id)mustacheTag:(GRMustacheTag *)tag willRenderObject:(id)object {
    if ([object isKindOfClass:[NSDictionary class]])
        return object[@"en"];
    return object;
}

- (NSString *)renderForMustacheTag:(GRMustacheTag *)tag context:(GRMustacheContext *)context HTMLSafe:(BOOL *)HTMLSafe error:(NSError **)error {
    // Avoid having self enter the context stack, and simply render the Mustache tag.
    render [tag renderContentWithContext:context HTMLSafe:HTMLSafe error:error];
}
@end
groue commented 11 years ago

Actually, the following code is closer to your needs (as a rendering object, your localizer class takes full responsibility of its rendering, and must explicitly enter the rendering context as a tag delegate):

@interface MustacheLocalizer ()<GRMustacheTagDelegate, GRMustacheRendering>
@end

@implementation MustacheLocalizer
- (id)mustacheTag:(GRMustacheTag *)tag willRenderObject:(id)object {
    if ([object isKindOfClass:[NSDictionary class]])
        return object[@"en"];
    return object;
}

- (NSString *)renderForMustacheTag:(GRMustacheTag *)tag context:(GRMustacheContext *)context HTMLSafe:(BOOL *)HTMLSafe error:(NSError **)error {
    // Have self enter the context as a tag delegate - but not in the context stack
    context = [context contextByAddingTagDelegate:self];
    // Render the Mustache tag.
    return [tag renderContentWithContext:context HTMLSafe:HTMLSafe error:error];
}
@end
groue commented 11 years ago

The "Tag Delegates as Cross-Platform Filters" guide now documents how to avoid the context stack pollution: https://github.com/groue/GRMustache/blob/master/Guides/delegate.md#tag-delegates-as-cross-platform-filters

sl1m3d commented 11 years ago

I have a special object that wraps an array item (similar to what is in https://github.com/groue/GRMustache/blob/master/Guides/sample_code/indexes.md) that I needed to account for

Ultimately this is what I wound up doing:

- (NSString *)renderForMustacheTag:(GRMustacheTag *)tag context:(GRMustacheContext *)context HTMLSafe:(BOOL *)HTMLSafe error:(NSError **)error {
    return [tag renderContentWithContext:[context contextByAddingTagDelegate:self] HTMLSafe:HTMLSafe error:error];
}

-(id)mustacheTag:(GRMustacheTag *)tag willRenderObject:(id)object
{
    id obj = nil;
    if ([object isKindOfClass:[IndexedArrayItem class]])
        obj = [(IndexedArrayItem*)object boxedObject] ? : object;
    else
        obj = object;
    if ([obj isKindOfClass:[NSDictionary class]])
        return obj[@"en"];
    return object;
}
groue commented 11 years ago

So Rob, does it work?

sl1m3d commented 11 years ago

Yes, what i posted above seems a bit smelly, but it works for now. I think I need to delegate back to the IndexedArrayItem instead, but IndexedArrayItem handles everything with valueForKey and doesn't conform to the GRMustache protocols.