-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrepeat.js
246 lines (214 loc) · 7.35 KB
/
repeat.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
var utils = require('../utils'),
config = require('../config')
/**
* Binding that manages VMs based on an Array
*/
module.exports = {
bind: function () {
this.identifier = '$r' + this.id
// a hash to cache the same expressions on repeated instances
// so they don't have to be compiled for every single instance
this.expCache = utils.hash()
var el = this.el,
ctn = this.container = el.parentNode
// extract child Id, if any
this.childId = this.compiler.eval(utils.attr(el, 'ref'))
// create a comment node as a reference node for DOM insertions
this.ref = document.createComment(config.prefix + '-repeat-' + this.key)
ctn.insertBefore(this.ref, el)
ctn.removeChild(el)
this.collection = null
this.vms = null
},
update: function (collection) {
if (!Array.isArray(collection)) {
if (utils.isObject(collection)) {
collection = utils.objectToArray(collection)
} else {
utils.warn('v-repeat only accepts Array or Object values.')
}
}
// keep reference of old data and VMs
// so we can reuse them if possible
this.oldVMs = this.vms
this.oldCollection = this.collection
collection = this.collection = collection || []
var isObject = collection[0] && utils.isObject(collection[0])
this.vms = this.oldCollection
? this.diff(collection, isObject)
: this.init(collection, isObject)
if (this.childId) {
this.vm.$[this.childId] = this.vms
}
},
init: function (collection, isObject) {
var vm, vms = []
for (var i = 0, l = collection.length; i < l; i++) {
vm = this.build(collection[i], i, isObject)
vms.push(vm)
if (this.compiler.init) {
this.container.insertBefore(vm.$el, this.ref)
} else {
vm.$before(this.ref)
}
}
return vms
},
/**
* Diff the new array with the old
* and determine the minimum amount of DOM manipulations.
*/
diff: function (newCollection, isObject) {
var i, l, item, vm,
oldIndex,
targetNext,
currentNext,
nextEl,
ctn = this.container,
oldVMs = this.oldVMs,
vms = []
vms.length = newCollection.length
// first pass, collect new reused and new created
for (i = 0, l = newCollection.length; i < l; i++) {
item = newCollection[i]
if (isObject) {
item.$index = i
if (item.__emitter__ && item.__emitter__[this.identifier]) {
// this piece of data is being reused.
// record its final position in reused vms
item.$reused = true
} else {
vms[i] = this.build(item, i, isObject)
}
} else {
// we can't attach an identifier to primitive values
// so have to do an indexOf...
oldIndex = indexOf(oldVMs, item)
if (oldIndex > -1) {
// record the position on the existing vm
oldVMs[oldIndex].$reused = true
oldVMs[oldIndex].$data.$index = i
} else {
vms[i] = this.build(item, i, isObject)
}
}
}
// second pass, collect old reused and destroy unused
for (i = 0, l = oldVMs.length; i < l; i++) {
vm = oldVMs[i]
item = this.arg
? vm.$data[this.arg]
: vm.$data
if (item.$reused) {
vm.$reused = true
delete item.$reused
}
if (vm.$reused) {
// update the index to latest
vm.$index = item.$index
// the item could have had a new key
if (item.$key && item.$key !== vm.$key) {
vm.$key = item.$key
}
vms[vm.$index] = vm
} else {
// this one can be destroyed.
if (item.__emitter__) {
delete item.__emitter__[this.identifier]
}
vm.$destroy()
}
}
// final pass, move/insert DOM elements
i = vms.length
while (i--) {
vm = vms[i]
item = vm.$data
targetNext = vms[i + 1]
if (vm.$reused) {
nextEl = vm.$el.nextSibling
// destroyed VMs' element might still be in the DOM
// due to transitions
while (!nextEl.vue_vm && nextEl !== this.ref) {
nextEl = nextEl.nextSibling
}
currentNext = nextEl.vue_vm
if (currentNext !== targetNext) {
if (!targetNext) {
ctn.insertBefore(vm.$el, this.ref)
} else {
nextEl = targetNext.$el
// new VMs' element might not be in the DOM yet
// due to transitions
while (!nextEl.parentNode) {
targetNext = vms[nextEl.vue_vm.$index + 1]
nextEl = targetNext
? targetNext.$el
: this.ref
}
ctn.insertBefore(vm.$el, nextEl)
}
}
delete vm.$reused
delete item.$index
delete item.$key
} else { // a new vm
vm.$before(targetNext ? targetNext.$el : this.ref)
}
}
return vms
},
build: function (data, index, isObject) {
// wrap non-object values
var raw, alias,
wrap = !isObject || this.arg
if (wrap) {
raw = data
alias = this.arg || '$value'
data = {}
data[alias] = raw
}
data.$index = index
var el = this.el.cloneNode(true),
Ctor = this.compiler.resolveComponent(el, data),
vm = new Ctor({
el: el,
data: data,
parent: this.vm,
compilerOptions: {
repeat: true,
expCache: this.expCache
}
})
if (isObject) {
// attach an ienumerable identifier to the raw data
(raw || data).__emitter__[this.identifier] = true
}
return vm
},
unbind: function () {
if (this.childId) {
delete this.vm.$[this.childId]
}
if (this.vms) {
var i = this.vms.length
while (i--) {
this.vms[i].$destroy()
}
}
}
}
// Helpers --------------------------------------------------------------------
/**
* Find an object or a wrapped data object
* from an Array
*/
function indexOf (vms, obj) {
for (var vm, i = 0, l = vms.length; i < l; i++) {
vm = vms[i]
if (!vm.$reused && vm.$value === obj) {
return i
}
}
return -1
}