| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- var Observer = require('../observer'),
- utils = require('../utils'),
- config = require('../config'),
- def = utils.defProtected,
- ViewModel // lazy def to avoid circular dependency
- /**
- * Mathods that perform precise DOM manipulation
- * based on mutator method triggered
- */
- var mutationHandlers = {
- push: function (m) {
- var i = 0, l = m.args.length, vm,
- base = this.collection.length - l
- for (; i < l; i++) {
- vm = this.buildItem(m.args[i], base + i)
- this.updateObject(vm, 1)
- }
- },
- pop: function () {
- var vm = this.vms.pop()
- if (vm) {
- vm.$destroy()
- this.updateObject(vm, -1)
- }
- },
- unshift: function (m) {
- var i = 0, l = m.args.length, vm
- for (; i < l; i++) {
- vm = this.buildItem(m.args[i], i)
- this.updateObject(vm, 1)
- }
- },
- shift: function () {
- var vm = this.vms.shift()
- if (vm) {
- vm.$destroy()
- this.updateObject(vm, -1)
- }
- },
- splice: function (m) {
- var i, l, vm,
- index = m.args[0],
- removed = m.args[1],
- added = m.args.length - 2,
- removedVMs = removed === undefined
- ? this.vms.splice(index)
- : this.vms.splice(index, removed)
- for (i = 0, l = removedVMs.length; i < l; i++) {
- removedVMs[i].$destroy()
- this.updateObject(removedVMs[i], -1)
- }
- for (i = 0; i < added; i++) {
- vm = this.buildItem(m.args[i + 2], index + i)
- this.updateObject(vm, 1)
- }
- },
- sort: function () {
- var vms = this.vms,
- col = this.collection,
- l = col.length,
- sorted = new Array(l),
- i, j, vm, data
- for (i = 0; i < l; i++) {
- data = col[i]
- for (j = 0; j < l; j++) {
- vm = vms[j]
- if (vm.$data === data) {
- sorted[i] = vm
- break
- }
- }
- }
- for (i = 0; i < l; i++) {
- this.container.insertBefore(sorted[i].$el, this.ref)
- }
- this.vms = sorted
- },
- reverse: function () {
- var vms = this.vms
- vms.reverse()
- for (var i = 0, l = vms.length; i < l; i++) {
- this.container.insertBefore(vms[i].$el, this.ref)
- }
- }
- }
- /**
- * Convert an Object to a v-repeat friendly Array
- */
- function objectToArray (obj) {
- var res = [], val, data
- for (var key in obj) {
- val = obj[key]
- data = utils.typeOf(val) === 'Object'
- ? val
- : { $value: val }
- def(data, '$key', key)
- res.push(data)
- }
- return res
- }
- /**
- * Find an object or a wrapped data object
- * from an Array
- */
- function indexOf (arr, obj) {
- for (var i = 0, l = arr.length; i < l; i++) {
- if (arr[i] === obj || (obj.$value && arr[i].$value === obj.$value)) {
- return i
- }
- }
- return -1
- }
- module.exports = {
- bind: function () {
- var el = this.el,
- ctn = this.container = el.parentNode
- // extract child VM information, if any
- ViewModel = ViewModel || require('../viewmodel')
- this.Ctor = this.Ctor || ViewModel
- // extract child Id, if any
- this.childId = 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.initiated = false
- this.collection = null
- this.vms = null
- var self = this
- this.mutationListener = function (path, arr, mutation) {
- var method = mutation.method
- mutationHandlers[method].call(self, mutation)
- if (method !== 'push' && method !== 'pop') {
- // update index
- var i = arr.length
- while (i--) {
- self.vms[i].$index = i
- }
- }
- if (method === 'push' || method === 'unshift' || method === 'splice') {
- // recalculate dependency
- self.changed()
- }
- }
- },
- update: function (collection, init) {
- if (
- collection === this.collection ||
- collection === this.object
- ) return
- if (utils.typeOf(collection) === 'Object') {
- collection = this.convertObject(collection)
- }
- this.reset()
- // if initiating with an empty collection, we need to
- // force a compile so that we get all the bindings for
- // dependency extraction.
- if (!this.initiated && (!collection || !collection.length)) {
- this.buildItem()
- this.initiated = true
- }
- // keep reference of old data and VMs
- // so we can reuse them if possible
- this.old = this.collection
- var oldVMs = this.oldVMs = this.vms
- collection = this.collection = collection || []
- this.vms = []
- if (this.childId) {
- this.vm.$[this.childId] = this.vms
- }
- // listen for collection mutation events
- // the collection has been augmented during Binding.set()
- if (!collection.__emitter__) Observer.watchArray(collection)
- collection.__emitter__.on('mutate', this.mutationListener)
- // create new VMs and append to DOM
- if (collection.length) {
- collection.forEach(this.buildItem, this)
- if (!init) this.changed()
- }
- // destroy unused old VMs
- if (oldVMs) {
- var i = oldVMs.length, vm
- while (i--) {
- vm = oldVMs[i]
- if (vm.$reused) {
- vm.$reused = false
- } else {
- vm.$destroy()
- }
- }
- }
- this.old = this.oldVMs = null
- },
- /**
- * Notify parent compiler that new items
- * have been added to the collection, it needs
- * to re-calculate computed property dependencies.
- * Batched to ensure it's called only once every event loop.
- */
- changed: function () {
- if (this.queued) return
- this.queued = true
- var self = this
- utils.nextTick(function () {
- if (!self.compiler) return
- self.compiler.parseDeps()
- self.queued = false
- })
- },
- /**
- * Create a new child VM from a data object
- * passing along compiler options indicating this
- * is a v-repeat item.
- */
- buildItem: function (data, index) {
- var ctn = this.container,
- vms = this.vms,
- col = this.collection,
- el, i, existing, ref, item, primitive, detached
- // append node into DOM first
- // so v-if can get access to parentNode
- // TODO: logic here is a total mess.
- if (data) {
- if (this.old) {
- i = indexOf(this.old, data)
- }
- existing = i > -1
- if (existing) { // existing, reuse the old VM
- item = this.oldVMs[i]
- // mark, so it won't be destroyed
- item.$reused = true
- el = item.$el
- // existing VM's el can possibly be detached by v-if.
- // in that case don't insert.
- detached = !el.parentNode
- } else { // new data, need to create new VM
- el = this.el.cloneNode(true)
- // process transition info before appending
- el.vue_trans = utils.attr(el, 'transition', true)
- el.vue_anim = utils.attr(el, 'animation', true)
- el.vue_effect = utils.attr(el, 'effect', true)
- // wrap primitive element in an object
- if (utils.typeOf(data) !== 'Object') {
- primitive = true
- data = { $value: data }
- }
- }
- ref = vms.length > index
- ? vms[index].$el
- : this.ref
-
- // if ref VM's el is detached by v-if
- // use its v-if ref node instead
- if (!ref.parentNode) {
- ref = ref.vue_if_ref
- }
- if (existing) {
- // existing node
- // if not detached, just re-insert to new location
- // else re-insert its v-if ref node
- ctn.insertBefore(detached ? el.vue_if_ref : el, ref)
- } else {
- // new node, prepare it for v-if
- el.vue_if_parent = ctn
- el.vue_if_ref = ref
- }
- // set index so vm can init with it
- // and do not trigger stuff early
- data.$index = index
- }
- item = item || new this.Ctor({
- el: el,
- data: data,
- compilerOptions: {
- repeat: true,
- parentCompiler: this.compiler
- }
- })
- item.$index = index
- if (!data) {
- // this is a forced compile for an empty collection.
- // let's remove it...
- item.$destroy()
- } else {
- vms.splice(index, 0, item)
- // for primitive values, listen for value change
- if (primitive) {
- item.$compiler.observer.on('set', function (key, val) {
- if (key === '$value') {
- col[item.$index] = val
- }
- })
- }
- // new instance and v-if doesn't want it detached
- // good to insert.
- if (!existing && el.vue_if !== false) {
- if (this.compiler.init) {
- // do not transition on initial compile.
- ctn.insertBefore(item.$el, ref)
- item.$compiler.execHook('attached')
- } else {
- // transition in...
- item.$before(ref)
- }
- }
- }
- return item
- },
- /**
- * Convert an object to a repeater Array
- * and make sure changes in the object are synced to the repeater
- */
- convertObject: function (object) {
- if (this.object) {
- this.object.__emitter__.off('set', this.updateRepeater)
- }
- this.object = object
- var collection = object.$repeater || objectToArray(object)
- if (!object.$repeater) {
- def(object, '$repeater', collection)
- }
- var self = this
- this.updateRepeater = function (key, val) {
- if (key.indexOf('.') === -1) {
- var i = self.vms.length, item
- while (i--) {
- item = self.vms[i]
- if (item.$key === key) {
- if (item.$data !== val && item.$value !== val) {
- if ('$value' in item) {
- item.$value = val
- } else {
- item.$data = val
- }
- }
- break
- }
- }
- }
- }
- object.__emitter__.on('set', this.updateRepeater)
- return collection
- },
- /**
- * Sync changes from the $repeater Array
- * back to the represented Object
- */
- updateObject: function (vm, action) {
- var obj = this.object
- if (obj && vm.$key) {
- var key = vm.$key,
- val = vm.$value || vm.$data
- if (action > 0) { // new property
- // make key ienumerable
- delete vm.$data.$key
- obj[key] = val
- Observer.convert(obj, key)
- } else {
- delete obj[key]
- }
- obj.__emitter__.emit('set', key, val, true)
- }
- },
- reset: function (destroyAll) {
- if (this.childId) {
- delete this.vm.$[this.childId]
- }
- if (this.collection) {
- this.collection.__emitter__.off('mutate', this.mutationListener)
- if (destroyAll) {
- var i = this.vms.length
- while (i--) {
- this.vms[i].$destroy()
- }
- }
- }
- },
- unbind: function () {
- this.reset(true)
- }
- }
|