Skip to content

HTTPS clone URL

Subversion checkout URL

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