import {
createAnchor,
before,
prepend,
inDoc,
mapNodeRange,
removeNodeRange
} from '../util/index'
import {
beforeWithTransition,
removeWithTransition
} from '../transition/index'
/**
* Abstraction for a partially-compiled fragment.
* Can optionally compile content with a child scope.
*
* @param {Function} linker
* @param {Vue} vm
* @param {DocumentFragment} frag
* @param {Vue} [host]
* @param {Object} [scope]
* @param {Fragment} [parentFrag]
*/
export default function Fragment (linker, vm, frag, host, scope, parentFrag) {
this.children = []
this.childFrags = []
this.vm = vm
this.scope = scope
this.inserted = false
this.parentFrag = parentFrag
if (parentFrag) {
parentFrag.childFrags.push(this)
}
this.unlink = linker(vm, frag, host, scope, this)
var single = this.single =
frag.childNodes.length === 1 &&
// do not go single mode if the only node is an anchor
!(frag.childNodes[0].__v_anchor)
if (single) {
this.node = frag.childNodes[0]
this.before = singleBefore
this.remove = singleRemove
} else {
this.node = createAnchor('fragment-start')
this.end = createAnchor('fragment-end')
this.frag = frag
prepend(this.node, frag)
frag.appendChild(this.end)
this.before = multiBefore
this.remove = multiRemove
}
this.node.__v_frag = this
}
/**
* Call attach/detach for all components contained within
* this fragment. Also do so recursively for all child
* fragments.
*
* @param {Function} hook
*/
Fragment.prototype.callHook = function (hook) {
var i, l
for (i = 0, l = this.childFrags.length; i < l; i++) {
this.childFrags[i].callHook(hook)
}
for (i = 0, l = this.children.length; i < l; i++) {
hook(this.children[i])
}
}
/**
* Insert fragment before target, single node version
*
* @param {Node} target
* @param {Boolean} withTransition
*/
function singleBefore (target, withTransition) {
this.inserted = true
var method = withTransition !== false
? beforeWithTransition
: before
method(this.node, target, this.vm)
if (inDoc(this.node)) {
this.callHook(attach)
}
}
/**
* Remove fragment, single node version
*/
function singleRemove () {
this.inserted = false
var shouldCallRemove = inDoc(this.node)
var self = this
this.beforeRemove()
removeWithTransition(this.node, this.vm, function () {
if (shouldCallRemove) {
self.callHook(detach)
}
self.destroy()
})
}
/**
* Insert fragment before target, multi-nodes version
*
* @param {Node} target
* @param {Boolean} withTransition
*/
function multiBefore (target, withTransition) {
this.inserted = true
var vm = this.vm
var method = withTransition !== false
? beforeWithTransition
: before
mapNodeRange(this.node, this.end, function (node) {
method(node, target, vm)
})
if (inDoc(this.node)) {
this.callHook(attach)
}
}
/**
* Remove fragment, multi-nodes version
*/
function multiRemove () {
this.inserted = false
var self = this
var shouldCallRemove = inDoc(this.node)
this.beforeRemove()
removeNodeRange(this.node, this.end, this.vm, this.frag, function () {
if (shouldCallRemove) {
self.callHook(detach)
}
self.destroy()
})
}
/**
* Prepare the fragment for removal.
*/
Fragment.prototype.beforeRemove = function () {
var i, l
for (i = 0, l = this.childFrags.length; i < l; i++) {
// call the same method recursively on child
// fragments, depth-first
this.childFrags[i].beforeRemove(false)
}
for (i = 0, l = this.children.length; i < l; i++) {
// Call destroy for all contained instances,
// with remove:false and defer:true.
// Defer is necessary because we need to
// keep the children to call detach hooks
// on them.
this.children[i].$destroy(false, true)
}
var dirs = this.unlink.dirs
for (i = 0, l = dirs.length; i < l; i++) {
// disable the watchers on all the directives
// so that the rendered content stays the same
// during removal.
dirs[i]._watcher && dirs[i]._watcher.teardown()
}
}
/**
* Destroy the fragment.
*/
Fragment.prototype.destroy = function () {
if (this.parentFrag) {
this.parentFrag.childFrags.$remove(this)
}
this.node.__v_frag = null
this.unlink()
}
/**
* Call attach hook for a Vue instance.
*
* @param {Vue} child
*/
function attach (child) {
if (!child._isAttached && inDoc(child.$el)) {
child._callHook('attached')
}
}
/**
* Call detach hook for a Vue instance.
*
* @param {Vue} child
*/
function detach (child) {
if (child._isAttached && !inDoc(child.$el)) {
child._callHook('detached')
}
}
|