Skip to content

Commit bcdb13a

Browse files
authored
Merge pull request #66 from cossssmin/loop-meta
Add loop metadata.
2 parents e99bb12 + 3cff5cb commit bcdb13a

File tree

7 files changed

+157
-0
lines changed

7 files changed

+157
-0
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,29 @@ The value of the `loop` attribute is not a pure expressions evaluation, and it d
246246

247247
So you don't need to declare all the available variables (in this case, the index is skipped), and the expressions after `in` doesn't need to be a local variable, it can be any expressions.
248248

249+
#### Loop meta
250+
251+
Inside a loop, you have access to a special `loop` object, which contains information about the loop currently being executed:
252+
253+
- `loop.index` - the current iteration of the loop (0 indexed)
254+
- `loop.remaining` - number of iterations until the end (0 indexed)
255+
- `loop.first` - boolean indicating if it's the first iteration
256+
- `loop.last` - boolean indicating if it's the last iteration
257+
- `loop.length` - total number of items
258+
259+
Example:
260+
261+
```html
262+
<each loop='item in [1,2,3]'>
263+
<li>Item value: {{ item }}</li>
264+
<li>Current iteration of the loop: {{ loop.index }}</li>
265+
<li>Number of iterations until the end: {{ loop.remaining }} </li>
266+
<li>This {{ loop.first ? 'is' : 'is not' }} the first iteration</li>
267+
<li>This {{ loop.last ? 'is' : 'is not' }} the last iteration</li>
268+
<li>Total number of items: {{ loop.length }}</li>
269+
</each>
270+
```
271+
249272
### Scopes
250273

