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