• 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
22 * ViewModel template parser & data-binding process
23 */
24
25import {
26  hasOwn,
27  Log,
28  removeItem
29} from '../../utils/index';
30import {
31  initData,
32  initComputed
33} from '../reactivity/state';
34import {
35  bindElement,
36  setClass,
37  setIdStyle,
38  setTagStyle,
39  setUniversalStyle,
40  setId,
41  bindSubVm,
42  bindSubVmAfterInitialized,
43  newWatch,
44  bindDir,
45  setAttributeStyle
46} from './directive';
47import {
48  createBlock,
49  createBody,
50  createElement,
51  attachTarget,
52  moveTarget,
53  removeTarget
54} from './domHelper';
55import {
56  bindPageLifeCycle
57} from './pageLife';
58import Vm from './index';
59import Element from '../../vdom/Element';
60import Comment from '../../vdom/Comment';
61import Node from '../../vdom/Node';
62import Document from '../../vdom/Document';
63import { VmOptions } from './vmOptions';
64
65export interface FragBlockInterface {
66  start: Comment;
67  end: Comment;
68  element?: Element;
69  blockId: number;
70  children?: any[];
71  data?: any[];
72  vms?: Vm[];
73  updateMark?: Node;
74  display?: boolean;
75  type?: string;
76  vm?: Vm;
77}
78
79export interface AttrInterface {
80  type: string;
81  value: () => void | string;
82  tid: number;
83  append: string;
84  slot: string;
85  slotScope: string;
86  name: string;
87  data: () => any | string;
88  $data: () => any | string;
89}
90
91export interface TemplateInterface {
92  type: string;
93  attr: Partial<AttrInterface>;
94  classList?: () => any | string[];
95  children?: TemplateInterface[];
96  events?: object;
97  repeat?: () => any | RepeatInterface;
98  shown?: () => any;
99  style?: Record<string, string>;
100  id?: () => any | string;
101  append?: string;
102  onBubbleEvents?: object;
103  onCaptureEvents?: object;
104  catchBubbleEvents?: object;
105  catchCaptureEvents?: object;
106}
107
108interface RepeatInterface {
109  exp: () => any;
110  key?: string;
111  value?: string;
112  tid?: number;
113}
114
115interface MetaInterface {
116  repeat: object;
117  shown: boolean;
118  type: string;
119}
120
121interface ConfigInterface {
122  latestValue: undefined | string | number;
123  recorded: boolean;
124}
125
126export function build(vm: Vm) {
127  const opt: any = vm._vmOptions || {};
128  const template: any = opt.template || {};
129  compile(vm, template, vm._parentEl);
130  // foreach vm
131  const doc: Document = vm._app.doc;
132  const body: Node = doc.body;
133  compileVm(vm, body);
134  compileElementAndElement(vm, body);
135  compileAttrStyle(vm, body);
136  Log.debug(`"OnReady" lifecycle in Vm(${vm._type}).`);
137  vm.$emit('hook:onReady');
138  if (vm._parent) {
139    vm.$emit('hook:onAttached');
140  }
141  vm._ready = true;
142}
143
144/**
145 * Compile the Virtual Dom.
146 * @param {Vm} vm - Vm object needs to be compiled.
147 * @param {TemplateInterface} target - Node need to be compiled. Structure of the label in the template.
148 * @param {FragBlockInterface | Element} dest - Parent Node's VM of current.
149 * @param {MetaInterface} [meta] - To transfer data.
150 */
151function compile(vm: Vm, target: TemplateInterface, dest: FragBlockInterface | Element, meta?: Partial<MetaInterface>): void {
152  const app: any = vm._app || {};
153  if (app.lastSignal === -1) {
154    return;
155  }
156  meta = meta || {};
157  if (targetIsSlot(target)) {
158    compileSlot(vm, target, dest as Element);
159    return;
160  }
161
162  if (targetNeedCheckRepeat(target, meta)) {
163    if (dest.type === 'document') {
164      Log.warn('The root element does\'t support `repeat` directive!');
165    } else {
166      compileRepeat(vm, target, dest as Element);
167    }
168    return;
169  }
170  if (targetNeedCheckShown(target, meta)) {
171    if (dest.type === 'document') {
172      Log.warn('The root element does\'t support `if` directive!');
173    } else {
174      compileShown(vm, target, dest, meta);
175    }
176    return;
177  }
178  const type = meta.type || target.type;
179  const component: VmOptions | null = targetIsComposed(vm, type);
180  if (component) {
181    compileCustomComponent(vm, component, target, dest, type, meta);
182    return;
183  }
184  if (type === 'compontent') {
185    compileDyanmicComponent(vm, target, dest, type, meta);
186    return;
187  }
188  if (targetIsBlock(target)) {
189    compileBlock(vm, target, dest);
190    return;
191  }
192  compileNativeComponent(vm, target, dest, type);
193}
194
195function compileVm(vm: Vm, body: Node): void {
196  if (body.nodeType === Node.NodeType.Element) {
197    const node: Element = body as Element;
198    let count = 0;
199    node.children.forEach((child: Node) => {
200      const el = child as Element;
201      const tag = child.type;
202      if (count === 0) {
203        setTagStyle(vm, el, tag, true, false, false);
204      } else if (count === node.children.length - 1) {
205        setTagStyle(vm, el, tag, false, true, false);
206      }
207      count++;
208      compileVmChild(vm, child);
209    });
210  }
211}
212
213function compileVmChild(vm: Vm, body: Node): void {
214  if (body.nodeType === Node.NodeType.Element) {
215    const node: Element = body as Element;
216    let count = 0;
217    node.children.forEach((child: Node) => {
218      const el = child as Element;
219      const tag = child.type;
220      if (count === 0) {
221        setTagStyle(vm, el, tag, true, false, false);
222      } else if (count === node.children.length - 1) {
223        setTagStyle(vm, el, tag, false, true, false);
224      }
225      count++;
226      compileVm(vm, child);
227    });
228  }
229}
230
231function compileAttrStyle(vm: Vm, body: Node): void {
232  if (body.nodeType === Node.NodeType.Element) {
233    const node: Element = body as Element;
234    node.children.forEach((child: Node) => {
235      const el = child as Element;
236      setAttributeStyle(vm, el);
237      compileAttrStyleChild(vm, child);
238    });
239  }
240}
241
242function compileAttrStyleChild(vm: Vm, body: Node): void {
243  if (body.nodeType === Node.NodeType.Element) {
244    const node: Element = body as Element;
245    node.children.forEach((child: Node) => {
246      const el = child as Element;
247      setAttributeStyle(vm, el);
248      compileAttrStyle(vm, child);
249    });
250  }
251}
252
253function compileElementAndElement(vm: Vm, body: Node): void {
254  if (body.nodeType === Node.NodeType.Element) {
255    const node: Element = body as Element;
256    node.children.forEach((child: Node) => {
257      if (child.nextSibling) {
258        const el = child.nextSibling as Element;
259        const tag = child.type + '+' + child.nextSibling.type;
260        setTagStyle(vm, el, tag, false, false, false);
261      }
262      compileElementAndElementChild(vm, child);
263    });
264  }
265}
266
267function compileElementAndElementChild(vm: Vm, body: Node): void {
268  if (body.nodeType === Node.NodeType.Element) {
269    const node: Element = body as Element;
270    node.children.forEach((child: Node) => {
271      if (child.nextSibling) {
272        const el = child.nextSibling as Element;
273        const tag = child.type + '+' + child.nextSibling.type;
274        setTagStyle(vm, el, tag, false, false, false);
275      }
276      compileElementAndElement(vm, child);
277    });
278  }
279}
280
281/**
282 * Compile a dynamic component.
283 * @param {Vm} vm - Vm object needs to be compiled.
284 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
285 * @param {Element | FragBlockInterface} dest - Parent Node's VM of current.
286 * @param {string} type - Component Type.
287 * @param {MetaInterface} meta - To transfer data.
288 */
289export function compileDyanmicComponent(
290  vm: Vm,
291  target: TemplateInterface,
292  dest: Element | FragBlockInterface,
293  type: string,
294  meta: Partial<MetaInterface>
295): void {
296  const attr: object = target.attr;
297  let dynamicType: string;
298  for (const key in attr) {
299    const value = attr[key];
300    if (key === 'name') {
301      if (typeof value === 'function') {
302        dynamicType = value.call(vm, vm);
303      } else if (typeof value === 'string') {
304        dynamicType = value;
305      } else {
306        Log.error('compontent attr name is unkonwn');
307        return;
308      }
309    }
310  }
311
312  const elementDiv = createElement(vm, 'div');
313  attachTarget(elementDiv, dest);
314
315  const element = createElement(vm, type);
316  element.vm = vm;
317  element.target = target;
318  element.destroyHook = function() {
319    if (element.watchers !== undefined) {
320      element.watchers.forEach(function(watcher) {
321        watcher.teardown();
322      });
323      element.watchers = [];
324    }
325  };
326  bindDir(vm, element, 'attr', attr);
327  attachTarget(element, elementDiv);
328
329  const component: VmOptions | null = targetIsComposed(vm, dynamicType);
330  if (component) {
331    compileCustomComponent(vm, component, target, elementDiv, dynamicType, meta);
332    return;
333  }
334}
335
336/**
337 * Check if target type is slot.
338 *
339 * @param  {object}  target
340 * @return {boolean}
341 */
342function targetIsSlot(target: TemplateInterface) {
343  return target.type === 'slot';
344}
345
346/**
347 * Check if target needs to compile by a list.
348 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
349 * @param {MetaInterface} meta - To transfer data.
350 * @return {boolean} - True if target needs repeat. Otherwise return false.
351 */
352function targetNeedCheckRepeat(target: TemplateInterface, meta: Partial<MetaInterface>) {
353  return !hasOwn(meta, 'repeat') && target.repeat;
354}
355
356/**
357 * Check if target needs to compile by a 'if' or 'shown' value.
358 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
359 * @param {MetaInterface} meta - To transfer data.
360 * @return {boolean} - True if target needs a 'shown' value. Otherwise return false.
361 */
362function targetNeedCheckShown(target: TemplateInterface, meta: Partial<MetaInterface>) {
363  return !hasOwn(meta, 'shown') && target.shown;
364}
365
366/**
367 * Check if this kind of component is composed.
368 * @param {Vm} vm - Vm object needs to be compiled.
369 * @param {string} type - Component type.
370 * @return {VmOptions} Component.
371 */
372export function targetIsComposed(vm: Vm, type: string): VmOptions {
373  let component;
374  if (vm._app && vm._app.customComponentMap) {
375    component = vm._app.customComponentMap[type];
376  }
377  if (component) {
378    if (component.data && typeof component.data === 'object') {
379      if (!component.initObjectData) {
380        component.initObjectData = component.data;
381      }
382      const str = JSON.stringify(component.initObjectData);
383      component.data = JSON.parse(str);
384    }
385  }
386  return component;
387}
388
389/**
390 * Compile a target with repeat directive.
391 * @param {Vm} vm - Vm object needs to be compiled.
392 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
393 * @param {dest} dest - Node need to be appended.
394 */
395function compileSlot(vm: Vm, target: TemplateInterface, dest: Element): Element {
396  if (!vm._slotContext) {
397    // slot in root vm
398    return;
399  }
400
401  const slotDest = createBlock(vm, dest);
402
403  // reslove slot content
404  const namedContents = vm._slotContext.content;
405  const parentVm = vm._slotContext.parentVm;
406  const slotItem = { target, dest: slotDest };
407  const slotName = target.attr.name || 'default';
408
409  // acquire content by name
410  const namedContent = namedContents[slotName];
411  if (!namedContent) {
412    compileChildren(vm, slotItem.target, slotItem.dest);
413  } else {
414    // Bind slot scope
415    if (Array.isArray(namedContent)) {
416      namedContent.forEach((item: TemplateInterface) => {
417        const slotScope = item.attr && item.attr.slotScope;
418        if (typeof slotScope === 'string') {
419          parentVm[slotScope] = vm._data;
420        }
421      });
422    }
423    compileChildren(parentVm, { children: namedContent }, slotItem.dest);
424  }
425}
426
427/**
428 * Compile a target with repeat directive.
429 * @param {Vm} vm - Vm object needs to be compiled.
430 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
431 * @param {Element} dest - Parent Node's VM of current.
432 */
433function compileRepeat(vm: Vm, target: TemplateInterface, dest: Element): void {
434  const repeat = target.repeat;
435  let getter: any;
436  let key: any;
437  let value: any;
438  let trackBy: any;
439
440  if (isRepeat(repeat)) {
441    getter = repeat.exp;
442    key = repeat.key || '$idx';
443    value = repeat.value;
444    trackBy = target.attr && target.attr.tid;
445  } else {
446    getter = repeat;
447    key = '$idx';
448    value = '$item';
449    trackBy = target.attr && target.attr.tid;
450  }
451  if (typeof getter !== 'function') {
452    getter = function() {
453      return [];
454    };
455  }
456  const fragBlock: FragBlockInterface = createBlock(vm, dest);
457  fragBlock.children = [];
458  fragBlock.data = [];
459  fragBlock.vms = [];
460  bindRepeat(vm, target, fragBlock, { getter, key, value, trackBy });
461}
462
463/**
464 * Compile a target with 'if' directive.
465 * @param {Vm} vm - Vm object needs to be compiled.
466 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
467 * @param {FragBlockInterface | Element} dest - Parent Node's VM of current.
468 * @param {MetaInterface} meta - To transfer data.
469 */
470function compileShown(
471  vm: Vm,
472  target: TemplateInterface,
473  dest: Element | FragBlockInterface,
474  meta: Partial<MetaInterface>
475): void {
476  const newMeta: Partial<MetaInterface> = { shown: true };
477  const fragBlock = createBlock(vm, dest);
478  if (isBlock(dest) && dest.children) {
479    dest.children.push(fragBlock);
480  }
481  if (hasOwn(meta, 'repeat')) {
482    newMeta.repeat = meta.repeat;
483  }
484  bindShown(vm, target, fragBlock, newMeta);
485}
486
487/**
488 * Support <block>.
489 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
490 * @return {boolean} True if target supports bolck. Otherwise return false.
491 */
492function targetIsBlock(target: TemplateInterface): boolean {
493  return target.type === 'block';
494}
495
496/**
497 * If <block> create block and compile the children node.
498 * @param {Vm} vm - Vm object needs to be compiled.
499 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
500 * @param {Element | FragBlockInterface} dest - Parent Node's VM of current.
501 */
502function compileBlock(vm: Vm, target: TemplateInterface, dest: Element | FragBlockInterface): void {
503  const block = createBlock(vm, dest);
504  if (isBlock(dest) && dest.children) {
505    dest.children.push(block);
506  }
507  const app: any = vm._app || {};
508  const children = target.children;
509  if (children && children.length) {
510    children.every((child) => {
511      compile(vm, child, block);
512      return app.lastSignal !== -1;
513    });
514  }
515}
516
517/**
518 * Compile a composed component.
519 * @param {Vm} vm - Vm object needs to be compiled.
520 * @param {VmOptions} component - Composed component.
521 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
522 * @param {Element | FragBlockInterface} dest - Parent Node's VM of current.
523 * @param {string} type - Component Type.
524 * @param {MetaInterface} meta - To transfer data.
525 */
526export function compileCustomComponent(
527  vm: Vm,
528  component: VmOptions,
529  target: TemplateInterface,
530  dest: Element | FragBlockInterface,
531  type: string,
532  meta: Partial<MetaInterface>
533): void {
534  const subVm = new Vm(
535    type,
536    component,
537    vm,
538    dest,
539    undefined,
540    {
541      'hook:_innerInit': function() {
542        // acquire slot content of context
543        const namedContents = {};
544        if (target.children) {
545          target.children.forEach(item => {
546            const slotName = item.attr.slot || 'default';
547            if (namedContents[slotName]) {
548              namedContents[slotName].push(item);
549            } else {
550              namedContents[slotName] = [item];
551            }
552          });
553        }
554        this.__slotContext = { content: namedContents, parentVm: vm };
555        setId(vm, null, target.id, this);
556
557        // Bind template earlier because of lifecycle issues.
558        this.__externalBinding = {
559          parent: vm,
560          template: target
561        };
562
563        // Bind props before init data.
564        bindSubVm(vm, this, target, meta.repeat);
565      }
566    });
567  bindSubVmAfterInitialized(vm, subVm, target, dest);
568}
569
570/**
571 * Reset the element style.
572 * @param {Vm} vm - Vm object needs to be compiled.
573 * @param {Element} element - To be reset.
574 */
575function resetElementStyle(vm: Vm, element: Element): void {
576  // Add judgment statements to avoid repeatedly calling 'setClass' function.
577  let len = 0;
578  if (element.children !== undefined) {
579    len = element.children.length;
580  }
581  const css = vm._css || {};
582  const mqArr = css['@MEDIA'];
583  for (let ii = 0; ii < len; ii++) {
584    const el = element.children[ii] as Element;
585    if (!el.isCustomComponent) {
586      resetElementStyle(vm, el);
587    }
588  }
589  setUniversalStyle(vm, element);
590  if (element.type) {
591    setTagStyle(vm, element, element.type, false, false, false);
592  }
593  if (element.id) {
594    setIdStyle(vm, element, element.id);
595  }
596  if (element.classList && mqArr) {
597    for (let i = 0; i < element.classList.length; i++) {
598      for (let m = 0; m < mqArr.length; m++) {
599        const clsKey = '.' + element.classList[i];
600        if (hasOwn(mqArr[m], clsKey)) {
601          setClass(vm, element, element.classList);
602        }
603      }
604    }
605  }
606}
607
608/**
609 * <p>Generate element from template and attach to the dest if needed.</p>
610 * <p>The time to attach depends on whether the mode status is node or tree.</p>
611 * @param {Vm} vm - Vm object needs to be compiled.
612 * @param {TemplateInterface} template - Generate element from template.
613 * @param {FragBlockInterface | Element} dest - Parent Node's VM of current.
614 * @param {string} type - Vm type.
615 */
616function compileNativeComponent(vm: Vm, template: TemplateInterface, dest: FragBlockInterface | Element, type: string): void {
617  function handleViewSizeChanged(e) {
618    if (!vm._mediaStatus) {
619      vm._mediaStatus = {};
620    }
621    vm._mediaStatus.orientation = e.orientation;
622    vm._mediaStatus.width = e.width;
623    vm._mediaStatus.height = e.height;
624    vm._mediaStatus.resolution = e.resolution;
625    vm._mediaStatus['device-type'] = e['device-type'];
626    vm._mediaStatus['aspect-ratio'] = e['aspect-ratio'];
627    vm._mediaStatus['device-width'] = e['device-width'];
628    vm._mediaStatus['device-height'] = e['device-height'];
629    vm._mediaStatus['round-screen'] = e['round-screen'];
630    vm._mediaStatus['dark-mode'] = e['dark-mode'];
631    const css = vm._vmOptions && vm._vmOptions.style || {};
632    const mqArr = css['@MEDIA'];
633    if (!mqArr) {
634      return;
635    }
636    if (e.isInit && vm._init) {
637      return;
638    }
639    vm._init = true;
640    resetElementStyle(vm, e.currentTarget);
641    e.currentTarget.addEvent('show');
642  }
643
644  let element;
645  if (!isBlock(dest) && dest.ref === '_documentElement') {
646    // If its parent is documentElement then it's a body.
647    element = createBody(vm, type);
648  } else {
649    element = createElement(vm, type);
650    element.destroyHook = function() {
651      if (element.block !== undefined) {
652        removeTarget(element.block);
653      }
654      if (element.watchers !== undefined) {
655        element.watchers.forEach(function(watcher) {
656          watcher.teardown();
657        });
658        element.watchers = [];
659      }
660    };
661  }
662
663  if (!vm._rootEl) {
664    vm._rootEl = element;
665    // Bind event earlier because of lifecycle issues.
666    const binding: any = vm._externalBinding || {};
667    const target = binding.template;
668    const parentVm = binding.parent;
669    if (target && target.events && parentVm && element) {
670      for (const type in target.events) {
671        const handler = parentVm[target.events[type]];
672        if (handler) {
673          element.addEvent(type, handler.bind(parentVm));
674        }
675      }
676    }
677    // Page show hide life circle hook function.
678    bindPageLifeCycle(vm, element);
679    element.setCustomFlag();
680    element.customFlag = true;
681    vm._init = true;
682    element.addEvent('viewsizechanged', handleViewSizeChanged);
683  }
684
685  // Dest is parent element.
686  bindElement(vm, element, template, dest);
687  if (element.event && element.event['attached']) {
688    element.fireEvent('attached', {});
689  }
690
691  if (template.attr && template.attr.append) {
692    template.append = template.attr.append;
693  }
694  if (template.append) {
695    element.attr = element.attr || {};
696    element.attr.append = template.append;
697  }
698  let treeMode = template.append === 'tree';
699  const app: any = vm._app || {};
700
701  // Record the parent node of treeMode, used by class selector.
702  if (treeMode) {
703    if (!global.treeModeParentNode) {
704      global.treeModeParentNode = dest;
705    } else {
706      treeMode = false;
707    }
708  }
709  if (app.lastSignal !== -1 && !treeMode) {
710    app.lastSignal = attachTarget(element, dest);
711  }
712  if (app.lastSignal !== -1) {
713    compileChildren(vm, template, element);
714  }
715  if (app.lastSignal !== -1 && treeMode) {
716    delete global.treeModeParentNode;
717    app.lastSignal = attachTarget(element, dest);
718  }
719}
720
721/**
722 * Set all children to a certain parent element.
723 * @param {Vm} vm - Vm object needs to be compiled.
724 * @param {any} template - Generate element from template.
725 * @param {Element | FragBlockInterface} dest - Parent Node's VM of current.
726 * @return {void | boolean} If there is no children, return null. Return true if has node.
727 */
728function compileChildren(vm: Vm, template: any, dest: Element | FragBlockInterface): void | boolean {
729  const app: any = vm._app || {};
730  const children = template.children;
731  if (children && children.length) {
732    children.every((child) => {
733      compile(vm, child, dest);
734      return app.lastSignal !== -1;
735    });
736  }
737}
738
739/**
740 * Watch the list update and refresh the changes.
741 * @param {Vm} vm - Vm object need to be compiled.
742 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
743 * @param {FragBlockInterface} fragBlock - {vms, data, children}
744 * @param {*} info - {getter, key, value, trackBy, oldStyle}
745 */
746function bindRepeat(vm: Vm, target: TemplateInterface, fragBlock: FragBlockInterface, info: any): void {
747  const vms = fragBlock.vms;
748  const children = fragBlock.children;
749  const { getter, trackBy } = info;
750  const keyName = info.key;
751  const valueName = info.value;
752
753  function compileItem(item: any, index: number, context: Vm) {
754    const mergedData = {};
755    mergedData[keyName] = index;
756    mergedData[valueName] = item;
757    const newContext = mergeContext(context, mergedData);
758    vms.push(newContext);
759    compile(newContext, target, fragBlock, { repeat: item });
760  }
761  const list = watchBlock(vm, fragBlock, getter, 'repeat',
762    (data) => {
763      Log.debug(`The 'repeat' item has changed ${data}.`);
764      if (!fragBlock || !data) {
765        return;
766      }
767      const oldChildren = children.slice();
768      const oldVms = vms.slice();
769      const oldData = fragBlock.data.slice();
770
771      // Collect all new refs track by.
772      const trackMap = {};
773      const reusedMap = {};
774      data.forEach((item, index) => {
775        const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index;
776        if (key === null || key === '') {
777          return;
778        }
779        trackMap[key] = item;
780      });
781      // Remove unused element foreach old item.
782      const reusedList: any[] = [];
783      const cacheList: any[] = [];
784      oldData.forEach((item, index) => {
785        const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index;
786        if (hasOwn(trackMap, key)) {
787          reusedMap[key] = {
788            item, index, key,
789            target: oldChildren[index],
790            vm: oldVms[index]
791          };
792          reusedList.push(item);
793        } else {
794          cacheList.push({
795            target: oldChildren[index],
796            vm: oldVms[index]
797          });
798        }
799      });
800      // Create new element for each new item.
801      children.length = 0;
802      vms.length = 0;
803      fragBlock.data = data.slice();
804      fragBlock.updateMark = fragBlock.start;
805
806      data.forEach((item, index) => {
807        const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index;
808        const reused = reusedMap[key];
809        if (reused) {
810          if (reused.item === reusedList[0]) {
811            reusedList.shift();
812          } else {
813            removeItem(reusedList, reused.item);
814            moveTarget(reused.target, fragBlock.updateMark);
815          }
816          children.push(reused.target);
817          vms.push(reused.vm);
818          reused.vm[valueName] = item;
819
820          reused.vm[keyName] = index;
821          fragBlock.updateMark = reused.target;
822        } else {
823          if (cacheList.length > 0) {
824            const reusedItem = cacheList[0];
825            cacheList.shift();
826            moveTarget(reusedItem.target, fragBlock.updateMark);
827            children.push(reusedItem.target);
828            vms.push(reusedItem.vm);
829            reusedItem.vm[valueName] = item;
830
831            reusedItem.vm[keyName] = index;
832            fragBlock.updateMark = reusedItem.target;
833          } else {
834            compileItem(item, index, vm);
835          }
836        }
837      });
838      delete fragBlock.updateMark;
839      cacheList.forEach((item) => {
840        removeTarget(item.target);
841      });
842    }
843  );
844  if (list && Array.isArray(list)) {
845    fragBlock.data = list.slice(0);
846    list.forEach((item, index) => {
847      compileItem(item, index, vm);
848    });
849  }
850}
851
852/**
853 * Watch the display update and add/remove the element.
854 * @param {Vm} vm - Vm object needs to be compiled.
855 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template.
856 * @param {FragBlockInterface} fragBlock - {vms, data, children}
857 * @param {MetaInterface} meta - To transfer data.
858 */
859function bindShown(
860  vm: Vm,
861  target: TemplateInterface,
862  fragBlock: FragBlockInterface,
863  meta: Partial<MetaInterface>
864): void {
865  const display = watchBlock(vm, fragBlock, target.shown, 'shown',
866    (display) => {
867      Log.debug(`The 'if' item was changed ${display}.`);
868      if (!fragBlock || !!fragBlock.display === !!display) {
869        return;
870      }
871      fragBlock.display = !!display;
872      if (display) {
873        compile(vm, target, fragBlock, meta);
874      } else {
875        removeTarget(fragBlock, true);
876      }
877    }
878  );
879
880  fragBlock.display = !!display;
881  if (display) {
882    compile(vm, target, fragBlock, meta);
883  }
884}
885
886/**
887 * Watch calc changes and append certain type action to differ.
888 * @param {Vm} vm - Vm object needs to be compiled.
889 * @param {FragBlockInterface} fragBlock - {vms, data, children}
890 * @param {Function} calc - Function.
891 * @param {string} type - Vm type.
892 * @param {Function} handler - Function.
893 * @return {*} Init value of calc.
894 */
895function watchBlock(vm: Vm, fragBlock: FragBlockInterface, calc: Function, type: string, handler: Function): any {
896  const differ = vm && vm._app && vm._app.differ;
897  const config: Partial<ConfigInterface> = {};
898  const newWatcher = newWatch(vm, calc, (value) => {
899    config.latestValue = value;
900    if (differ && !config.recorded) {
901      differ.append(type, fragBlock.blockId.toString(), () => {
902        const latestValue = config.latestValue;
903        handler(latestValue);
904        config.recorded = false;
905        config.latestValue = undefined;
906      });
907    }
908    config.recorded = true;
909  });
910  fragBlock.end.watchers.push(newWatcher);
911  return newWatcher.value;
912}
913
914/**
915 * Clone a context and merge certain data.
916 * @param {Vm} context - Context value.
917 * @param {Object} mergedData - Certain data.
918 * @return {*} The new context.
919 */
920function mergeContext(context: Vm, mergedData: object): any {
921  const newContext = Object.create(context);
922  newContext.__data = mergedData;
923  newContext.__shareData = {};
924  initData(newContext);
925  initComputed(newContext);
926  newContext.__realParent = context;
927  return newContext;
928}
929
930/**
931 * Check if it needs repeat.
932 * @param {Function | RepeatInterface} repeat - Repeat value.
933 * @return {boolean} - True if it needs repeat. Otherwise return false.
934 */
935function isRepeat(repeat: Function | RepeatInterface): repeat is RepeatInterface {
936  const newRepeat = <RepeatInterface>repeat;
937  return newRepeat.exp !== undefined;
938}
939
940/**
941 * Check if it is a block.
942 * @param {FragBlockInterface | Node} node - Node value.
943 * @return {boolean} - True if it is a block. Otherwise return false.
944 */
945export function isBlock(node: FragBlockInterface | Node): node is FragBlockInterface {
946  const newNode = <FragBlockInterface>node;
947  return newNode.blockId !== undefined;
948}
949