• 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
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