Skip to content

Commit a7dfb3f

Browse files
committed
Fix #65
A computed property can depend on properties on repeated items. When new items are added to the Array, the dependency collection has already been done so their properties are not collected by the computed property on the parent. We need to re-parse the dependencies in such situation, namely push, unshift, splice and when the entire Array is swapped. Also included casper test case by @daines.
1 parent 3cb7027 commit a7dfb3f

File tree

5 files changed

+113
-14
lines changed

5 files changed

+113
-14
lines changed

src/compiler.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,7 @@ function Compiler (vm, options) {
124124
}
125125

126126
// extract dependencies for computed properties
127-
if (compiler.computed.length) {
128-
DepsParser.parse(compiler.computed)
129-
}
127+
compiler.parseDeps()
130128

131129
// done!
132130
compiler.init = false
@@ -600,6 +598,14 @@ CompilerProto.hasKey = function (key) {
600598
hasOwn.call(this.vm, baseKey)
601599
}
602600

601+
/**
602+
* Collect dependencies for computed properties
603+
*/
604+
CompilerProto.parseDeps = function () {
605+
if (!this.computed.length) return
606+
DepsParser.parse(this.computed)
607+
}
608+
603609
/**
604610
* Unbind and remove element
605611
*/

src/deps-parser.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ function catchDeps (binding) {
1111
if (binding.isFn) return
1212
utils.log('\n- ' + binding.key)
1313
var got = utils.hash()
14+
binding.deps = []
1415
catcher.on('get', function (dep) {
1516
var has = got[dep.key]
1617
if (has && has.compiler === dep.compiler) return
@@ -36,7 +37,8 @@ module.exports = {
3637
parse: function (bindings) {
3738
utils.log('\nparsing dependencies...')
3839
Observer.shouldGet = true
39-
bindings.forEach(catchDeps)
40+
var i = bindings.length
41+
while (i--) { catchDeps(bindings[i]) }
4042
Observer.shouldGet = false
4143
utils.log('\ndone.')
4244
}

src/directives/repeat.js

+31-10
Original file line numberDiff line numberDiff line change
@@ -110,38 +110,59 @@ module.exports = {
110110
if (method !== 'push' && method !== 'pop') {
111111
self.updateIndexes()
112112
}
113+
if (method === 'push' || method === 'unshift' || method === 'splice') {
114+
self.changed()
115+
}
113116
}
114117

115118
},
116119

117-
update: function (collection) {
120+
update: function (collection, init) {
118121

119-
this.unbind(true)
122+
var self = this
123+
self.unbind(true)
120124
// attach an object to container to hold handlers
121-
this.container.vue_dHandlers = utils.hash()
125+
self.container.vue_dHandlers = utils.hash()
122126
// if initiating with an empty collection, we need to
123127
// force a compile so that we get all the bindings for
124128
// dependency extraction.
125-
if (!this.initiated && (!collection || !collection.length)) {
126-
this.buildItem()
127-
this.initiated = true
129+
if (!self.initiated && (!collection || !collection.length)) {
130+
self.buildItem()
131+
self.initiated = true
128132
}
129-
collection = this.collection = collection || []
130-
this.vms = []
133+
collection = self.collection = collection || []
134+
self.vms = []
131135

132136
// listen for collection mutation events
133137
// the collection has been augmented during Binding.set()
134138
if (!collection.__observer__) Observer.watchArray(collection, null, new Emitter())
135-
collection.__observer__.on('mutate', this.mutationListener)
139+
collection.__observer__.on('mutate', self.mutationListener)
136140

137141
// create child-vms and append to DOM
138142
if (collection.length) {
139143
for (var i = 0, l = collection.length; i < l; i++) {
140-
this.buildItem(collection[i], i)
144+
self.buildItem(collection[i], i)
141145
}
146+
if (!init) self.changed()
142147
}
143148
},
144149

150+
/**
151+
* Notify parent compiler that new items
152+
* have been added to the collection, it needs
153+
* to re-calculate computed property dependencies.
154+
* Batched to ensure it's called only once every event loop.
155+
*/
156+
changed: function () {
157+
var self = this
158+
if (self.queued) return
159+
self.queued = true
160+
setTimeout(function () {
161+
self.compiler.parseDeps()
162+
self.queued = false
163+
}, 0)
164+
},
165+
145166
/**
146167
* Create a new child VM from a data object
147168
* passing along compiler options indicating this
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Repeated form elements</title>
5+
<meta charset="utf-8">
6+
<script src="../../../dist/vue.js"></script>
7+
</head>
8+
<body>
9+
<form id="form">
10+
<p v-repeat="items">
11+
<input type="text" name="text{{$index}}" v-model="text">
12+
</p>
13+
<button v-on="click: add" id="add">Add</button>
14+
<p id="texts">{{texts}}</p>
15+
</form>
16+
<script>
17+
var app = new Vue({
18+
el: '#form',
19+
data: {
20+
items: [
21+
{ text: "a" },
22+
{ text: "b" }
23+
]
24+
},
25+
methods: {
26+
add: function(e) {
27+
this.items.push({ text: "c" })
28+
e.preventDefault()
29+
}
30+
},
31+
computed: {
32+
texts: function () {
33+
return this.items.map(function(item) {
34+
return item.text
35+
}).join(",")
36+
}
37+
}
38+
})
39+
</script>
40+
</body>
41+
</html>
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
casper.test.begin('Computed property depending on repeated items', 4, function (test) {
2+
3+
casper
4+
.start('./fixtures/computed-repeat.html')
5+
.then(function () {
6+
test.assertSelectorHasText('#texts', 'a,b')
7+
})
8+
.thenClick('#add', function () {
9+
test.assertSelectorHasText('#texts', 'a,b,c')
10+
})
11+
.then(function () {
12+
this.fill('#form', {
13+
"text0": 'd',
14+
"text1": 'e',
15+
"text2": 'f',
16+
})
17+
})
18+
.then(function () {
19+
this.sendKeys('input[name="text2"]', 'fff')
20+
})
21+
.then(function () {
22+
test.assertField('text0', 'd')
23+
test.assertSelectorHasText('#texts', 'd,e,ffff')
24+
})
25+
.run(function () {
26+
test.done()
27+
})
28+
29+
})

0 commit comments

Comments
 (0)