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 * 2021.01.08 - Rewrite some functions and remove some redundant judgments to fit framework. 21 * Copyright (c) 2021 Huawei Device Co., Ltd. 22 */ 23 24/** 25 * @fileOverview 26 * Directive Parser 27 */ 28 29import { 30 typof, 31 camelize, 32 Log 33} from '../../utils/index'; 34import Watcher from '../reactivity/watcher'; 35import { 36 setDescendantStyle 37} from './selector'; 38import { 39 getDefaultPropValue 40} from '../util/props'; 41import { 42 matchMediaQueryCondition 43} from '../extend/mediaquery/mediaQuery'; 44import { 45 TemplateInterface, 46 FragBlockInterface, 47 AttrInterface 48} from './compiler'; 49import Vm from './index'; 50import Element from '../../vdom/Element'; 51 52const SETTERS = { 53 attr: 'setAttr', 54 style: 'setStyle', 55 data: 'setData', 56 $data: 'setData', 57 event: 'addEvent', 58 idStyle: 'setIdStyle', 59 tagStyle: 'setTagStyle', 60 attrStyle: 'setAttrStyle', 61 tagAndTagStyle: 'setTagAndTagStyle', 62 tagAndIdStyle: 'setTagAndIdStyle', 63 universalStyle: 'setUniversalStyle', 64 firstOrLastChildStyle: 'setFirstOrLastChildStyle' 65}; 66 67/* eslint-disable no-unused-vars */ 68enum ContentType { 69 CONTENT_STRING, 70 CONTENT_OPEN_QUOTE, 71 CONTENT_CLOSE_QUOTE, 72 CONTENT_ATTR, 73 CONTENT_COUNTER 74} 75 76interface ContentObject { 77 value: string, 78 contentType: ContentType 79} 80let finallyItems: Array <ContentObject> = []; 81 82/** 83 * Bind id, attr, classnames, style, events to an element. 84 * @param {Vm} vm - Vm object. 85 * @param {Element} el - Element to be bind. 86 * @param {TemplateInterface} template - Structure of the component. 87 * @param {Element | FragBlockInterface} parentElement - Parent element of current element. 88 */ 89export function bindElement(vm: Vm, el: Element, template: TemplateInterface, parentElement: Element | FragBlockInterface): void { 90 // Set descendant style. 91 setDescendantStyle( 92 vm._selector, 93 { 94 id: template.id, 95 class: template.classList, 96 tag: template.type 97 }, 98 parentElement, 99 vm, 100 function(style: {[key: string]: any}) { 101 if (!style) { 102 return; 103 } 104 const css = vm._css || {}; 105 setAnimation(style, css); 106 setFontFace(style, css); 107 setStyle(vm, el, style); 108 } 109 ); 110 111 // inherit 'show' attribute of custom component 112 if (el.isCustomComponent) { 113 const value = vm['show']; 114 if (template.attr && value !== undefined) { 115 if (typeof value === 'function') { 116 // vm['show'] is assigned to this.show in initPropsToData() 117 template.attr['show'] = function() { 118 return this.show; 119 }; 120 } else { 121 template.attr['show'] = value; 122 } 123 } 124 } 125 126 setId(vm, el, template.id, vm); 127 setAttr(vm, el, template.attr); 128 setStyle(vm, el, template.style); 129 setIdStyle(vm, el, template.id); 130 setClass(vm, el, template.classList); 131 setTagStyle(vm, el, template.type, false, false, true); 132 setTagAndIdStyle(vm, el, template.type, template.id); 133 setUniversalStyle(vm, el); 134 applyStyle(vm, el); 135 136 bindEvents(vm, el, template.events); 137 bindEvents(vm, el, template.onBubbleEvents, ''); 138 bindEvents(vm, el, template.onCaptureEvents, 'capture'); 139 bindEvents(vm, el, template.catchBubbleEvents, 'catchbubble'); 140 bindEvents(vm, el, template.catchCaptureEvents, 'catchcapture'); 141 142 if (!vm._isHide && !vm._init) { 143 el.addEvent('hide'); 144 vm._isHide = true; 145 } 146} 147 148/** 149 * <p>Bind all props to sub vm and bind all style, events to the root element</p> 150 * <p>of the sub vm if it doesn't have a replaced multi-node fragment.</p> 151 * @param {Vm} vm - Vm object. 152 * @param {Vm} subVm - Sub vm. 153 * @param {TemplateInterface} template - Structure of the component. 154 * @param {Object} repeatItem - Item object. 155 */ 156export function bindSubVm(vm: Vm, rawSubVm: Vm, rawTemplate: TemplateInterface, repeatItem: object): void { 157 const subVm: any = rawSubVm || {}; 158 const template: any = rawTemplate || {}; 159 const options: any = subVm._vmOptions || {}; 160 161 let props = options.props; 162 if (isArray(props) || !props) { 163 if (isArray(props)) { 164 props = props.reduce((result, value) => { 165 result[value] = true; 166 return result; 167 }, {}); 168 } 169 mergeProps(repeatItem, props, vm, subVm); 170 mergeProps(template.attr, props, vm, subVm); 171 } else { 172 const attrData = template.attr || {}; 173 const repeatData = repeatItem || {}; 174 Object.keys(props).forEach(key => { 175 const prop = props[key]; 176 let value = attrData[key] || repeatData[key] || undefined; 177 if (value === undefined) { 178 value = getDefaultPropValue(vm, prop); 179 } 180 mergePropsObject(key, value, vm, subVm); 181 }); 182 } 183} 184 185/** 186 * Merge class and styles from vm to sub vm. 187 * @param {Vm} vm - Vm object. 188 * @param {Vm} subVm - Sub vm. 189 * @param {TemplateInterface} template - Structure of the component. 190 * @param {Element | FragBlockInterface} target - The target of element. 191 */ 192export function bindSubVmAfterInitialized(vm: Vm, subVm: Vm, template: TemplateInterface, target: Element | FragBlockInterface): void { 193 mergeClassStyle(template.classList, vm, subVm); 194 mergeStyle(template.style, vm, subVm); 195 if (target.children) { 196 target.children[target.children.length - 1]._vm = subVm; 197 } else { 198 target.vm = subVm; 199 } 200 bindSubEvent(vm, subVm, template); 201} 202 203/** 204 * Bind custom event from vm to sub vm for calling parent method. 205 * @param {Vm} vm - Vm object. 206 * @param {Vm} subVm - Sub vm. 207 * @param {TemplateInterface} template - Structure of the component. 208 */ 209function bindSubEvent(vm: Vm, subVm: Vm, template: TemplateInterface): void { 210 if (template.events) { 211 for (const type in template.events) { 212 subVm.$on(camelize(type), function() { 213 const args = []; 214 for (const i in arguments) { 215 args[i] = arguments[i]; 216 } 217 if (vm[template.events[type]] 218 && typeof vm[template.events[type]] === 'function') { 219 vm[template.events[type]].apply(vm, args); 220 } 221 }); 222 } 223 } 224} 225 226/** 227 * Merge props from vm to sub vm. 228 * @param {string} key - Get vm object by key. 229 * @param {*} value - Default Value. 230 * @param {Vm} vm - Vm object. 231 * @param {Vm} subVm - Sub vm. 232 * @return {*} Sub vm object. 233 */ 234function mergePropsObject(key: string, value: any, vm: Vm, subVm: Vm): any { 235 subVm._props.push(key); 236 if (typeof value === 'function') { 237 const returnValue = watch(vm, value, function(v) { 238 subVm[key] = v; 239 }); 240 // 'show' attribute will be inherited by elements in custom component 241 if (key === 'show') { 242 subVm[key] = value; 243 } else { 244 subVm[key] = returnValue; 245 } 246 } else { 247 const realValue = 248 value && value.__hasDefault ? value.__isDefaultValue : value; 249 subVm[key] = realValue; 250 } 251 return subVm[key]; 252} 253 254/** 255 * Bind props from vm to sub vm and watch their updates. 256 * @param {Object} target - Target object. 257 * @param {*} props - Vm props. 258 * @param {Vm} vm - Vm object. 259 * @param {Vm} subVm - Sub vm. 260 */ 261function mergeProps(target: object, props: any, vm: Vm, subVm: Vm): void { 262 if (!target) { 263 return; 264 } 265 for (const key in target) { 266 if (!props || props[key] || key === 'show') { 267 subVm._props.push(key); 268 const value = target[key]; 269 if (typeof value === 'function') { 270 const returnValue = watch(vm, value, function(v) { 271 subVm[key] = v; 272 }); 273 // 'show' attribute will be inherited by elements in custom component 274 if (key === 'show') { 275 subVm[key] = value; 276 } else { 277 subVm[key] = returnValue; 278 } 279 } else { 280 subVm[key] = value; 281 } 282 } 283 } 284} 285 286/** 287 * Bind style from vm to sub vm and watch their updates. 288 * @param {Object} target - Target object. 289 * @param {Vm} vm - Vm object. 290 * @param {Vm} subVm - Sub vm. 291 */ 292function mergeStyle(target: { [key: string]: any }, vm: Vm, subVm: Vm): void { 293 for (const key in target) { 294 const value = target[key]; 295 if (typeof value === 'function') { 296 const returnValue = watch(vm, value, function(v) { 297 if (subVm._rootEl) { 298 subVm._rootEl.setStyle(key, v); 299 } 300 }); 301 subVm._rootEl.setStyle(key, returnValue); 302 } else { 303 if (subVm._rootEl) { 304 subVm._rootEl.setStyle(key, value); 305 } 306 } 307 } 308} 309 310/** 311 * Bind class and style from vm to sub vm and watch their updates. 312 * @param {Object} target - Target object. 313 * @param {Vm} vm - Vm object. 314 * @param {Vm} subVm - Sub vm. 315 */ 316function mergeClassStyle(target: Function | string[], vm: Vm, subVm: Vm): void { 317 const css = vm._css || {}; 318 if (!subVm._rootEl) { 319 return; 320 } 321 322 /** 323 * Class name. 324 * @constant {string} 325 */ 326 const CLASS_NAME = '@originalRootEl'; 327 css['.' + CLASS_NAME] = subVm._rootEl.classStyle; 328 329 function addClassName(list, name) { 330 if (typof(list) === 'array') { 331 list.unshift(name); 332 } 333 } 334 335 if (typeof target === 'function') { 336 const value = watch(vm, target, v => { 337 addClassName(v, CLASS_NAME); 338 setClassStyle(subVm._rootEl, css, v); 339 }); 340 addClassName(value, CLASS_NAME); 341 setClassStyle(subVm._rootEl, css, value); 342 } else if (target !== undefined) { 343 addClassName(target, CLASS_NAME); 344 setClassStyle(subVm._rootEl, css, target); 345 } 346} 347 348/** 349 * Bind id to an element. Note: Each id is unique in a whole vm. 350 * @param {Vm} vm - Vm object. 351 * @param {Element} el - Element object. 352 * @param {Function | string} id - Unique vm id. 353 * @param {Vm} target - Target vm. 354 */ 355export function setId(vm: Vm, el: Element, id: Function | string, target: Vm): void { 356 const map = Object.create(null); 357 Object.defineProperties(map, { 358 vm: { 359 value: target, 360 writable: false, 361 configurable: false 362 }, 363 el: { 364 get: () => el || target._rootEl, 365 configurable: false 366 } 367 }); 368 if (typeof id === 'function') { 369 const handler = id; 370 const newId = handler.call(vm); 371 if (newId || newId === 0) { 372 setElementId(el, newId); 373 vm._ids[newId] = map; 374 } 375 watch(vm, handler, (newId) => { 376 if (newId) { 377 setElementId(el, newId); 378 vm._ids[newId] = map; 379 } 380 }); 381 } else if (id && typeof id === 'string') { 382 setElementId(el, id); 383 vm._ids[id] = map; 384 } 385} 386 387/** 388 * Set id to Element. 389 * @param {Element} el - Element object. 390 * @param {string} id - Element id. 391 */ 392function setElementId(el: Element, id: string): void { 393 if (el) { 394 el.id = id; 395 } 396} 397 398/** 399 * Bind attr to an element. 400 * @param {Vm} vm - Vm object. 401 * @param {Element} el - Element. 402 * @param {AttrInterface} attr - Attr to bind. 403 */ 404export function setAttr(vm: Vm, el: Element, attr: Partial<AttrInterface>): void { 405 if (attr) { 406 // address $data or data independently 407 if (attr.$data) { 408 bindDir(vm, el, '$data', attr.$data); 409 } else if (attr.data && Object.prototype.toString.call(attr.data) === '[object Object]') { 410 bindDir(vm, el, 'data', attr.data); 411 } 412 } 413 bindDir(vm, el, 'attr', attr); 414} 415 416/** 417 * Set font family and get font resource. 418 * @param {Object} css - Css style. 419 * @param {string | string[]} fontFamilyNames - Font family names. 420 * @return {*} Font resource. 421 */ 422function _getFontFamily(css: any, fontFamilyNames: string | string[]): any[] { 423 let results = []; 424 const familyMap = css['@FONT-FACE']; 425 if (typeof fontFamilyNames === 'string') { 426 fontFamilyNames.split(',').forEach(fontFamilyName => { 427 fontFamilyName = fontFamilyName.trim(); 428 let find = false; 429 if (familyMap && Array.isArray(familyMap)) { 430 let len = familyMap.length; 431 while (len) { 432 if ( 433 familyMap[len - 1].fontFamily && 434 familyMap[len - 1].fontFamily === fontFamilyName 435 ) { 436 results.push(familyMap[len - 1]); 437 find = true; 438 } 439 len--; 440 } 441 } else if (familyMap && typeof familyMap === 'object') { 442 const definedFontFamily = familyMap[fontFamilyName]; 443 if (definedFontFamily && definedFontFamily.src) { 444 if (Array.isArray(definedFontFamily.src)) { 445 definedFontFamily.src = definedFontFamily.src.map(item => `url("${item}")`).join(','); 446 } 447 results.push(definedFontFamily); 448 find = true; 449 } 450 } 451 if (!find) { 452 results.push({ 'fontFamily': fontFamilyName }); 453 } 454 }); 455 } else if (Array.isArray(fontFamilyNames)) { 456 results = fontFamilyNames; 457 } else if (fontFamilyNames) { 458 Log.warn(`GetFontFamily Array error, unexpected fontFamilyNames type [${typeof fontFamilyNames}].`); 459 } 460 return results; 461} 462 463/** 464 * Select class style. 465 * @param {Object} css - Css style. 466 * @param {Function | string[]} classList - List of class label. 467 * @param {number} index - Index of classList. 468 * @return {*} Select style. 469 */ 470function selectClassStyle(css: object, classList: Function | string[], index: number, vm: Vm): any { 471 const key = '.' + classList[index]; 472 return selectStyle(css, key, vm); 473} 474 475/** 476 * Select id style. 477 * @param {Object} css - Css style. 478 * @param {string} id - Id label. 479 * @param {Vm} vm - Vm object. 480 * @return {*} Select style. 481 */ 482function selectIdStyle(css: object, id: string, vm: Vm): any { 483 const key = '#' + id; 484 return selectStyle(css, key, vm); 485} 486 487function selectTagAndIdStyle(css: object, tag: string, id: string, vm: Vm): any { 488 const key = tag + '#' + id; 489 return selectStyle(css, key, vm); 490} 491 492/** 493 * Replace style. 494 * @param {*} oStyle - Current style. 495 * @param {*} rStyle - New style. 496 */ 497function replaceStyle(oStyle: any, rStyle: any): void { 498 if (!rStyle || rStyle.length <= 0) { 499 return; 500 } 501 Object.keys(rStyle).forEach(function(key) { 502 oStyle[key] = rStyle[key]; 503 }); 504} 505 506/** 507 * Select style for class label, id label. 508 * @param {Object} css - Css style. 509 * @param {string} key - Key index. 510 * @param {Vm} vm - Vm object. 511 * @return {*} 512 */ 513function selectStyle(css: object, key: string, vm: Vm): any { 514 const style = css[key]; 515 if (!vm) { 516 return style; 517 } 518 const mediaStatus = vm._mediaStatus; 519 if (!mediaStatus) { 520 return style; 521 } 522 const mqArr = css['@MEDIA']; 523 if (!mqArr) { 524 vm._init = true; 525 return style; 526 } 527 const classStyle = {}; 528 if (style) { 529 Object.keys(style).forEach(function(key) { 530 classStyle[key] = style[key]; 531 setCounterValue(vm, key, style[key]); 532 }); 533 } 534 for (let i$1 = 0; i$1 < mqArr.length; i$1++) { 535 if (matchMediaQueryCondition(mqArr[i$1].condition, mediaStatus, false)) { 536 replaceStyle(classStyle, mqArr[i$1][key]); 537 } 538 } 539 return classStyle; 540} 541 542/** 543 * Set class style after SelectClassStyle. 544 * @param {Element} el - Element object. 545 * @param {Object} css - Css style. 546 * @param {string[]} classList - List of class label. 547 */ 548function setClassStyle(el: Element, css: object, classList: string[], vm?: Vm): void { 549 const SPACE_REG: RegExp = /\s+/; 550 const newClassList: string[] = []; 551 if (Array.isArray(classList)) { 552 classList.forEach(v => { 553 if (typeof v === 'string' && SPACE_REG.test(v)) { 554 newClassList.push(...v.trim().split(SPACE_REG)); 555 } else { 556 newClassList.push(v); 557 } 558 }); 559 } 560 classList = newClassList; 561 const classStyle = {}; 562 const length = classList.length; 563 if (length === 1) { 564 const style = selectClassStyle(css, classList, 0, vm); 565 if (style) { 566 Object.keys(style).forEach((key) => { 567 classStyle[key] = style[key]; 568 setCounterValue(vm, key, style[key]); 569 }); 570 } 571 } else { 572 const rets = []; 573 const keys = Object.keys(css || {}); 574 for (let i = 0; i < length; i++) { 575 const clsKey = '.' + classList[i]; 576 const style = selectStyle(css, clsKey, vm); 577 if (style) { 578 const order = clsKey === '.@originalRootEl' ? -1000 : keys.indexOf(clsKey); 579 rets.push({style: style, order: order}); 580 } 581 } 582 if (rets.length === 1) { 583 const style = rets[0].style; 584 if (style) { 585 Object.keys(style).forEach((key) => { 586 classStyle[key] = style[key]; 587 setCounterValue(vm, key, style[key]); 588 }); 589 } 590 } else if (rets.length > 1) { 591 rets.sort(function(a, b) { 592 if (!a) { 593 return -1; 594 } else if (!b) { 595 return 1; 596 } else { 597 return a.order > b.order ? 1 : -1; 598 } 599 }); 600 const retStyle = {}; 601 rets.forEach(function(key) { 602 if (key && key.style) { 603 Object.assign(retStyle, key.style); 604 } 605 }); 606 Object.keys(retStyle).forEach((key) => { 607 classStyle[key] = retStyle[key]; 608 setCounterValue(vm, key, retStyle[key]); 609 }); 610 } 611 } 612 613 const keyframes = css['@KEYFRAMES']; 614 if (keyframes) { 615 /* 616 * Assign @KEYFRAMES's value. 617 */ 618 const animationName = classStyle['animationName']; 619 if (animationName) { 620 classStyle['animationName'] = keyframes[animationName]; 621 if (classStyle['animationName']) { 622 classStyle['animationName'].push({'animationName': animationName}); 623 } 624 } 625 const transitionEnter = classStyle['transitionEnter']; 626 if (transitionEnter) { 627 classStyle['transitionEnter'] = keyframes[transitionEnter]; 628 classStyle['transitionEnterName'] = transitionEnter; 629 } 630 const transitionExit = classStyle['transitionExit']; 631 if (transitionExit) { 632 classStyle['transitionExit'] = keyframes[transitionExit]; 633 classStyle['transitionExitName'] = transitionExit; 634 } 635 const sharedTransitionName = classStyle['sharedTransitionName']; 636 if (sharedTransitionName) { 637 classStyle['sharedTransitionName'] = keyframes[sharedTransitionName]; 638 } 639 } 640 const fontFace = classStyle['fontFamily']; 641 if (fontFace) { 642 const fontCompileList = _getFontFamily(css, fontFace); 643 classStyle['fontFamily'] = fontCompileList; 644 } 645 el.setClassStyle(classStyle); 646 el.classList = classList; 647} 648 649/** 650 * Bind classnames to an element 651 * @param {Vm} vm - Vm object. 652 * @param {Element} el - Element object. 653 * @param {Function | string[]} classList - List of class label. 654 */ 655export function setClass(vm: Vm, el: Element, classList: Function | string[]): void { 656 if (typeof classList !== 'function' && !Array.isArray(classList)) { 657 return; 658 } 659 if (Array.isArray(classList) && !classList.length) { 660 el.setClassStyle({}); 661 return; 662 } 663 const style = vm._css || {}; 664 if (typeof classList === 'function') { 665 const value = watch(vm, classList, v => { 666 setClassStyle(el, style, v, vm); 667 }); 668 setClassStyle(el, style, value, vm); 669 } else { 670 setClassStyle(el, style, classList, vm); 671 } 672} 673 674/** 675 * Support css selector by id and component. 676 * @param {Vm} vm - Vm object. 677 * @param {Element} el - ELement component. 678 * @param {Function | string} id - Id label. 679 */ 680export function setIdStyle(vm: Vm, el: Element, id: Function | string): void { 681 if (id) { 682 const css = vm._css || {}; 683 if (typeof id === 'function') { 684 const value = watch(vm, id, v => { 685 doSetStyle(vm, el, selectIdStyle(css, v, vm), css, 'idStyle'); 686 }); 687 doSetStyle(vm, el, selectIdStyle(css, value, vm), css, 'idStyle'); 688 } else if (typeof id === 'string') { 689 doSetStyle(vm, el, selectIdStyle(css, id, vm), css, 'idStyle'); 690 } 691 } 692} 693 694/** 695 * Set style. 696 * @param {Vm} vm - Vm object. 697 * @param {Element} el - ELement component. 698 * @param {*} style - Style to be Set. 699 * @param {*} css - Css style. 700 * @param {string} name - Bind by name. 701 */ 702function doSetStyle(vm: Vm, el: Element, style: any, css: any, name: string, isFirst?: boolean, isLast?: boolean, isSetContent?: boolean): void { 703 if (!style) { 704 return; 705 } 706 const typeStyle = {}; 707 Object.assign(typeStyle, style); 708 setAnimation(typeStyle, css); 709 setFontFace(typeStyle, css); 710 bindDir(vm, el, name, typeStyle, isFirst, isLast, isSetContent); 711} 712 713/** 714 * Set FontFace. 715 * @param {*} style - Style. 716 * @param {*} css - Css style. 717 */ 718function setFontFace(style: any, css: any): void { 719 const fontFace = style['fontFamily']; 720 if (fontFace) { 721 const fontCompileList = _getFontFamily(css, fontFace); 722 style['fontFamily'] = fontCompileList; 723 } 724} 725 726/** 727 * Set Animation 728 * @param {*} style - Style. 729 * @param {*} css - Css style. 730 */ 731function setAnimation(style: any, css: any): void { 732 const animationName = style['animationName']; 733 const keyframes = css['@KEYFRAMES']; 734 if (animationName && keyframes) { 735 style['animationName'] = keyframes[animationName]; 736 if (style['animationName']) { 737 style['animationName'].push({'animationName': animationName}); 738 } 739 } 740} 741 742/** 743 * Set tag style. 744 * @param {Vm} vm - Vm object. 745 * @param {Element} el - ELement component. 746 * @param {string} tag - Tag. 747 */ 748export function setTagStyle(vm: Vm, el: Element, tag: string, isFirst?: boolean, isLast?: boolean, isSetContent?: boolean): void { 749 const css = vm._css || {}; 750 if (tag && typeof tag === 'string') { 751 let tagStyle = 'tagStyle'; 752 if (tag.indexOf('+') > 0) { 753 tagStyle = 'tagAndTagStyle'; 754 } 755 doSetStyle(vm, el, selectStyle(css, tag, vm), css, tagStyle, isFirst, isLast, isSetContent); 756 } 757} 758 759export function setTagAndIdStyle(vm: Vm, el: Element, tag: string, id: Function | string): void { 760 const css = vm._css || {}; 761 if (typeof id === 'string') { 762 if (tag && typeof tag === 'string') { 763 doSetStyle(vm, el, selectTagAndIdStyle(css, tag, id, vm), css, 'tagAndIdStyle'); 764 } 765 } 766} 767 768/** 769 * Set * style. 770 * @param {Vm} vm - Vm object. 771 * @param {Element} el - ELement component. 772 */ 773export function setUniversalStyle(vm: Vm, el: Element): void { 774 const css = vm._css || {}; 775 doSetStyle(vm, el, selectStyle(css, '*', vm), css, 'universalStyle'); 776} 777 778/** 779 * Bind style to an element. 780 * @param {Vm} vm - Vm object. 781 * @param {Element} el - ELement component. 782 * @param {*} style - Style. 783 */ 784function setStyle(vm: Vm, el: Element, style: any): void { 785 bindDir(vm, el, 'style', style); 786} 787 788/** 789 * Add an event type and handler to an element and generate a dom update. 790 * @param {Vm} vm - Vm object. 791 * @param {Element} el - ELement component. 792 * @param {string} type - Type added to event. 793 * @param {Function} handler - Handle added to event. 794 */ 795function setEvent(vm: Vm, el: Element, type: string, handler: Function): void { 796 el.addEvent(type, handler.bind(vm)); 797} 798 799/** 800 * Add all events of an element. 801 * @param {Vm} vm - Vm object. 802 * @param {Element} el - ELement component. 803 * @param {Object} events - Events of an element. 804 */ 805function bindEvents(vm: Vm, el: Element, events: object, eventType?: string): void { 806 if (!events) { 807 return; 808 } 809 const keys = Object.keys(events); 810 let i = keys.length; 811 while (i--) { 812 const key = keys[i]; 813 let handler = events[key]; 814 if (typeof handler === 'string') { 815 handler = vm[handler]; 816 if (!handler || typeof handler !== 'function') { 817 Log.warn(`The event handler '${events[key]}' is undefined or is not function.`); 818 continue; 819 } 820 } 821 const eventName: string = eventType ? eventType + key : key; 822 setEvent(vm, el, eventName, handler); 823 } 824} 825 826/** 827 * <p>Set a series of members as a kind of an element.</p> 828 * <p>for example: style, attr, ...</p> 829 * <p>if the value is a function then bind the data changes.</p> 830 * @param {Vm} vm - Vm object. 831 * @param {Element} el - ELement component. 832 * @param {string} name - Method name. 833 * @param {Object} data - Data that needed. 834 */ 835export function bindDir(vm: Vm, el: Element, name: string, data: object, isFirst?: boolean, isLast?: boolean, isSetContent?: boolean): void { 836 if (!data) { 837 return; 838 } 839 const keys = Object.keys(data); 840 let i = keys.length; 841 if (!i) { 842 return; 843 } 844 let methodName = SETTERS[name]; 845 let method = el[methodName]; 846 const isSetStyle = methodName === 'setStyle'; 847 if (methodName === 'setIdStyle') { 848 for (const id in el.idStyle) { 849 el.idStyle[id] = ''; 850 } 851 } 852 if (name === 'tagStyle' || name === 'tagAndIdStyle') { 853 let j: number = 0; 854 let k: number = 0; 855 let temp: string = null; 856 for (j = 0; j < i - 1; j++) { 857 for (k = 0; k < i - 1 - j; k++) { 858 if (keys[k] > keys[k + 1]) { 859 temp = keys[k + 1]; 860 keys[k + 1] = keys[k]; 861 keys[k] = temp; 862 } 863 } 864 } 865 } 866 while (i--) { 867 let key = keys[i]; 868 const value = data[key]; 869 if (name === 'tagStyle') { 870 if (key.endsWith(':first-child') || key.endsWith(':last-child')) { 871 methodName = SETTERS['firstOrLastChildStyle']; 872 } else { 873 methodName = SETTERS[name]; 874 } 875 if (key.endsWith(':first-child') && isFirst) { 876 key = key.replace(':first-child', ''); 877 } else if (key.endsWith(':last-child') && isLast) { 878 key = key.replace(':last-child', ''); 879 } 880 if (key === 'counterIncrement::before' || key === 'counterIncrement::after' || key === 'counterIncrement') { 881 if (vm._counterMapping.has(value)) { 882 let counter = vm.$getCounterMapping(value); 883 vm.$setCounterMapping(value, ++counter); 884 } else { 885 vm.$setCounterMapping(value, 1); 886 } 887 } 888 889 if (isSetContent && (key === 'content::before' || key === 'content::after')) { 890 finallyItems = []; 891 splitItems(value); 892 const newValue = setContent(vm, el, key); 893 methodName = SETTERS['attr']; 894 method = el[methodName]; 895 method.call(el, 'value', newValue); 896 continue; 897 } 898 } 899 900 if (name === 'tagAndIdStyle') { 901 const newValue = updateTagAndIdStyle(el, key, value); 902 methodName = SETTERS['attr']; 903 method = el[methodName]; 904 method.call(el, 'value', newValue); 905 continue; 906 } 907 method = el[methodName]; 908 if (key === 'ref') { 909 vm.$refs[value] = el; 910 } 911 const isSetFont = isSetStyle && key === 'fontFamily'; 912 const setValue = function(value) { 913 if (isSetFont) { 914 value = filterFontFamily(vm, value); 915 } 916 method.call(el, key, value); 917 }; 918 if (typeof value === 'function') { 919 bindKey(vm, el, setValue, value); 920 } else { 921 setValue(value); 922 } 923 } 924} 925 926/** 927 * Bind data changes to a certain key to a name series in an element. 928 * @param {Vm} vm - Vm object. 929 * @param {Element} el - ELement component. 930 * @param {Function} setValue - Set value. 931 * @param {Function} calc - Watch the calc and return a value by calc.call(). 932 */ 933function bindKey(vm: Vm, el: Element, setValue: Function, calc: Function): void { 934 // Watch the calc, and returns a value by calc.call(). 935 const watcher = newWatch(vm, calc, (value) => { 936 function handler() { 937 setValue(value); 938 } 939 const differ = vm && vm._app && vm._app.differ; 940 if (differ) { 941 differ.append('element', el.ref, handler); 942 } else { 943 handler(); 944 } 945 }); 946 el.watchers.push(watcher); 947 setValue(watcher.value); 948} 949 950/** 951 * FontFamily Filter. 952 * @param {Vm} vm - Vm object. 953 * @param {string} fontFamilyName - FontFamily name. 954 * @return {*} FontFamily Filter. 955 */ 956export function filterFontFamily(vm: Vm, fontFamilyName: string): any[] { 957 const css = vm._css || {}; 958 return _getFontFamily(css, fontFamilyName); 959} 960 961/** 962 * Watch the calc. 963 * @param {Vm} vm - Vm object. 964 * @param {Function} calc - Watch the calc, and returns a value by calc.call(). 965 * @param {Function} callback - Callback callback Function. 966 * @return {Watcher} New watcher for rhe calc value. 967 */ 968export function newWatch(vm: Vm, calc: Function, callback: Function): Watcher { 969 const watcher = new Watcher(vm, calc, function(value, oldValue) { 970 if (typeof value !== 'object' && value === oldValue) { 971 return; 972 } 973 callback(value); 974 }, null); 975 return watcher; 976} 977 978/** 979 * Watch a calc function and callback if the calc value changes. 980 * @param {Vm} vm - Vm object. 981 * @param {Function} calc - Watch the calc, and returns a value by calc.call(). 982 * @param {Function} callback - Callback callback Function. 983 * @return {*} Watcher value. 984 */ 985export function watch(vm: Vm, calc: Function, callback: Function): any { 986 const watcher = new Watcher(vm, calc, function(value, oldValue) { 987 if (typeof value !== 'object' && value === oldValue) { 988 return; 989 } 990 callback(value); 991 }, null); 992 return watcher.value; 993} 994 995/** 996 * Apply style to an element. 997 * @param {Vm} vm - Vm object. 998 * @param {Element} el - Element object. 999 */ 1000function applyStyle(vm: Vm, el: Element): void { 1001 const css = vm._css || {}; 1002 const allStyle = el.style; 1003 setAnimation(allStyle, css); 1004} 1005 1006/** 1007 * Check if it is an Array. 1008 * @param {*} params - Any value. 1009 * @return {boolean} Return true if it is an array. Otherwise return false. 1010 */ 1011function isArray(params: any): params is Array<string> { 1012 return Array.isArray(params); 1013} 1014 1015function splitItems(valueStr: string): void { 1016 let i: number; 1017 let item: string = ''; 1018 let startQuote: boolean = false; 1019 const itemList: string[] = []; 1020 const len = valueStr.length; 1021 for (i = 0; i < len; i++) { 1022 if (!startQuote) { 1023 if (valueStr[i] === '"') { 1024 const itemLength = item.length; 1025 if (itemLength > 0) { 1026 const itemListLength = itemList.length; 1027 itemList[itemListLength] = item; 1028 } 1029 item = '"'; 1030 startQuote = true; 1031 continue; 1032 } else { 1033 item = item + valueStr[i]; 1034 if (i === len - 1) { 1035 const itemListLength = itemList.length; 1036 itemList[itemListLength] = item; 1037 } 1038 continue; 1039 } 1040 } else { 1041 if (valueStr[i] === '"') { 1042 item = item + valueStr[i]; 1043 startQuote = false; 1044 const itemListLength = itemList.length; 1045 itemList[itemListLength] = item; 1046 item = ''; 1047 continue; 1048 } else { 1049 item = item + valueStr[i]; 1050 continue; 1051 } 1052 } 1053 } 1054 doSplitItem(itemList); 1055} 1056 1057function doSplitItem(itemList: string[]): void { 1058 let i: number; 1059 const itemListLength = itemList.length; 1060 for (i = 0; i < itemListLength; i++) { 1061 let item = itemList[i].trim(); 1062 if (item.indexOf('"') === 0) { 1063 item = item.replace('"', ''); 1064 item = item.replace('"', ''); 1065 const contentObject: ContentObject = { 1066 value: item, 1067 contentType: ContentType.CONTENT_STRING 1068 }; 1069 const finallyItemsLength = finallyItems.length; 1070 finallyItems[finallyItemsLength] = contentObject; 1071 } else { 1072 splitItem(item.trim()); 1073 if (finallyItems.length > 0) { 1074 continue; 1075 } else { 1076 return; 1077 } 1078 } 1079 } 1080} 1081 1082function splitItem(item: string): void{ 1083 if (item.length === 0) { 1084 return; 1085 } 1086 const finallyItemsLength = finallyItems.length; 1087 if (item.indexOf('open-quote') === 0) { 1088 const subItem = item.substr(0, 10); 1089 const contentObject: ContentObject = { 1090 value: subItem, 1091 contentType: ContentType.CONTENT_OPEN_QUOTE 1092 }; 1093 finallyItems[finallyItemsLength] = contentObject; 1094 splitItem(item.substr(10).trim()); 1095 } else if (item.indexOf('close-quote') === 0) { 1096 const subItem = item.substr(0, 11); 1097 const contentObject: ContentObject = { 1098 value: subItem, 1099 contentType: ContentType.CONTENT_CLOSE_QUOTE 1100 }; 1101 finallyItems[finallyItemsLength] = contentObject; 1102 splitItem(item.substr(11).trim()); 1103 } else if (item.indexOf('attr') === 0) { 1104 const fromIndex = item.indexOf('('); 1105 const toIndex = item.indexOf(')'); 1106 const subLen = toIndex - fromIndex - 1; 1107 const subItem = item.substr(fromIndex + 1, subLen).trim(); 1108 const contentObject: ContentObject = { 1109 value: subItem, 1110 contentType: ContentType.CONTENT_ATTR 1111 }; 1112 finallyItems[finallyItemsLength] = contentObject; 1113 splitItem(item.substr(toIndex + 1).trim()); 1114 } else if (item.indexOf('counter') === 0) { 1115 const toIndex = item.indexOf(')'); 1116 const subItem = item.substr(0, toIndex + 1).trim(); 1117 const contentObject: ContentObject = { 1118 value: subItem, 1119 contentType: ContentType.CONTENT_COUNTER 1120 }; 1121 finallyItems[finallyItemsLength] = contentObject; 1122 splitItem(item.substr(toIndex + 1).trim()); 1123 } else { 1124 finallyItems = []; 1125 } 1126} 1127 1128function setContent(vm: Vm, el: Element, key: string): string { 1129 const itemLength = finallyItems.length; 1130 let contentValue = ''; 1131 let newValue = ''; 1132 if (itemLength > 0) { 1133 let i: number; 1134 for (i = 0; i < itemLength; i++) { 1135 const contentType = finallyItems[i].contentType; 1136 switch (contentType) { 1137 case ContentType.CONTENT_STRING: 1138 contentValue = contentValue + getContentString(finallyItems[i].value); 1139 break; 1140 case ContentType.CONTENT_OPEN_QUOTE: 1141 contentValue = contentValue + getContentOpenQuote(el, key); 1142 break; 1143 case ContentType.CONTENT_CLOSE_QUOTE: 1144 contentValue = contentValue + getContentCloseQuote(el, key); 1145 break; 1146 case ContentType.CONTENT_ATTR: 1147 contentValue = contentValue + getContentAttr(el, finallyItems[i].value); 1148 break; 1149 case ContentType.CONTENT_COUNTER: 1150 contentValue = contentValue + getCounter(vm, el, finallyItems[i].value); 1151 break; 1152 } 1153 } 1154 const oldValue = el.attr['value']; 1155 if (key === 'content::before') { 1156 newValue = contentValue + oldValue; 1157 } else if (key === 'content::after') { 1158 newValue = oldValue + contentValue; 1159 } 1160 } 1161 return newValue; 1162} 1163 1164function getContentString(value: string): string { 1165 let contentValue = value.replace('\"', ''); 1166 contentValue = contentValue.replace('\"', ''); 1167 return contentValue; 1168} 1169 1170function getContentOpenQuote(el: Element, key: string): string { 1171 let contentValue = ''; 1172 if (el.isOpen) { 1173 contentValue = '\''; 1174 } else { 1175 contentValue = '\"'; 1176 el.isOpen = true; 1177 } 1178 if (key === 'content::before') { 1179 el.hasBefore = true; 1180 } 1181 return contentValue; 1182} 1183 1184function getContentCloseQuote(el: Element, key: string): string { 1185 let contentValue = ''; 1186 if (el.isOpen) { 1187 contentValue = '\"'; 1188 } else { 1189 contentValue = ''; 1190 } 1191 if (el.isOpen && key === 'content::after') { 1192 el.hasAfter = true; 1193 } 1194 return contentValue; 1195} 1196 1197function getContentAttr(el: Element, value: string): string { 1198 let contentValue = el.attr[value]; 1199 if (contentValue === undefined) { 1200 contentValue = ''; 1201 } 1202 return contentValue; 1203} 1204 1205function getCounter(vm: Vm, el: Element, key: string): string { 1206 const fromIndex = key.indexOf('('); 1207 const toIndex = key.indexOf(')'); 1208 const counterName = key.substr(fromIndex + 1, toIndex - fromIndex - 1); 1209 if (vm._counterMapping.has(counterName)) { 1210 return vm.$getCounterMapping(counterName) + ''; 1211 } 1212 return ''; 1213} 1214 1215function updateTagAndIdStyle(el: Element, key: string, value: string): string { 1216 let newValue = ''; 1217 let contentValue = ''; 1218 let oldValue = el.attr['value']; 1219 if (key === 'content::before') { 1220 if (value === 'open-quote' || value === 'close-quote') { 1221 if (value === 'open-quote' && key === 'content::before') { 1222 contentValue = '\"'; 1223 if (el.hasBefore) { 1224 oldValue = oldValue.substr(1, oldValue.length); 1225 } 1226 newValue = contentValue + oldValue; 1227 oldValue = newValue; 1228 } else if (el.hasBefore && value === 'close-quote' && key === 'content::before') { 1229 el.hasBefore = false; 1230 oldValue = oldValue.substr(1, oldValue.length); 1231 newValue = oldValue; 1232 } 1233 } 1234 } else if (key === 'content::after') { 1235 if (value === 'open-quote' && key === 'content::after') { 1236 contentValue = '\"'; 1237 if (el.hasBefore) { 1238 contentValue = '\''; 1239 } 1240 if (el.hasAfter) { 1241 oldValue = oldValue.substr(0, oldValue.length - 1); 1242 } 1243 newValue = oldValue + contentValue; 1244 } else if (value === 'close-quote' && key === 'content::after' && el.hasBefore) { 1245 contentValue = '\"'; 1246 if (el.hasAfter) { 1247 oldValue = oldValue.substr(0, oldValue.length - 1); 1248 } 1249 newValue = oldValue + contentValue; 1250 } 1251 } 1252 if (value === 'no-open-quote' || value === 'no-close-quote') { 1253 newValue = oldValue; 1254 if (key === 'content::before' && value === 'no-open-quote') { 1255 if (el.hasBefore) { 1256 newValue = oldValue.substr(1); 1257 } 1258 } 1259 if (key === 'content::after') { 1260 if (el.hasAfter) { 1261 newValue = oldValue.substr(0, oldValue.length - 1); 1262 } 1263 } 1264 } 1265 return newValue; 1266} 1267 1268export function setAttributeStyle(vm: Vm, el: Element): void { 1269 const css = vm._css; 1270 if (css) { 1271 const keys = Object.keys(css); 1272 if (keys !== undefined || keys !== null) { 1273 const i = keys.length; 1274 let j: number = 0; 1275 for (j; j < i; j++) { 1276 let cssKey = keys[j].trim(); 1277 if (cssKey.indexOf('[') === 0) { 1278 cssKey = cssKey.substr(1, cssKey.length - 2); 1279 const equalIndex = cssKey.indexOf('='); 1280 if (equalIndex !== -1) { 1281 const attrId = cssKey.substr(0, equalIndex).trim(); 1282 let attrValue = cssKey.substr(equalIndex + 1).trim(); 1283 if (attrValue.indexOf('\"') !== -1) { 1284 attrValue = attrValue.replace('"', '').trim(); 1285 attrValue = attrValue.replace('"', '').trim(); 1286 } 1287 const elValue = el.attr[attrId]; 1288 if (elValue !== undefined && elValue === attrValue) { 1289 const newKey = keys[j]; 1290 bindDir(vm, el, 'attrStyle', css[newKey]); 1291 } 1292 } 1293 } 1294 } 1295 } 1296 } 1297} 1298 1299function setCounterValue(vm: Vm, key: string, value: string): void { 1300 if (key === 'counterReset') { 1301 vm.$setCounterMapping(value, 0); 1302 } 1303 if (key === 'counterIncrement') { 1304 if (vm._counterMapping.has(value)) { 1305 let counter = vm.$getCounterMapping(value); 1306 vm.$setCounterMapping(value, ++counter); 1307 } else { 1308 vm.$setCounterMapping(value, 1); 1309 } 1310 } 1311} 1312 1313