Skip to content
Newer
Older
100644 265 lines (208 sloc) 8.92 KB
da9cdeb @groue indexes.md guide
authored
1 [up](../sample_code.md), [next](indexes.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
35162b7 @groue Hard wrap documentation to 80 columns
authored
37 Often, data comes from your model objects, not from a hand-crafted NSDictionary.
18da0ef @groue Number formatting sample code
authored
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
c1aaf13 @groue More links from guides to sample projects
authored
67 **[Download the code](../../../../tree/master/Guides/sample_code/number_formatting)**
3f50ab0 @groue Links to sample code projects hosted at https://github.com/groue/GRMu…
authored
68
18da0ef @groue Number formatting sample code
authored
69 You may ask yourself, is it worth declaring dozens of stub properties just for formatting numbers?
70
1491a0c @groue no 404 please
authored
71 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
72
73 So check again the genuine Mustache way, above. Or keep on reading, now that you are warned.
74
d75c054 @groue Number formatting sample code
authored
75 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
76
77 raw: {{float}}
78
6e6db69 @groue Number formatting sample code
authored
79 {{#PERCENT_FORMAT}}
18da0ef @groue Number formatting sample code
authored
80 percent: {{float}}
6e6db69 @groue Number formatting sample code
authored
81 {{/PERCENT_FORMAT}}
18da0ef @groue Number formatting sample code
authored
82
6e6db69 @groue Number formatting sample code
authored
83 {{#DECIMAL_FORMAT}}
18da0ef @groue Number formatting sample code
authored
84 decimal: {{float}}
6e6db69 @groue Number formatting sample code
authored
85 {{/DECIMAL_FORMAT}}
18da0ef @groue Number formatting sample code
authored
86
d75c054 @groue Number formatting sample code
authored
87 It will render, on a French system:
18da0ef @groue Number formatting sample code
authored
88
89 raw: 0.5
6e6db69 @groue Number formatting sample code
authored
90 percent: 50 %
18da0ef @groue Number formatting sample code
authored
91 decimal: 0,5
92
93 Here is the rendering code:
94
95 ```objc
96 @implementation MYObject
97
98 - (NSString *)render
99 {
100 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
101 * So, our goal is to format all numbers in the `{{#PERCENT_FORMAT}}` and
102 * `{{#DECIMAL_FORMAT}}` sections of template.mustache.
103 *
104 * First, we attach a NSNumberFormatter instance to those sections. This is
105 * done by setting NSNumberFormatter instances to corresponding keys in the
106 * data object that we will render. We'll use a NSDictionary for storing
107 * the data, but you can use any other KVC-compliant container.
108 *
109 * The NSNumberFormatter instances will never be rendered: GRMustache
110 * considers them as "true" objects that will trigger the rendering of the
111 * sections they are attached to. We use them as plain sentinels.
18da0ef @groue Number formatting sample code
authored
112 */
113
114 NSMutableDictionary *data = [NSMutableDictionary dictionary];
115
6e6db69 @groue Number formatting sample code
authored
116 // Attach a percent NSNumberFormatter to the "PERCENT_FORMAT" key
18da0ef @groue Number formatting sample code
authored
117 NSNumberFormatter *percentNumberFormatter = [[NSNumberFormatter alloc] init];
118 percentNumberFormatter.numberStyle = kCFNumberFormatterPercentStyle;
6e6db69 @groue Number formatting sample code
authored
119 [data setObject:percentNumberFormatter forKey:@"PERCENT_FORMAT"];
18da0ef @groue Number formatting sample code
authored
120
6e6db69 @groue Number formatting sample code
authored
121 // Attach a decimal NSNumberFormatter to the "DECIMAL_FORMAT" key
18da0ef @groue Number formatting sample code
authored
122 NSNumberFormatter *decimalNumberFormatter = [[NSNumberFormatter alloc] init];
123 decimalNumberFormatter.numberStyle = kCFNumberFormatterDecimalStyle;
6e6db69 @groue Number formatting sample code
authored
124 [data setObject:decimalNumberFormatter forKey:@"DECIMAL_FORMAT"];
18da0ef @groue Number formatting sample code
authored
125
126
127 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
128 * Now we need a float to be rendered as the {{float}} tags of our
129 * template.
18da0ef @groue Number formatting sample code
authored
130 */
131
132 // Attach a float to the "float" key
133 [data setObject:[NSNumber numberWithFloat:0.5] forKey:@"float"];
134
135
136 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
137 * Render. The formatting of numbers will happen in the
138 * GRMustacheTemplateDelegate methods, hereafter.
18da0ef @groue Number formatting sample code
authored
139 */
140
141 GRMustacheTemplate *template = [GRMustacheTemplate templateFromResource:@"template" bundle:nil error:NULL];
142 template.delegate = self;
096d921 @groue Number formatting sample code
authored
143 return [template renderObject:data];
18da0ef @groue Number formatting sample code
authored
144 }
145 @end
146 ```
147
2a4ef9c @groue Counters sample code
authored
148 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
149
150 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.
151
152 First declare a property that will hold the number formatters stack, and pose ourselves as a GRMustacheTemplateDelegate:
153
154 ```objc
2a4ef9c @groue Counters sample code
authored
155 @interface MYObject() <GRMustacheTemplateDelegate>
5d72dee @groue Number formatting sample code
authored
156 @property (nonatomic, strong) NSMutableArray *templateNumberFormatterStack;
18da0ef @groue Number formatting sample code
authored
157 @end
158 ```
159
160 And then implement the delegate methods:
161
162 ```objc
2a4ef9c @groue Counters sample code
authored
163 @implementation MYObject()
164 @synthesize templateNumberFormatterStack;
165
6ab8d70 @groue Number formatting sample code
authored
166 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
167 * This method is called right before the template start rendering.
6ab8d70 @groue Number formatting sample code
authored
168 */
18da0ef @groue Number formatting sample code
authored
169 - (void)templateWillRender:(GRMustacheTemplate *)template
170 {
171 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
172 * Prepare a stack of NSNumberFormatter objects.
173 *
174 * Each time we'll enter a section that is attached to a NSNumberFormatter,
175 * we'll enqueue this NSNumberFormatter in the stack. This is done in
176 * [template:willInterpretReturnValueOfInvocation:as:]
18da0ef @groue Number formatting sample code
authored
177 */
178 self.templateNumberFormatterStack = [NSMutableArray array];
179 }
180
6ab8d70 @groue Number formatting sample code
authored
181 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
182 * This method is called when the template is about to render a tag.
6ab8d70 @groue Number formatting sample code
authored
183 */
459f57b @groue v4.1.0
authored
184 - (void)template:(GRMustacheTemplate *)template willInterpretReturnValueOfInvocation:(GRMustacheInvocation *)invocation as:(GRMustacheInterpretation)interpretation
18da0ef @groue Number formatting sample code
authored
185 {
186 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
187 * The invocation object tells us which object is about to be rendered.
188 *
189 * If it is a NSNumberFormatter, enqueue it in templateNumberFormatterStack,
190 * and return.
18da0ef @groue Number formatting sample code
authored
191 */
192 if ([invocation.returnValue isKindOfClass:[NSNumberFormatter class]])
193 {
194 [self.templateNumberFormatterStack addObject:invocation.returnValue];
459f57b @groue v4.1.0
authored
195 return;
18da0ef @groue Number formatting sample code
authored
196 }
459f57b @groue v4.1.0
authored
197
35162b7 @groue Hard wrap documentation to 80 columns
authored
198 /**
199 * We actually only format numbers for variable tags such as `{{name}}`. We
200 * must carefully avoid messing with sections: they as well can be provided
201 * with numbers, that they interpret as booleans. We surely do not want to
202 * convert NO to the truthy @"0%" string...
203 *
204 * So let's ignore sections, and return.
205 */
459f57b @groue v4.1.0
authored
206 if (interpretation == GRMustacheInterpretationSection)
207 {
208 return;
209 }
210
35162b7 @groue Hard wrap documentation to 80 columns
authored
211 /**
212 * If our number formatter stack is empty, we can not format anything: let's
213 * return.
214 */
459f57b @groue v4.1.0
authored
215 if (self.templateNumberFormatterStack.count == 0)
216 {
217 return;
218 }
219
35162b7 @groue Hard wrap documentation to 80 columns
authored
220 /**
221 * There we are: invocation's return value is a NSNumber, and our
222 * templateNumberFormatterStack is not empty.
223 *
224 * Let's use the top NSNumberFormatter to format this number, and set the
225 * invocation's returnValue: this is the object that will be rendered.
226 */
459f57b @groue v4.1.0
authored
227 if ([invocation.returnValue isKindOfClass:[NSNumber class]])
18da0ef @groue Number formatting sample code
authored
228 {
229 NSNumberFormatter *numberFormatter = self.templateNumberFormatterStack.lastObject;
67efa53 @groue Number formatting sample code
authored
230 NSNumber *number = invocation.returnValue;
231 invocation.returnValue = [numberFormatter stringFromNumber:number];
18da0ef @groue Number formatting sample code
authored
232 }
233 }
234
6ab8d70 @groue Number formatting sample code
authored
235 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
236 * This method is called right after the template has rendered a tag.
6ab8d70 @groue Number formatting sample code
authored
237 */
459f57b @groue v4.1.0
authored
238 - (void)template:(GRMustacheTemplate *)template didInterpretReturnValueOfInvocation:(GRMustacheInvocation *)invocation as:(GRMustacheInterpretation)interpretation
18da0ef @groue Number formatting sample code
authored
239 {
240 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
241 * Make sure we dequeue NSNumberFormatters when we leave their scope.
18da0ef @groue Number formatting sample code
authored
242 */
243 if ([invocation.returnValue isKindOfClass:[NSNumberFormatter class]])
244 {
245 [self.templateNumberFormatterStack removeLastObject];
246 }
247 }
248
6ab8d70 @groue Number formatting sample code
authored
249 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
250 * This method is called right after the template has finished rendering.
6ab8d70 @groue Number formatting sample code
authored
251 */
18da0ef @groue Number formatting sample code
authored
252 - (void)templateDidRender:(GRMustacheTemplate *)template
253 {
254 /**
35162b7 @groue Hard wrap documentation to 80 columns
authored
255 * Final cleanup: release the stack created in templateWillRender:
18da0ef @groue Number formatting sample code
authored
256 */
257 self.templateNumberFormatterStack = nil;
258 }
259 @end
260 ```
261
c1aaf13 @groue More links from guides to sample projects
authored
262 **[Download the code](../../../../tree/master/Guides/sample_code/number_formatting)**
263
da9cdeb @groue indexes.md guide
authored
264 [up](../sample_code.md), [next](indexes.md)
Something went wrong with that request. Please try again.