Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 296 lines (235 sloc) 9.898 kB
2e61bf9 @groue Fix internal guide links
authored
1 [up](../../../../tree/master/Guides/sample_code), [next](../forking.md)
438ce09 @groue indexes.md guide
authored
2
3 Indexes
4 =======
5
e741dbc @groue Rewrite indexes sample code with filters (WIP)
authored
6 In a genuine Mustache way
7 -------------------------
8
438ce09 @groue indexes.md guide
authored
9 Mustache is a simple template language. Its [specification](https://github.com/mustache/spec) does not provide any built-in access to loop indexes. It does not provide any way to render a section at the beginning of the loop, and another section at the end. It does not help you render different sections for odd and even indexes.
10
11 If your goal is to design your templates so that they are compatible with [other Mustache implementations](https://github.com/defunkt/mustache/wiki/Other-Mustache-implementations), the best way to render indices and provide custom looping logic is to have each of your data objects provide with its index, regardless of how tedious it may be for you to prepare the rendered data.
12
e741dbc @groue Rewrite indexes sample code with filters (WIP)
authored
13 For instance, instead of `[ { name:'Alice' }, { name:'Bob' } ]`, you would provide: `[ { name:'Alice', position:1, isFirst:true, isOdd:true }, { name:'Bob', position:2, isFirst:false, isOdd:false } ]`.
438ce09 @groue indexes.md guide
authored
14
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
15
16 GRMustache solution: filters
17 ----------------------------
438ce09 @groue indexes.md guide
authored
18
19f0f57 @groue Link from guide to sample project
authored
19 **[Download the code](../../../../tree/master/Guides/sample_code/indexes)**
3f50ab0 @groue Links to sample code projects hosted at https://github.com/groue/GRMu…
authored
20
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
21 The [GRMustacheFilter](../filter.md) protocol can help you extend the mustache language, and avoid preparing your data.
438ce09 @groue indexes.md guide
authored
22
657c934 @groue Mandatory warning against other Mustache implementations
authored
23 **However, it may be tedious or impossible for other Mustache implementations to produce the same rendering.**
24
25 So check again the genuine Mustache way, above. Or keep on reading, now that you are warned.
26
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
27 Below we'll implement the special keys `position`, `isFirst`, and `isOdd`. We'll render the following template:
438ce09 @groue indexes.md guide
authored
28
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
29 {{% FILTERS}}
438ce09 @groue indexes.md guide
authored
30 <ul>
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
31 {{# withPosition(people) }}
32 <li class="{{#isOdd}}odd{{/isOdd}} {{#isFirst}}first{{/isFirst}}">
33 {{ position }}:{{ name }}
34 </li>
35 {{/ withPosition(people) }}
438ce09 @groue indexes.md guide
authored
36 </ul>
37
38 We expect, on output, the following rendering:
39
40 <ul>
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
41 <li class="odd first">
42 1:Alice
43 </li>
44 <li class=" ">
45 2:Bob
46 </li>
47 <li class="odd ">
48 3:Craig
49 </li>
438ce09 @groue indexes.md guide
authored
50 </ul>
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
51
52
53 Our people array will be a plain array filled with plain people who don't know anything but their name. The support for the special positional keys will be entirely done by a filter object.
438ce09 @groue indexes.md guide
authored
54
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
55 We can thus focus on the two subjects separately.
56
57 Let's first assume that the class PositionFilter is already written. Here is its documentation:
438ce09 @groue indexes.md guide
authored
58
59 ```objc
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
60 /**
61 * A GRMustache filter that, given an array, returns another array made of
62 * objects that forward all keys to the original array items, but the following:
63 *
64 * - position: returns the 1-based index of the item
65 * - isOdd: returns YES if the position of the item is odd
66 * - isFirst: returns YES if the item is at position 1
67 */
68 @interface PositionFilter : NSObject<GRMustacheFilter>
69 @end
70 ```
71
ea8f1a8 @groue Rewrite indexes sample code with filters (WIP)
authored
72 Well, it looks quite a good fit for our task: if we provide to this filter an array of people, it will return an array of objects that will be able to tell a template their position, and for all other keys, will give the original person's value.
438ce09 @groue indexes.md guide
authored
73
ea8f1a8 @groue Rewrite indexes sample code with filters (WIP)
authored
74 We have everything we need to render our template:
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
75
76 ```objc
438ce09 @groue indexes.md guide
authored
77 - (NSString *)render
78 {
79 /**
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
80 * Our template want to render the `people` array with support for various
81 * positional information on top of regular keys fetched from each person
82 * of the array:
83 *
84 * - position: the 1-based index of the person
85 * - isOdd: YES if the position of the person is odd
86 * - isFirst: YES if the person is the first of the people array.
87 *
88 * This is typically a job for filters: we'll define the `withPosition`
89 * filters to be an instance of the PositionFilter class. That class has
90 * been implemented so that it provides us with the extra keys for free.
91 *
92 * For now, we just declare our template. The initial {{%FILTERS}} pragma
93 * tag tells GRMustache to trigger support for filters, which are an
94 * extension to the Mustache specification.
95 */
96 NSString *templateString = @"{{% FILTERS}}"
97 @"<ul>\n"
98 @"{{# withPosition(people) }}"
99 @" <li class=\"{{# isOdd }}odd{{/ isOdd }} {{# isFirst }}first{{/ isFirst }}\">\n"
100 @" {{ position }}:{{ name }}\n"
101 @" </li>\n"
102 @"{{/ withPosition(people) }}"
103 @"</ul>";
104 GRMustacheTemplate *template = [GRMustacheTemplate templateFromString:templateString error:NULL];
105
106
107 /**
108 * Now we have to define this filter. The PositionFilter class is already
109 * there, ready to be instanciated:
110 */
111
112 PositionFilter *positionFilter = [[PositionFilter alloc] init];
113
114
115 /**
116 * GRMustache does not load filters from the rendered data, but from a
117 * specific filters container.
118 *
119 * We'll use a NSDictionary for attaching positionFilter to the
120 * "withPosition" key, but you can use any other KVC-compliant container.
121 */
122
123 NSDictionary *filters = [NSDictionary dictionaryWithObject:positionFilter forKey:@"withPosition"];
124
125
126 /**
127 * Now we need an array of people that will be sequentially rendered by the
128 * `{{# withPosition(people) }}...{{/ withPosition(people) }}` section.
35162b7 @groue Hard wrap documentation to 80 columns
authored
129 *
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
130 * We'll use a NSDictionary for storing the array, but as always you can use
131 * any other KVC-compliant container.
438ce09 @groue indexes.md guide
authored
132 */
133
134 Person *alice = [Person personWithName:@"Alice"];
135 Person *bob = [Person personWithName:@"Bob"];
136 Person *craig = [Person personWithName:@"Craig"];
137 NSArray *people = [NSArray arrayWithObjects: alice, bob, craig, nil];
138 NSDictionary *data = [NSDictionary dictionaryWithObject:people forKey:@"people"];
139
140
141 /**
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
142 * Render.
438ce09 @groue indexes.md guide
authored
143 */
144
bc112a1 @groue Rewrite indexes sample code with filters (WIP)
authored
145 return [template renderObject:data withFilters:filters];
438ce09 @groue indexes.md guide
authored
146 }
147 ```
148
e741dbc @groue Rewrite indexes sample code with filters (WIP)
authored
149 Now it's time to implement this nifty PositionFilter filter.
150
151 We have already seen above its declaration: it's simply a class that conforms to the GRMustacheFilter protocol:
152
153 ```objc
154 @interface PositionFilter : NSObject<GRMustacheFilter>
155 @end
156 ```
157
158 As such, it must implement the `transformedValue:` method:
159
160 ```objc
161 @implementation PositionFilter
162 - (id)transformedValue:(id)object
163 {
164 return ...;
165 }
166 ```
167
168 Provided with an array, it returns another array filled with objects "that forward all keys to the original array items, but the following: position, isOdd, isFirst". We have to implement those objects as well. In order to do their job, they have to now both the original item in the original array, and its index. Here is the declaration of those objects:
169
170 ```objc
171 /**
172 * PositionFilterItem's responsability is, given an array and an index, to
173 * forward to the original item in the array all keys but:
174 *
175 * - position: returns the 1-based index of the item
176 * - isOdd: returns YES if the position of the item is odd
177 * - isFirst: returns YES if the item is at position 1
178 *
179 * All other keys are forwared to the original item.
180 */
181 @interface PositionFilterItem : NSObject
182 @property (nonatomic, readonly) NSUInteger position;
183 @property (nonatomic, readonly) BOOL isFirst;
184 @property (nonatomic, readonly) BOOL isOdd;
185 - (id)initWithObjectAtIndex:(NSUInteger)index inArray:(NSArray *)array;
186 @end
187 ```
188
189 Now we can implement the PositionFilter class itself:
190
191 ```objc
192 @implementation PositionFilter
193
194 /**
195 * GRMustacheFilter protocol required method
196 */
197 - (id)transformedValue:(id)object
198 {
199 /**
200 * Let's first validate the input: we can only filter arrays.
201 */
202
203 NSAssert([object isKindOfClass:[NSArray class]], @"Not an NSArray");
204 NSArray *array = (NSArray *)object;
205
206
207 /**
208 * Let's return a new array made of PositionFilterItem instances.
209 * They will provide the `position`, `isOdd` and `isFirst` keys while
210 * letting original array items provide the other keys.
211 */
212
213 NSMutableArray *replacementArray = [NSMutableArray arrayWithCapacity:array.count];
214 [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
215 PositionFilterItem *item = [[PositionFilterItem alloc] initWithObjectAtIndex:idx inArray:array];
216 [replacementArray addObject:item];
217 }];
218 return replacementArray;
219 }
220
221 @end
222 ```
223
224 And finally, write the PositionFilterItem implementation:
225
226 ```objc
227 @implementation PositionFilterItem {
228 /**
229 * The original 0-based index and the array of original items are stored in
230 * ivars without any exposed property: we do not want GRMustache to render
231 * {{ index }} or {{ array }}
232 */
233 NSUInteger _index;
234 NSArray *_array;
235 }
438ce09 @groue indexes.md guide
authored
236
e741dbc @groue Rewrite indexes sample code with filters (WIP)
authored
237 - (id)initWithObjectAtIndex:(NSUInteger)index inArray:(NSArray *)array
238 {
239 self = [super init];
240 if (self) {
241 _index = index;
242 _array = array;
243 }
244 return self;
245 }
438ce09 @groue indexes.md guide
authored
246
e741dbc @groue Rewrite indexes sample code with filters (WIP)
authored
247 /**
248 * The implementation of `description` is required so that whenever GRMustache
249 * wants to render the original item itself (with a `{{ . }}` tag, for
250 * instance).
251 */
252 - (NSString *)description
253 {
254 id originalObject = [_array objectAtIndex:_index];
255 return [originalObject description];
256 }
438ce09 @groue indexes.md guide
authored
257
e741dbc @groue Rewrite indexes sample code with filters (WIP)
authored
258 /**
259 * Support for `{{position}}`: return a 1-based index.
260 */
261 - (NSUInteger)position
262 {
263 return _index + 1;
264 }
265
266 /**
267 * Support for `{{isFirst}}`: return YES if element is the first
268 */
269 - (BOOL)isFirst
270 {
271 return _index == 0;
272 }
273
274 /**
275 * Support for `{{isOdd}}`: return YES if element's position is odd.
276 */
277 - (BOOL)isOdd
278 {
279 return (_index % 2) == 0;
280 }
281
282 /**
283 * Support for other keys: forward to original array element
284 */
285 - (id)valueForUndefinedKey:(NSString *)key
286 {
287 id originalObject = [_array objectAtIndex:_index];
288 return [originalObject valueForKey:key];
289 }
290
291 @end
292 ```
438ce09 @groue indexes.md guide
authored
293
c1aaf13 @groue More links from guides to sample projects
authored
294 **[Download the code](../../../../tree/master/Guides/sample_code/indexes)**
295
2e61bf9 @groue Fix internal guide links
authored
296 [up](../../../../tree/master/Guides/sample_code), [next](../forking.md)
Something went wrong with that request. Please try again.