Skip to content

Commit e0166e3

Browse files
author
Jeff Escalante
committed
cleanup, switch value and key positions for object loop
1 parent acd06dc commit e0166e3

File tree

4 files changed

+38
-30
lines changed

4 files changed

+38
-30
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ Output:
192192
And an example using an object:
193193

194194
```html
195-
<each loop="key, value in anObject">
195+
<each loop="value, key in anObject">
196196
<p>{{ key }}: {{ value }}</p>
197197
</each>
198198
```

lib/index.js

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,16 @@ function walk (opts, nodes) {
125125

126126
// parse loops
127127
if (node.tag === loops[0]) {
128+
// handle syntax error
128129
if (!(node.attrs && node.attrs.loop)) {
129130
throw new Error(`the "${conditionals[1]}" tag must have a "loop" attribute`)
130131
}
132+
131133
// parse the "loop" param
132134
const loopParams = parseLoopStatement(node.attrs.loop)
133135
const target = vm.runInContext(loopParams.expression, ctx)
134136

137+
// handle additional syntax errors
135138
if (typeof target !== 'object') {
136139
throw new Error('You must provide an array or object to loop through')
137140
}
@@ -140,41 +143,19 @@ function walk (opts, nodes) {
140143
throw new Error('You must provide at least one loop argument')
141144
}
142145

146+
// run the loop, different types of loops for arrays and objects
143147
if (Array.isArray(target)) {
144148
for (let index = 0; index < target.length; index++) {
145-
const value = target[index]
146-
// add value and optional index loop locals
147-
const scopedLocals = {}
148-
scopedLocals[loopParams.keys[0]] = value
149-
if (loopParams.keys[1]) scopedLocals[loopParams.keys[1]] = index
150-
// merge nondestructively into existing locals
151-
const scopedOptions = merge(opts, { locals: scopedLocals })
152-
// provide the modified options to the content evaluation
153-
// we need to clone the node because the normal operation modifies
154-
// the node directly
155-
const content = cloneDeep(node.content)
156-
const res = walk(scopedOptions, content)
157-
m.push(res)
149+
m.push(executeLoop(loopParams.keys, target[index], index, node, opts))
158150
}
159-
return m
160151
} else {
161152
for (let key in target) {
162-
const value = target[key]
163-
// add item and optional index loop locals
164-
const scopedLocals = {}
165-
scopedLocals[loopParams.keys[0]] = key
166-
if (loopParams.keys[1]) scopedLocals[loopParams.keys[1]] = value
167-
// merge nondestructively into existing locals
168-
const scopedOptions = merge(opts, { locals: scopedLocals })
169-
// provide the modified options to the content evaluation
170-
// we need to clone the node because the normal operation modifies
171-
// the node directly
172-
const content = cloneDeep(node.content)
173-
const res = walk(scopedOptions, content)
174-
m.push(res)
153+
m.push(executeLoop(loopParams.keys, target[key], key, node, opts))
175154
}
176-
return m
177155
}
156+
157+
// return directly out of the loop, which will skip the "each" tag
158+
return m
178159
}
179160

180161
// return the node
@@ -196,6 +177,10 @@ function getNextTag (nodes, i, nodeCount) {
196177
return [i, { tag: undefined }]
197178
}
198179

180+
/**
181+
* Given a "loop" parameter from an "each" tag, parses out the param names and
182+
* expression to be looped through using a mini text parser.
183+
*/
199184
function parseLoopStatement (input) {
200185
let current = 0
201186
let char = input[current]
@@ -250,3 +235,22 @@ function parseLoopStatement (input) {
250235
return res
251236
}
252237
}
238+
239+
/**
240+
* Creates a set of local variables within the loop, and evaluates all nodes
241+
* within the loop, returning their contents
242+
*/
243+
function executeLoop (loopParams, p1, p2, node, opts) {
244+
// two loop locals are allowed
245+
// - for arrays it's the current value and the index
246+
// - for objects, it's the value and the key
247+
const scopedLocals = {}
248+
scopedLocals[loopParams[0]] = p1
249+
if (loopParams[1]) scopedLocals[loopParams[1]] = p2
250+
// merge nondestructively into existing locals
251+
const scopedOptions = merge(opts, { locals: scopedLocals })
252+
// walk through the contents and run replacements with modified options
253+
// we need to clone the node because the normal operation modifies
254+
// the node directly
255+
return walk(scopedOptions, cloneDeep(node.content))
256+
}

test/fixtures/loop_object.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p>x</p>
2-
<each loop='key, value in items'>
2+
<each loop='value, key in items'>
33
<p>{{key}}: {{value}}</p>
44
</each>
55
<p>x</p>

test/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ test('loop object', (t) => {
7575
return matchExpected(t, 'loop_object', { locals: { items: { a: 'b', c: 'd' } } })
7676
})
7777

78+
test.todo('loop with other locals included')
79+
test.todo('loop with conflicting locals')
80+
test.todo('nested loops')
81+
7882
//
7983
// Utility
8084
//

0 commit comments

Comments
 (0)