Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 238 lines (182 sloc) 8.18 kb
2a4ef9c @groue Counters sample code
authored
1 [up](../sample_code.md), [next](counters.md)
18da0ef @groue Number formatting sample code
authored
2
3 Number formatting
4 =================
5
6 In a genuine Mustache way
7 -------------------------
8
9 Mustache is a simple template language. This is why there are so many [other Mustache implementations](https://github.com/defunkt/mustache/wiki/Other-Mustache-implementations).
10
11 If your goal is to design your templates so that they are compatible with those, the best way to format numbers is to have your data objects provide those formatted numbers.
12
13 ### NSDictionary data objects
14
15 Let's render the simple template:
16
17 {(percent)}
18
19 It's quite easy to put formatted numbers in a dictionary:
20
21 ```objc
22 // The number to render
23 NSNumber *number = [NSNumber numberWithFloat: 0.5]:
24
25 // An NSNumberFormatter knows how to format numbers
26 NSNumberFormatter *percentNumberFormatter = [[NSNumberFormatter alloc] init];
27 percentNumberFormatter.numberStyle = kCFNumberFormatterPercentStyle;
28 NSString *formattedNumber = [numberFormatter stringFromNumber:number];
29
30 // Render "50%"
31 NSDictionary *dictionary = [NSDictionary dictionaryWithObject:formattedNumber forKey:"percent"];
32 NSString *rendering = [template renderObject:dictionary];
33 ```
34
35 ### Custom data objects
36
37 Often, data comes from your model objects, not from an hand-crafted NSDictionary.
38
39 In this case, the best option is to declare a category on your model object, and implement a specific key that will output the formatted number:
40
41 ```objc
42 @interface MYModel(GRMustache)
43 @property (readonly) NSString *percent;
44 @end
45
46 @implementation MYModel(GRMustache)
47 - (NSString *)percent
48 {
49 NSNumberFormatter *percentNumberFormatter = [[NSNumberFormatter alloc] init];
50 percentNumberFormatter.numberStyle = kCFNumberFormatterPercentStyle;
51 return [numberFormatter stringFromNumber:self.number];
52 }
53 @end
54 ```
55
56 You would then render normally:
57
58 ```objc
59 // Render "50%"
60 MYModel *model = [MYModel modelWithNumber:0.5];
61 NSString *rendering = [template renderObject:model];
62 ```
63
64 In the GRMustache way
65 ---------------------
66
67 You may ask yourself, is it worth declaring dozens of stub properties just for formatting numbers?
68
1491a0c @groue no 404 please
authored
69 Before you answer "Of course not, I'm a lazy bastard, just gimme the code", beware that we will use below the [GRMustacheTemplateDelegate](../delegate.md) protocol. **It thus may be tedious or impossible for [other Mustache implementations](https://github.com/defunkt/mustache/wiki/Other-Mustache-implementations) to produce the same rendering.**
18da0ef @groue Number formatting sample code
authored
70
71 So check again the genuine Mustache way, above. Or keep on reading, now that you are warned.
72
d75c054 @groue Number formatting sample code
authored
73 The sample code below format all numbers in specific sections, without any cooperation from the data object. For instance, consider the following template, that uses a single `{{float}}` value:
18da0ef @groue Number formatting sample code
authored
74
75 raw: {{float}}
76
6e6db69 @groue Number formatting sample code
authored
77 {{#PERCENT_FORMAT}}
18da0ef @groue Number formatting sample code
authored
78 percent: {{float}}
6e6db69 @groue Number formatting sample code
authored
79 {{/PERCENT_FORMAT}}
18da0ef @groue Number formatting sample code
authored
80
6e6db69 @groue Number formatting sample code
authored
81 {{#DECIMAL_FORMAT}}
18da0ef @groue Number formatting sample code
authored
82 decimal: {{float}}
6e6db69 @groue Number formatting sample code
authored
83 {{/DECIMAL_FORMAT}}
18da0ef @groue Number formatting sample code
authored
84
d75c054 @groue Number formatting sample code
authored
85 It will render, on a French system:
18da0ef @groue Number formatting sample code
authored
86
87 raw: 0.5
6e6db69 @groue Number formatting sample code
authored
88 percent: 50 %
18da0ef @groue Number formatting sample code
authored
89 decimal: 0,5
90
91 Here is the rendering code:
92
93 ```objc
94 @implementation MYObject
95
96 - (NSString *)render
97 {
98 /**
6e6db69 @groue Number formatting sample code
authored
99 So, our goal is to format all numbers in the `{{#PERCENT_FORMAT}}` and
100 `{{#DECIMAL_FORMAT}}` sections of template.mustache.
18da0ef @groue Number formatting sample code
authored
101
102 First, we attach a NSNumberFormatter instance to those sections. This is
103 done by setting NSNumberFormatter instances to corresponding keys in the
104 data object that we will render. We'll use a NSDictionary for storing
2a4ef9c @groue Counters sample code
authored
105 the data, but you can use any other KVC-compliant container.
c8dc886 @groue Number formatting sample code
authored
106
107 The NSNumberFormatter instances will never be rendered: GRMustache
108 considers them as "true" objects that will trigger the rendering of the
109 sections they are attached to. We use them as plain sentinels.
18da0ef @groue Number formatting sample code
authored
110 */
111
112 NSMutableDictionary *data = [NSMutableDictionary dictionary];
113
6e6db69 @groue Number formatting sample code
authored
114 // Attach a percent NSNumberFormatter to the "PERCENT_FORMAT" key
18da0ef @groue Number formatting sample code
authored
115 NSNumberFormatter *percentNumberFormatter = [[NSNumberFormatter alloc] init];
116 percentNumberFormatter.numberStyle = kCFNumberFormatterPercentStyle;
6e6db69 @groue Number formatting sample code
authored
117 [data setObject:percentNumberFormatter forKey:@"PERCENT_FORMAT"];
18da0ef @groue Number formatting sample code
authored
118
6e6db69 @groue Number formatting sample code
authored
119 // Attach a decimal NSNumberFormatter to the "DECIMAL_FORMAT" key
18da0ef @groue Number formatting sample code
authored
120 NSNumberFormatter *decimalNumberFormatter = [[NSNumberFormatter alloc] init];
121 decimalNumberFormatter.numberStyle = kCFNumberFormatterDecimalStyle;
6e6db69 @groue Number formatting sample code
authored
122 [data setObject:decimalNumberFormatter forKey:@"DECIMAL_FORMAT"];
18da0ef @groue Number formatting sample code
authored
123
124
125 /**
126 Now we need a float to be rendered as the {{float}} tags of our
127 template.
128 */
129
130 // Attach a float to the "float" key
131 [data setObject:[NSNumber numberWithFloat:0.5] forKey:@"float"];
132
133
134 /**
135 Render. The formatting of numbers will happen in the
136 GRMustacheTemplateDelegate methods, hereafter.
137 */
138
139 GRMustacheTemplate *template = [GRMustacheTemplate templateFromResource:@"template" bundle:nil error:NULL];
140 template.delegate = self;
096d921 @groue Number formatting sample code
authored
141 return [template renderObject:data];
18da0ef @groue Number formatting sample code
authored
142 }
143 @end
144 ```
145
2a4ef9c @groue Counters sample code
authored
146 But we haven't told yet how those number formatters will be used for rendering the `{{float}}` tags.
18da0ef @groue Number formatting sample code
authored
147
148 We'll build a stack of number formatters. When GRMustache is about to render a section attached to a number formatter, we'll enqueue it. When the section has been rendered, we'll dequeue. Meanwhile, when we'll have to render a number, we'll format it with the last enqueued number formatter.
149
150 First declare a property that will hold the number formatters stack, and pose ourselves as a GRMustacheTemplateDelegate:
151
152 ```objc
2a4ef9c @groue Counters sample code
authored
153 @interface MYObject() <GRMustacheTemplateDelegate>
5d72dee @groue Number formatting sample code
authored
154 @property (nonatomic, strong) NSMutableArray *templateNumberFormatterStack;
18da0ef @groue Number formatting sample code
authored
155 @end
156 ```
157
158 And then implement the delegate methods:
159
160 ```objc
2a4ef9c @groue Counters sample code
authored
161 @implementation MYObject()
162 @synthesize templateNumberFormatterStack;
163
6ab8d70 @groue Number formatting sample code
authored
164 /**
165 This method is called right before the template start rendering.
166 */
18da0ef @groue Number formatting sample code
authored
167 - (void)templateWillRender:(GRMustacheTemplate *)template
168 {
169 /**
170 Prepare a stack of NSNumberFormatter objects.
171
172 Each time we'll enter a section that is attached to a NSNumberFormatter,
173 we'll enqueue this NSNumberFormatter in the stack. This is done in
174 [template:willRenderReturnValueOfInvocation:]
175 */
176 self.templateNumberFormatterStack = [NSMutableArray array];
177 }
178
6ab8d70 @groue Number formatting sample code
authored
179 /**
180 This method is called when the template is about to render a tag.
181 */
18da0ef @groue Number formatting sample code
authored
182 - (void)template:(GRMustacheTemplate *)template willRenderReturnValueOfInvocation:(GRMustacheInvocation *)invocation
183 {
184 /**
185 The invocation object tells us which object is about to be rendered.
186 */
187 if ([invocation.returnValue isKindOfClass:[NSNumberFormatter class]])
188 {
189 /**
67efa53 @groue Number formatting sample code
authored
190 If it is a NSNumberFormatter, enqueue it in
191 templateNumberFormatterStack.
18da0ef @groue Number formatting sample code
authored
192 */
193 [self.templateNumberFormatterStack addObject:invocation.returnValue];
194 }
195 else if (self.templateNumberFormatterStack.count > 0 && [invocation.returnValue isKindOfClass:[NSNumber class]])
196 {
197 /**
67efa53 @groue Number formatting sample code
authored
198 If it is a NSNumber, and if our templateNumberFormatterStack is not
199 empty, use the top NSNumberFormatter to format the number.
18da0ef @groue Number formatting sample code
authored
200
67efa53 @groue Number formatting sample code
authored
201 Set the invocation's returnValue: this is the object that will be
18da0ef @groue Number formatting sample code
authored
202 rendered.
203 */
204 NSNumberFormatter *numberFormatter = self.templateNumberFormatterStack.lastObject;
67efa53 @groue Number formatting sample code
authored
205 NSNumber *number = invocation.returnValue;
206 invocation.returnValue = [numberFormatter stringFromNumber:number];
18da0ef @groue Number formatting sample code
authored
207 }
208 }
209
6ab8d70 @groue Number formatting sample code
authored
210 /**
211 This method is called right after the template has rendered a tag.
212 */
18da0ef @groue Number formatting sample code
authored
213 - (void)template:(GRMustacheTemplate *)template didRenderReturnValueOfInvocation:(GRMustacheInvocation *)invocation
214 {
215 /**
216 Make sure we dequeue NSNumberFormatters when we leave their scope.
217 */
218 if ([invocation.returnValue isKindOfClass:[NSNumberFormatter class]])
219 {
220 [self.templateNumberFormatterStack removeLastObject];
221 }
222 }
223
6ab8d70 @groue Number formatting sample code
authored
224 /**
225 This method is called right after the template has finished rendering.
226 */
18da0ef @groue Number formatting sample code
authored
227 - (void)templateDidRender:(GRMustacheTemplate *)template
228 {
229 /**
230 Final cleanup: release the stack created in templateWillRender:
231 */
232 self.templateNumberFormatterStack = nil;
233 }
234 @end
235 ```
236
2a4ef9c @groue Counters sample code
authored
237 [up](../sample_code.md), [next](counters.md)
Something went wrong with that request. Please try again.