Skip to content

HTTPS clone URL

Subversion checkout URL

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