blob: 6e5a800cfb656717b3e0a214796cf7f962974ac3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* @fileOverview Document & Element Helpers.
*
* required:
* Document#: createElement, createComment, getRef
* Element#: appendChild, insertBefore, removeChild, nextSibling
*/
/**
* Create a body by type
* Using this._app.doc
*
* @param {string} type
*/
export function createBody (vm, type) {
const doc = vm._app.doc
return doc.createBody(type)
}
/**
* Create an element by type
* Using this._app.doc
*
* @param {string} type
*/
export function createElement (vm, type) {
const doc = vm._app.doc
return doc.createElement(type)
}
/**
* Create and return a frag block for an element.
* The frag block has a starter, ender and the element itself.
*
* @param {object} element
*/
export function createBlock (vm, element) {
const start = createBlockStart(vm)
const end = createBlockEnd(vm)
const blockId = lastestBlockId++
if (element.element) {
let updateMark = element.updateMark
if (updateMark) {
if (updateMark.element) {
updateMark = updateMark.end
}
element.element.insertAfter(end, updateMark)
element.element.insertAfter(start, updateMark)
element.updateMark = end
}
else {
element.element.insertBefore(start, element.end)
element.element.insertBefore(end, element.end)
}
element = element.element
}
else {
element.appendChild(start)
element.appendChild(end)
}
return { start, end, element, blockId }
}
let lastestBlockId = 1
/**
* Create and return a block starter.
* Using this._app.doc
*/
function createBlockStart (vm) {
const doc = vm._app.doc
const anchor = doc.createComment('start')
return anchor
}
/**
* Create and return a block ender.
* Using this._app.doc
*/
function createBlockEnd (vm) {
const doc = vm._app.doc
const anchor = doc.createComment('end')
return anchor
}
/**
* Attach target to a certain dest using appendChild by default.
* If the dest is a frag block then insert before the ender.
* If the target is a frag block then attach the starter and ender in order.
*
* @param {object} target
* @param {object} dest
*/
export function attachTarget (vm, target, dest) {
if (dest.element) {
const before = dest.end
const after = dest.updateMark
// push new target for watch list update later
if (dest.children) {
dest.children.push(target)
}
// for check repeat case
if (after) {
const signal = moveTarget(vm, target, after)
dest.updateMark = target.element ? target.end : target
return signal
}
else if (target.element) {
dest.element.insertBefore(target.start, before)
dest.element.insertBefore(target.end, before)
}
else {
return dest.element.insertBefore(target, before)
}
}
else {
if (target.element) {
dest.appendChild(target.start)
dest.appendChild(target.end)
}
else {
return dest.appendChild(target)
}
}
}
/**
* Move target before a certain element. The target maybe block or element.
*
* @param {object} target
* @param {object} before
*/
export function moveTarget (vm, target, after) {
if (target.element) {
return moveBlock(target, after)
}
return moveElement(target, after)
}
/**
* Move element before a certain element.
*
* @param {object} element
* @param {object} before
*/
function moveElement (element, after) {
const parent = after.parentNode
if (parent) {
return parent.insertAfter(element, after)
}
}
/**
* Move all elements of the block before a certain element.
*
* @param {object} fragBlock
* @param {object} before
*/
function moveBlock (fragBlock, after) {
const parent = after.parentNode
if (parent) {
let el = fragBlock.start
let signal
const group = [el]
while (el && el !== fragBlock.end) {
el = el.nextSibling
group.push(el)
}
let temp = after
group.every((el) => {
signal = parent.insertAfter(el, temp)
temp = el
return signal !== -1
})
return signal
}
}
/**
* Remove target from DOM tree.
* If the target is a frag block then call _removeBlock
*
* @param {object} target
*/
export function removeTarget (vm, target, preserveBlock = false) {
if (target.element) {
removeBlock(target, preserveBlock)
}
else {
removeElement(target)
}
if (target._vm) {
target._vm.$emit('hook:destroyed')
}
}
/**
* Remove a certain element.
* Using this._app.doc
*
* @param {object} target
*/
function removeElement (target) {
const parent = target.parentNode
if (parent) {
parent.removeChild(target)
}
}
/**
* Remove a frag block.
* The second param decides whether the block self should be removed too.
*
* @param {object} fragBlock
* @param {Boolean} preserveBlock=false
*/
function removeBlock (fragBlock, preserveBlock = false) {
const result = []
let el = fragBlock.start.nextSibling
while (el && el !== fragBlock.end) {
result.push(el)
el = el.nextSibling
}
if (!preserveBlock) {
removeElement(fragBlock.start)
}
result.forEach((el) => {
removeElement(el)
})
if (!preserveBlock) {
removeElement(fragBlock.end)
}
}