251274
You can replace locals inside certain area wrapped in a `<scope>` tag. For example you can use it after [posthtml-include](https://github.com/posthtml/posthtml-include)

lib/index.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ function executeScope (scope, locals, node) {
5555
return walk({ locals: scope }, node.content)
5656
}
5757

58+
/**
59+
* @description Returns an object containing loop metadata
60+
*
61+
* @method getLoopMeta
62+
*
63+
* @param {Integer|Object} index Current iteration
64+
* @param {Object} target Object being iterated
65+
*
66+
* @return {Object} Object containing loop metadata
67+
*/
68+
function getLoopMeta (index, target) {
69+
index = Array.isArray(target) ? index : Object.keys(target).indexOf(index)
70+
const arr = Array.isArray(target) ? target : Object.keys(target)
71+
72+
return {
73+
index: index,
74+
remaining: arr.length - index - 1,
75+
first: arr.indexOf(arr[index]) === 0,
76+
last: index + 1 == arr.length,
77+
length: arr.length
78+
}
79+
}
80+
5881
/**
5982
* @author Jeff Escalante Denis (@jescalan),
6083
* Denis Malinochkin (mrmlnc),
@@ -324,10 +347,12 @@ function walk (opts, nodes) {
324347
// run the loop, different types of loops for arrays and objects
325348
if (Array.isArray(target)) {
326349
for (let index = 0; index < target.length; index++) {
350+
opts.locals.loop = getLoopMeta(index, target)
327351
m.push(executeLoop(keys, target[index], index, opts.locals, treeString))
328352
}
329353
} else {
330354
for (let key in target) {
355+
opts.locals.loop = getLoopMeta(key, target)
331356
m.push(executeLoop(keys, target[key], key, opts.locals, treeString))
332357
}
333358
}

test/expect/loop_metadata.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<ul>
2+
3+
<li>Item: 1</li>
4+
<li>Current iteration of the loop: 0</li>
5+
<li>Number of iterations until the end: 2 </li>
6+
<li>This is the first iteration</li>
7+
<li>This is not the last iteration</li>
8+
<li>Total number of items: 3</li>
9+
10+
<li>Item: 2</li>
11+
<li>Current iteration of the loop: 1</li>
12+
<li>Number of iterations until the end: 1 </li>
13+
<li>This is not the first iteration</li>
14+
<li>This is not the last iteration</li>
15+
<li>Total number of items: 3</li>
16+
17+
<li>Item: 3</li>
18+
<li>Current iteration of the loop: 2</li>
19+
<li>Number of iterations until the end: 0 </li>
20+
<li>This is not the first iteration</li>
21+
<li>This is the last iteration</li>
22+
<li>Total number of items: 3</li>
23+
24+
</ul>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<ul>
2+
3+
<li>foo: [1,2]</li>
4+
<li>There are 1 iterations remaining on the foo object</li>
5+
6+
<ul>
7+
<li>Nested item value: 1</li>
8+
<li>Current iteration of the loop: 0</li>
9+
<li>Number of iterations until the end: 1</li>
10+
<li>This is the first iteration</li>
11+
<li>This is not the last iteration</li>
12+
<li>Total number of items: 2</li>
13+
</ul>
14+
15+
<ul>
16+
<li>Nested item value: 2</li>
17+
<li>Current iteration of the loop: 1</li>
18+
<li>Number of iterations until the end: 0</li>
19+
<li>This is not the first iteration</li>
20+
<li>This is the last iteration</li>
21+
<li>Total number of items: 2</li>
22+
</ul>
23+
24+
25+
<li>bar: [3,4]</li>
26+
<li>There are 0 iterations remaining on the bar object</li>
27+
28+
<ul>
29+
<li>Nested item value: 3</li>
30+
<li>Current iteration of the loop: 0</li>
31+
<li>Number of iterations until the end: 1</li>
32+
<li>This is the first iteration</li>
33+
<li>This is not the last iteration</li>
34+
<li>Total number of items: 2</li>
35+
</ul>
36+
37+
<ul>
38+
<li>Nested item value: 4</li>
39+
<li>Current iteration of the loop: 1</li>
40+
<li>Number of iterations until the end: 0</li>
41+
<li>This is not the first iteration</li>
42+
<li>This is the last iteration</li>
43+
<li>Total number of items: 2</li>
44+
</ul>
45+
46+
47+
</ul>

test/fixtures/loop_metadata.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<ul>
2+
<each loop='item in items'>
3+
<li>Item: {{ item }}</li>
4+
<li>Current iteration of the loop: {{ loop.index }}</li>
5+
<li>Number of iterations until the end: {{ loop.remaining }} </li>
6+
<li>This {{ loop.first ? 'is' : 'is not' }} the first iteration</li>
7+
<li>This {{ loop.last ? 'is' : 'is not' }} the last iteration</li>
8+
<li>Total number of items: {{ loop.length }}</li>
9+
</each>
10+
</ul>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<ul>
2+
<each loop='value, key in items'>
3+
<li>{{ key }}: {{ JSON.stringify(value) }}</li>
4+
<li>There are {{ loop.remaining }} iterations remaining on the {{ key }} object</li>
5+
<each loop='item in value'>
6+
<ul>
7+
<li>Nested item value: {{ item }}</li>
8+
<li>Current iteration of the loop: {{ loop.index }}</li>
9+
<li>Number of iterations until the end: {{ loop.remaining }}</li>
10+
<li>This {{ loop.first ? 'is' : 'is not' }} the first iteration</li>
11+
<li>This {{ loop.last ? 'is' : 'is not' }} the last iteration</li>
12+
<li>Total number of items: {{ loop.length }}</li>
13+
</ul>
14+
</each>
15+
</each>
16+
</ul>

test/test-loops.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,15 @@ test('Loops - expression error', (t) => {
120120
t.is(err.message, 'Invalid or unexpected token')
121121
})
122122
})
123+
124+
test('Loops - metadata', (t) => {
125+
return process(t, 'loop_metadata', {
126+
locals: { items: [1, 2, 3] }
127+
})
128+
})
129+
130+
test('Loops - nested metadata', (t) => {
131+
return process(t, 'loop_nested_metadata', {
132+
locals: { items: { foo: [1, 2], bar: [3, 4] } }
133+
})
134+
})

0 commit comments

Comments
 (0)