1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20/** 21 * @fileOverview Document & Element Helpers. 22 * 23 * required: 24 * Document#: createElement, createComment, getRef 25 * Element#: appendChild, insertBefore, removeChild, nextSibling 26 */ 27import Vm from './index'; 28import Element from '../../vdom/Element'; 29import Comment from '../../vdom/Comment'; 30import Node from '../../vdom/Node'; 31import { FragBlockInterface, isBlock } from './compiler'; 32import { emitSubVmLife } from './pageLife'; 33 34/** 35 * Create a body by type. 36 * @param {Vm} vm - Vm object. 37 * @param {string} type - Element type. 38 * @return {Node} Body of Node by type. 39 */ 40export function createBody(vm: Vm, type: string): Node { 41 const doc = vm._app.doc; 42 return doc.createBody(type); 43} 44 45/** 46 * Create an element by type 47 * @param {Vm} vm - Vm object. 48 * @param {string} type - Element type. 49 * @return {Element} Element of Node by type. 50 */ 51export function createElement(vm: Vm, type: string): Element { 52 const doc = vm._app.doc; 53 return doc.createElement(type); 54} 55 56/** 57 * Create and return a frag block for an element. 58 * @param {Vm} vm - Vm object. 59 * @param {Element} element - Element object. 60 * @return {FragBlockInterface} New block. 61 */ 62export function createBlock(vm: Vm, element: Element | FragBlockInterface): FragBlockInterface { 63 const start = createBlockStart(vm); 64 const end = createBlockEnd(vm); 65 const blockId = lastestBlockId++; 66 const newBlock: FragBlockInterface = {start, end, blockId}; 67 if (isBlock(element)) { 68 let updateMark = element.updateMark; 69 if (updateMark) { 70 if (isBlock(updateMark)) { 71 updateMark = updateMark.end; 72 } 73 element.element.insertAfter(end, updateMark); 74 element.element.insertAfter(start, updateMark); 75 element.updateMark = end; 76 } else { 77 element.element.insertBefore(start, element.end); 78 element.element.insertBefore(end, element.end); 79 } 80 newBlock.element = element.element; 81 } else { 82 element.appendChild(start); 83 element.appendChild(end); 84 newBlock.element = element; 85 element.block = newBlock; 86 } 87 return newBlock; 88} 89 90let lastestBlockId = 1; 91 92/** 93 * Create and return a block starter. 94 * @param {Vm} vm - Vm object. 95 * @return {Comment} A block starter. 96 */ 97function createBlockStart(vm: Vm): Comment { 98 const doc = vm._app.doc; 99 const anchor = doc.createComment('start'); 100 return anchor; 101} 102 103/** 104 * Create and return a block ender. 105 * @param {Vm} vm - Vm object. 106 * @return {Comment} A block starter. 107 */ 108function createBlockEnd(vm: Vm): Comment { 109 const doc = vm._app.doc; 110 const anchor = doc.createComment('end'); 111 anchor.destroyHook = function() { 112 if (anchor.watchers !== undefined) { 113 anchor.watchers.forEach(function(watcher) { 114 watcher.teardown(); 115 }); 116 anchor.watchers = []; 117 } 118 }; 119 return anchor; 120} 121 122/** 123 * Attach target to a certain dest using appendChild by default. 124 * @param {Element} target - If the dest is a frag block then insert before the ender. 125 * @param {FragBlockInterface | Element} dest - A certain dest. 126 * @return {*} 127 */ 128export function attachTarget(target: Element, dest: FragBlockInterface | Element): any { 129 if (isBlock(dest)) { 130 const before = dest.end; 131 const after = dest.updateMark; 132 if (dest.children) { 133 dest.children.push(target); 134 } 135 if (after) { 136 const signal = moveTarget(target, after); 137 if (isBlock(target)) { 138 dest.updateMark = target.end; 139 } else { 140 dest.updateMark = target; 141 } 142 return signal; 143 } else if (isBlock(target)) { 144 dest.element.insertBefore(target.start, before); 145 dest.element.insertBefore(target.end, before); 146 } else { 147 return dest.element.insertBefore(target, before); 148 } 149 } else { 150 if (isBlock(target)) { 151 dest.appendChild(target.start); 152 dest.appendChild(target.end); 153 } else { 154 return dest.appendChild(target); 155 } 156 } 157} 158 159/** 160 * Move target before a certain element. The target maybe block or element. 161 * @param {Element | FragBlockInterface} target - Block or element. 162 * @param {Node} after - Node object after moving. 163 * @return {*} 164 */ 165export function moveTarget(target: Element | FragBlockInterface, after: Node): any { 166 if (isBlock(target)) { 167 return moveBlock(target, after); 168 } 169 return moveElement(target, after); 170} 171 172/** 173 * Move element before a certain element. 174 * @param {Element} element - Element object. 175 * @param {Node} after - Node object after moving. 176 * @return {*} 177 */ 178function moveElement(element: Element, after: Node): any { 179 const parent = after.parentNode as Element; 180 if (parent && parent.children.indexOf(after) !== -1) { 181 return parent.insertAfter(element, after); 182 } 183} 184 185/** 186 * Move all elements of the block before a certain element. 187 * @param {FragBlockInterface} fragBlock - Frag block. 188 * @param {Node} after - Node object after moving. 189 */ 190function moveBlock(fragBlock: FragBlockInterface, after: Node): any { 191 const parent = after.parentNode as Element; 192 if (parent) { 193 let el = fragBlock.start as Node; 194 let signal; 195 const group = [el]; 196 while (el && el !== fragBlock.end) { 197 el = el.nextSibling; 198 group.push(el); 199 } 200 let temp = after; 201 group.every((el) => { 202 signal = parent.insertAfter(el, temp); 203 temp = el; 204 return signal !== -1; 205 }); 206 return signal; 207 } 208} 209 210/** 211 * Remove target from DOM tree. 212 * @param {Element | FragBlockInterface} target - If the target is a frag block then call _removeBlock 213 * @param {boolean} [preserveBlock] - Preserve block. 214 */ 215export function removeTarget(target: Element | FragBlockInterface, preserveBlock?: boolean): void { 216 if (!preserveBlock) { 217 preserveBlock = false; 218 } 219 if (isBlock(target)) { 220 removeBlock(target, preserveBlock); 221 } else { 222 removeElement(target); 223 } 224 if (target.vm) { 225 target.vm.$emit('hook:onDetached'); 226 emitSubVmLife(target.vm, 'onDetached'); 227 target.vm.$emit('hook:destroyed'); 228 } 229} 230 231/** 232 * Remove an element. 233 * @param {Element | Comment} target - Target element. 234 */ 235function removeElement(target: Element | Comment): void { 236 const parent = target.parentNode as Element; 237 if (parent) { 238 parent.removeChild(target); 239 } 240} 241 242/** 243 * Remove a frag block. 244 * @param {FragBlockInterface} fragBlock - Frag block. 245 * @param {boolean} [preserveBlock] - If preserve block. 246 */ 247function removeBlock(fragBlock: FragBlockInterface, preserveBlock?: boolean): void { 248 if (!preserveBlock) { 249 preserveBlock = false; 250 } 251 const result = []; 252 let el = fragBlock.start.nextSibling; 253 while (el && el !== fragBlock.end) { 254 result.push(el); 255 el = el.nextSibling; 256 } 257 if (!preserveBlock) { 258 removeElement(fragBlock.start); 259 } 260 result.forEach((el) => { 261 removeElement(el); 262 }); 263 if (!preserveBlock) { 264 removeElement(fragBlock.end); 265 } 266} 267