• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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