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