• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19/*
20 * 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