• 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  event: 'addEvent',
57  idStyle: 'setIdStyle',
58  tagStyle: 'setTagStyle'
59};
60
61/**
62 * Bind id, attr, classnames, style, events to an element.
63 * @param {Vm} vm - Vm object.
64 * @param {Element} el - Element to be bind.
65 * @param {TemplateInterface} template - Structure of the component.
66 * @param {Element | FragBlockInterface} parentElement - Parent element of current element.
67 */
68export function bindElement(vm: Vm, el: Element, template: TemplateInterface, parentElement: Element | FragBlockInterface): void {
69  // Set descendant style.
70  setDescendantStyle(
71    vm.selector,
72    {
73      id: template.id,
74      class: template.classList,
75      tag: template.type
76    },
77    parentElement,
78    vm,
79    function(style: {[key: string]: any}) {
80      if (!style) {
81        return;
82      }
83      const css = vm.css || {};
84      setAnimation(style, css);
85      setFontFace(style, css);
86      setStyle(vm, el, style);
87    }
88  );
89
90  // inherit 'show' attribute of custom component
91  if (el.isCustomComponent) {
92    const value = vm['show'];
93    if (template.attr && value !== undefined) {
94      if (typeof value === 'function') {
95        // vm['show'] is assigned to this.show in initPropsToData()
96        template.attr['show'] = function() {
97          return this.show;
98        };
99      } else {
100        template.attr['show'] = value;
101      }
102    }
103  }
104
105  setId(vm, el, template.id, vm);
106  setAttr(vm, el, template.attr);
107  setStyle(vm, el, template.style);
108  setIdStyle(vm, el, template.id);
109  setClass(vm, el, template.classList);
110  setTagStyle(vm, el, template.type);
111  applyStyle(vm, el);
112
113  bindEvents(vm, el, template.events);
114  bindEvents(vm, el, template.onBubbleEvents, '');
115  bindEvents(vm, el, template.onCaptureEvents, 'capture');
116  bindEvents(vm, el, template.catchBubbleEvents, 'catchbubble');
117  bindEvents(vm, el, template.catchCaptureEvents, 'catchcapture');
118
119  if (!vm.isHide && !vm.init) {
120    el.addEvent('hide');
121    vm.isHide = true;
122  }
123}
124
125/**
126 * <p>Bind all props to sub vm and bind all style, events to the root element</p>
127 * <p>of the sub vm if it doesn't have a replaced multi-node fragment.</p>
128 * @param {Vm} vm - Vm object.
129 * @param {Vm} subVm - Sub vm.
130 * @param {TemplateInterface} template - Structure of the component.
131 * @param {Object} repeatItem - Item object.
132 */
133export function bindSubVm(vm: Vm, rawSubVm: Vm, rawTemplate: TemplateInterface, repeatItem: object): void {
134  const subVm: any = rawSubVm || {};
135  const template: any = rawTemplate || {};
136  const options: any = subVm.vmOptions || {};
137
138  let props = options.props;
139  if (isArray(props) || !props) {
140    if (isArray(props)) {
141      props = props.reduce((result, value) => {
142        result[value] = true;
143        return result;
144      }, {});
145    }
146    mergeProps(repeatItem, props, vm, subVm);
147    mergeProps(template.attr, props, vm, subVm);
148  } else {
149    const attrData = template.attr || {};
150    const repeatData = repeatItem || {};
151    Object.keys(props).forEach(key => {
152      const prop = props[key];
153      let value = attrData[key] || repeatData[key] || undefined;
154      if (value === undefined) {
155        value = getDefaultPropValue(vm, prop);
156      }
157      mergePropsObject(key, value, vm, subVm);
158    });
159  }
160}
161
162/**
163 * Merge class and styles from vm to sub vm.
164 * @param {Vm} vm - Vm object.
165 * @param {Vm} subVm - Sub vm.
166 * @param {TemplateInterface} template - Structure of the component.
167 * @param {Element | FragBlockInterface} target - The target of element.
168 */
169export function bindSubVmAfterInitialized(vm: Vm, subVm: Vm, template: TemplateInterface, target: Element | FragBlockInterface): void {
170  mergeClassStyle(template.classList, vm, subVm);
171  mergeStyle(template.style, vm, subVm);
172  if (target.children) {
173    target.children[target.children.length - 1]._vm = subVm;
174  } else {
175    target.vm = subVm;
176  }
177  bindSubEvent(vm, subVm, template);
178}
179
180/**
181 * Bind custom event from vm to sub vm for calling parent method.
182 * @param {Vm} vm - Vm object.
183 * @param {Vm} subVm - Sub vm.
184 * @param {TemplateInterface} template - Structure of the component.
185 */
186function bindSubEvent(vm: Vm, subVm: Vm, template: TemplateInterface): void {
187  if (template.events) {
188    for (const type in template.events) {
189      subVm.$on(camelize(type), function() {
190        const args = [];
191        for (const i in arguments) {
192          args[i] = arguments[i];
193        }
194        if (vm[template.events[type]]
195            && typeof vm[template.events[type]] === 'function') {
196          vm[template.events[type]].apply(vm, args);
197        }
198      });
199    }
200  }
201}
202
203/**
204 * Merge props from vm to sub vm.
205 * @param {string} key - Get vm object by key.
206 * @param {*} value - Default Value.
207 * @param {Vm} vm - Vm object.
208 * @param {Vm} subVm - Sub vm.
209 * @return {*} Sub vm object.
210 */
211function mergePropsObject(key: string, value: any, vm: Vm, subVm: Vm): any {
212  subVm.props.push(key);
213  if (typeof value === 'function') {
214    const returnValue = watch(vm, value, function(v) {
215      subVm[key] = v;
216    });
217    // 'show' attribute will be inherited by elements in custom component
218    if (key === 'show') {
219      subVm[key] = value;
220    } else {
221      subVm[key] = returnValue;
222    }
223  } else {
224    const realValue =
225        value && value.__hasDefault ? value.__isDefaultValue : value;
226    subVm[key] = realValue;
227  }
228  return subVm[key];
229}
230
231/**
232 * Bind props from vm to sub vm and watch their updates.
233 * @param {Object} target - Target object.
234 * @param {*} props - Vm props.
235 * @param {Vm} vm - Vm object.
236 * @param {Vm} subVm - Sub vm.
237 */
238function mergeProps(target: object, props: any, vm: Vm, subVm: Vm): void {
239  if (!target) {
240    return;
241  }
242  for (const key in target) {
243    if (!props || props[key] || key === 'show') {
244      subVm.props.push(key);
245      const value = target[key];
246      if (typeof value === 'function') {
247        const returnValue = watch(vm, value, function(v) {
248          subVm[key] = v;
249        });
250        // 'show' attribute will be inherited by elements in custom component
251        if (key === 'show') {
252          subVm[key] = value;
253        } else {
254          subVm[key] = returnValue;
255        }
256      } else {
257        subVm[key] = value;
258      }
259    }
260  }
261}
262
263/**
264 * Bind style from vm to sub vm and watch their updates.
265 * @param {Object} target - Target object.
266 * @param {Vm} vm - Vm object.
267 * @param {Vm} subVm - Sub vm.
268 */
269function mergeStyle(target: { [key: string]: any }, vm: Vm, subVm: Vm): void {
270  for (const key in target) {
271    const value = target[key];
272    if (typeof value === 'function') {
273      const returnValue = watch(vm, value, function(v) {
274        if (subVm.rootEl) {
275          subVm.rootEl.setStyle(key, v);
276        }
277      });
278      subVm.rootEl.setStyle(key, returnValue);
279    } else {
280      if (subVm.rootEl) {
281        subVm.rootEl.setStyle(key, value);
282      }
283    }
284  }
285}
286
287/**
288 * Bind class and style from vm to sub vm and watch their updates.
289 * @param {Object} target - Target object.
290 * @param {Vm} vm - Vm object.
291 * @param {Vm} subVm - Sub vm.
292 */
293function mergeClassStyle(target: Function | string[], vm: Vm, subVm: Vm): void {
294  const css = vm.css || {};
295  if (!subVm.rootEl) {
296    return;
297  }
298
299  /**
300   * Class name.
301   * @constant {string}
302   */
303  const CLASS_NAME = '@originalRootEl';
304  css['.' + CLASS_NAME] = subVm.rootEl.classStyle;
305
306  function addClassName(list, name) {
307    if (typof(list) === 'array') {
308      list.unshift(name);
309    }
310  }
311
312  if (typeof target === 'function') {
313    const value = watch(vm, target, v => {
314      addClassName(v, CLASS_NAME);
315      setClassStyle(subVm.rootEl, css, v);
316    });
317    addClassName(value, CLASS_NAME);
318    setClassStyle(subVm.rootEl, css, value);
319  } else if (target !== undefined) {
320    addClassName(target, CLASS_NAME);
321    setClassStyle(subVm.rootEl, css, target);
322  }
323}
324
325/**
326 * Bind id to an element. Note: Each id is unique in a whole vm.
327 * @param {Vm} vm - Vm object.
328 * @param {Element} el - Element object.
329 * @param {Function | string} id - Unique vm id.
330 * @param {Vm} target - Target vm.
331 */
332export function setId(vm: Vm, el: Element, id: Function | string, target: Vm): void {
333  const map = Object.create(null);
334  Object.defineProperties(map, {
335    vm: {
336      value: target,
337      writable: false,
338      configurable: false
339    },
340    el: {
341      get: () => el || target.rootEl,
342      configurable: false
343    }
344  });
345  if (typeof id === 'function') {
346    const handler = id;
347    const newId = handler.call(vm);
348    if (newId || newId === 0) {
349      setElementId(el, newId);
350      vm.ids[newId] = map;
351    }
352    watch(vm, handler, (newId) => {
353      if (newId) {
354        setElementId(el, newId);
355        vm.ids[newId] = map;
356      }
357    });
358  } else if (id && typeof id === 'string') {
359    setElementId(el, id);
360    vm.ids[id] = map;
361  }
362}
363
364/**
365 * Set id to Element.
366 * @param {Element} el - Element object.
367 * @param {string} id - Element id.
368 */
369function setElementId(el: Element, id: string): void {
370  if (el) {
371    el.id = id;
372  }
373}
374
375/**
376 * Bind attr to an element.
377 * @param {Vm} vm - Vm object.
378 * @param {Element} el - Element.
379 * @param {AttrInterface} attr - Attr to bind.
380 */
381function setAttr(vm: Vm, el: Element, attr: Partial<AttrInterface>): void {
382  if (attr && attr.data) {
383    // address data independently
384    bindDir(vm, el, 'data', attr.data);
385  }
386  bindDir(vm, el, 'attr', attr);
387}
388
389/**
390 * Set font family and get font resource.
391 * @param {Object} css - Css style.
392 * @param {string | string[]} fontFamilyNames - Font family names.
393 * @return {*} Font resource.
394 */
395function _getFontFamily(css: any, fontFamilyNames: string | string[]): any[] {
396  let results = [];
397  const familyMap = css['@FONT-FACE'];
398  if (typeof fontFamilyNames === 'string') {
399    fontFamilyNames.split(',').forEach(fontFamilyName => {
400      fontFamilyName = fontFamilyName.trim();
401      let find = false;
402      if (familyMap && Array.isArray(familyMap)) {
403        let len = familyMap.length;
404        while (len) {
405          if (
406            familyMap[len - 1].fontFamily &&
407              familyMap[len - 1].fontFamily === fontFamilyName
408          ) {
409            results.push(familyMap[len - 1]);
410            find = true;
411          }
412          len--;
413        }
414      } else if (familyMap && typeof familyMap === 'object') {
415        const definedFontFamily = familyMap[fontFamilyName];
416        if (definedFontFamily && definedFontFamily.src) {
417          if (Array.isArray(definedFontFamily.src)) {
418            definedFontFamily.src = definedFontFamily.src.map(item => `url("${item}")`).join(',');
419          }
420          results.push(definedFontFamily);
421          find = true;
422        }
423      }
424      if (!find) {
425        results.push({ 'fontFamily': fontFamilyName });
426      }
427    });
428  } else if (Array.isArray(fontFamilyNames)) {
429    results = fontFamilyNames;
430  } else if (fontFamilyNames) {
431    Log.warn(`GetFontFamily Array error, unexpected fontFamilyNames type [${typeof fontFamilyNames}].`);
432  }
433  return results;
434}
435
436/**
437 * Select class style.
438 * @param {Object} css - Css style.
439 * @param {Function | string[]} classList - List of class label.
440 * @param {number} index - Index of classList.
441 * @return {*} Select style.
442 */
443function selectClassStyle(css: object, classList: Function | string[], index: number, vm: Vm): any {
444  const key = '.' + classList[index];
445  return selectStyle(css, key, vm);
446}
447
448/**
449 * Select id style.
450 * @param {Object} css - Css style.
451 * @param {string} id - Id label.
452 * @param {Vm} vm - Vm object.
453 * @return {*} Select style.
454 */
455function selectIdStyle(css: object, id: string, vm: Vm): any {
456  const key = '#' + id;
457  return selectStyle(css, key, vm);
458}
459
460/**
461 * Replace style.
462 * @param {*} oStyle - Current style.
463 * @param {*} rStyle - New style.
464 */
465function replaceStyle(oStyle: any, rStyle: any): void {
466  if (!rStyle || rStyle.length <= 0) {
467    return;
468  }
469  Object.keys(rStyle).forEach(function(key) {
470    oStyle[key] = rStyle[key];
471  });
472}
473
474/**
475 * Select style for class label, id label.
476 * @param {Object} css - Css style.
477 * @param {string} key - Key index.
478 * @param {Vm} vm - Vm object.
479 * @return {*}
480 */
481function selectStyle(css: object, key: string, vm: Vm): any {
482  const style = css[key];
483  if (!vm) {
484    return style;
485  }
486  const mediaStatus = vm.mediaStatus;
487  if (!mediaStatus) {
488    return style;
489  }
490  const mqArr = css['@MEDIA'];
491  if (!mqArr) {
492    vm.init = true;
493    return style;
494  }
495  const classStyle = {};
496  if (style) {
497    Object.keys(style).forEach(function(key) {
498      classStyle[key] = style[key];
499    });
500  }
501  for (let i$1 = 0; i$1 < mqArr.length; i$1++) {
502    if (matchMediaQueryCondition(mqArr[i$1].condition, mediaStatus, false)) {
503      replaceStyle(classStyle, mqArr[i$1][key]);
504    }
505  }
506  return classStyle;
507}
508
509/**
510 * Set class style after SelectClassStyle.
511 * @param {Element} el - Element object.
512 * @param {Object} css - Css style.
513 * @param {string[]} classList - List of class label.
514 */
515function setClassStyle(el: Element, css: object, classList: string[], vm?: Vm): void {
516  const SPACE_REG: RegExp = /\s+/;
517  const newClassList: string[] = [];
518  if (Array.isArray(classList)) {
519    classList.forEach(v => {
520      if (typeof v === 'string' && SPACE_REG.test(v)) {
521        newClassList.push(...v.trim().split(SPACE_REG));
522      } else {
523        newClassList.push(v);
524      }
525    });
526  }
527  classList = newClassList;
528  const classStyle = {};
529  const length = classList.length;
530  if (length === 1) {
531    const style = selectClassStyle(css, classList, 0, vm);
532    if (style) {
533      Object.keys(style).forEach((key) => {
534        classStyle[key] = style[key];
535      });
536    }
537  } else {
538    const rets = [];
539    const keys = Object.keys(css || {});
540    for (let i = 0; i < length; i++) {
541      const clsKey = '.' + classList[i];
542      const style = selectStyle(css, clsKey, vm);
543      if (style) {
544        const order = clsKey === '.@originalRootEl' ? -1000 : keys.indexOf(clsKey);
545        rets.push({style: style, order: order});
546      }
547    }
548    if (rets.length === 1) {
549      const style = rets[0].style;
550      if (style) {
551        Object.keys(style).forEach((key) => {
552          classStyle[key] = style[key];
553        });
554      }
555    } else if (rets.length > 1) {
556      rets.sort(function(a, b) {
557        if (!a) {
558          return -1;
559        } else if (!b) {
560          return 1;
561        } else {
562          return a.order > b.order ? 1 : -1;
563        }
564      });
565      const retStyle = {};
566      rets.forEach(function(key) {
567        if (key && key.style) {
568          Object.assign(retStyle, key.style);
569        }
570      });
571      Object.keys(retStyle).forEach((key) => {
572        classStyle[key] = retStyle[key];
573      });
574    }
575  }
576
577  const keyframes = css['@KEYFRAMES'];
578  if (keyframes) {
579    /*
580     * Assign @KEYFRAMES's value.
581     */
582    const animationName = classStyle['animationName'];
583    if (animationName) {
584      classStyle['animationName'] = keyframes[animationName];
585      classStyle['animationName'].push({'animationName': animationName});
586    }
587    const transitionEnter = classStyle['transitionEnter'];
588    if (transitionEnter) {
589      classStyle['transitionEnter'] = keyframes[transitionEnter];
590    }
591    const transitionExit = classStyle['transitionExit'];
592    if (transitionExit) {
593      classStyle['transitionExit'] = keyframes[transitionExit];
594    }
595    const sharedTransitionName = classStyle['sharedTransitionName'];
596    if (sharedTransitionName) {
597      classStyle['sharedTransitionName'] = keyframes[sharedTransitionName];
598    }
599  }
600  const fontFace = classStyle['fontFamily'];
601  if (fontFace) {
602    const fontCompileList = _getFontFamily(css, fontFace);
603    classStyle['fontFamily'] = fontCompileList;
604  }
605  el.setClassStyle(classStyle);
606  el.classList = classList;
607}
608
609/**
610 * Bind classnames to an element
611 * @param {Vm} vm - Vm object.
612 * @param {Element} el - Element object.
613 * @param {Function | string[]} classList - List of class label.
614 */
615export function setClass(vm: Vm, el: Element, classList: Function | string[]): void {
616  if (typeof classList !== 'function' && !Array.isArray(classList)) {
617    return;
618  }
619  if (Array.isArray(classList) && !classList.length) {
620    el.setClassStyle({});
621    return;
622  }
623  const style = vm.css || {};
624  if (typeof classList === 'function') {
625    const value = watch(vm, classList, v => {
626      setClassStyle(el, style, v, vm);
627    });
628    setClassStyle(el, style, value, vm);
629  } else {
630    setClassStyle(el, style, classList, vm);
631  }
632}
633
634/**
635 * Support css selector by id and component.
636 * @param {Vm} vm - Vm object.
637 * @param {Element} el - ELement component.
638 * @param {Function | string} id - Id label.
639 */
640export function setIdStyle(vm: Vm, el: Element, id: Function | string): void {
641  if (id) {
642    const css = vm.css || {};
643    if (typeof id === 'function') {
644      const value = watch(vm, id, v => {
645        doSetStyle(vm, el, selectIdStyle(css, v, vm), css, 'idStyle');
646      });
647      doSetStyle(vm, el, selectIdStyle(css, value, vm), css, 'idStyle');
648    } else if (typeof id === 'string') {
649      doSetStyle(vm, el, selectIdStyle(css, id, vm), css, 'idStyle');
650    }
651  }
652}
653
654/**
655 * Set style.
656 * @param {Vm} vm - Vm object.
657 * @param {Element} el - ELement component.
658 * @param {*} style - Style to be Set.
659 * @param {*} css - Css style.
660 * @param {string} name - Bind by name.
661 */
662function doSetStyle(vm: Vm, el: Element, style: any, css: any, name: string): void {
663  if (!style) {
664    return;
665  }
666  const typeStyle = {};
667  Object.assign(typeStyle, style);
668  setAnimation(typeStyle, css);
669  setFontFace(typeStyle, css);
670  bindDir(vm, el, name, typeStyle);
671}
672
673/**
674 * Set FontFace.
675 * @param {*} style - Style.
676 * @param {*} css - Css style.
677 */
678function setFontFace(style: any, css: any): void {
679  const fontFace = style['fontFamily'];
680  if (fontFace) {
681    const fontCompileList = _getFontFamily(css, fontFace);
682    style['fontFamily'] = fontCompileList;
683  }
684}
685
686/**
687 * Set Animation
688 * @param {*} style - Style.
689 * @param {*} css - Css style.
690 */
691function setAnimation(style: any, css: any): void {
692  const animationName = style['animationName'];
693  const keyframes = css['@KEYFRAMES'];
694  if (animationName && keyframes) {
695    style['animationName'] = keyframes[animationName];
696    if (style['animationName']) {
697      style['animationName'].push({'animationName': animationName});
698    }
699  }
700}
701
702/**
703 * Set tag style.
704 * @param {Vm} vm - Vm object.
705 * @param {Element} el - ELement component.
706 * @param {string} tag - Tag.
707 */
708export function setTagStyle(vm: Vm, el: Element, tag: string): void {
709  const css = vm.css || {};
710  if (tag && typeof tag === 'string') {
711    doSetStyle(vm, el, selectStyle(css, tag, vm), css, 'tagStyle');
712  }
713}
714
715/**
716 * Bind style to an element.
717 * @param {Vm} vm - Vm object.
718 * @param {Element} el - ELement component.
719 * @param {*} style - Style.
720 */
721function setStyle(vm: Vm, el: Element, style: any): void {
722  bindDir(vm, el, 'style', style);
723}
724
725/**
726 * Add an event type and handler to an element and generate a dom update.
727 * @param {Vm} vm - Vm object.
728 * @param {Element} el - ELement component.
729 * @param {string} type - Type added to event.
730 * @param {Function} handler - Handle added to event.
731 */
732function setEvent(vm: Vm, el: Element, type: string, handler: Function): void {
733  el.addEvent(type, handler.bind(vm));
734}
735
736/**
737 * Add all events of an element.
738 * @param {Vm} vm - Vm object.
739 * @param {Element} el - ELement component.
740 * @param {Object} events - Events of an element.
741 */
742function bindEvents(vm: Vm, el: Element, events: object, eventType?: string): void {
743  if (!events) {
744    return;
745  }
746  const keys = Object.keys(events);
747  let i = keys.length;
748  while (i--) {
749    const key = keys[i];
750    let handler = events[key];
751    if (typeof handler === 'string') {
752      handler = vm[handler];
753      if (!handler || typeof handler !== 'function') {
754        Log.warn(`The event handler '${events[key]}' is undefined or is not function.`);
755        continue;
756      }
757    }
758    const eventName: string = eventType ? eventType + key : key;
759    setEvent(vm, el, eventName, handler);
760  }
761}
762
763/**
764 * <p>Set a series of members as a kind of an element.</p>
765 * <p>for example: style, attr, ...</p>
766 * <p>if the value is a function then bind the data changes.</p>
767 * @param {Vm} vm - Vm object.
768 * @param {Element} el - ELement component.
769 * @param {string} name - Method name.
770 * @param {Object} data - Data that needed.
771 */
772function bindDir(vm: Vm, el: Element, name: string, data: object): void {
773  if (!data) {
774    return;
775  }
776  const keys = Object.keys(data);
777  let i = keys.length;
778  if (!i) {
779    return;
780  }
781  const methodName = SETTERS[name];
782  const method = el[methodName];
783  const isSetStyle = methodName === 'setStyle';
784  while (i--) {
785    const key = keys[i];
786    const value = data[key];
787    if (key === 'ref') {
788      vm.$refs[value] = el;
789    }
790    const isSetFont = isSetStyle && key === 'fontFamily';
791    const setValue = function(value) {
792      if (isSetFont) {
793        value = filterFontFamily(vm, value);
794      }
795      method.call(el, key, value);
796    };
797    if (typeof value === 'function') {
798      bindKey(vm, el, setValue, value);
799    } else {
800      setValue(value);
801    }
802  }
803}
804
805/**
806 * Bind data changes to a certain key to a name series in an element.
807 * @param {Vm} vm - Vm object.
808 * @param {Element} el - ELement component.
809 * @param {Function} setValue - Set value.
810 * @param {Function} calc - Watch the calc and return a value by calc.call().
811 */
812function bindKey(vm: Vm, el: Element, setValue: Function, calc: Function): void {
813  // Watch the calc, and returns a value by calc.call().
814  const watcher = newWatch(vm, calc, (value) => {
815    function handler() {
816      setValue(value);
817    }
818    const differ = vm && vm.app && vm.app.differ;
819    if (differ) {
820      differ.append('element', el.ref, handler);
821    } else {
822      handler();
823    }
824  });
825  el.watchers.push(watcher);
826  setValue(watcher.value);
827}
828
829/**
830 * FontFamily Filter.
831 * @param {Vm} vm - Vm object.
832 * @param {string} fontFamilyName - FontFamily name.
833 * @return {*} FontFamily Filter.
834 */
835export function filterFontFamily(vm: Vm, fontFamilyName: string): any[] {
836  const css = vm.css || {};
837  return _getFontFamily(css, fontFamilyName);
838}
839
840/**
841 * Watch the calc.
842 * @param {Vm} vm - Vm object.
843 * @param {Function} calc - Watch the calc, and returns a value by calc.call().
844 * @param {Function} callback - Callback callback Function.
845 * @return {Watcher} New watcher for rhe calc value.
846 */
847export function newWatch(vm: Vm, calc: Function, callback: Function): Watcher {
848  const watcher = new Watcher(vm, calc, function(value, oldValue) {
849    if (typeof value !== 'object' && value === oldValue) {
850      return;
851    }
852    callback(value);
853  }, null);
854  return watcher;
855}
856
857/**
858 * Watch a calc function and callback if the calc value changes.
859 * @param {Vm} vm - Vm object.
860 * @param {Function} calc - Watch the calc, and returns a value by calc.call().
861 * @param {Function} callback - Callback callback Function.
862 * @return {*} Watcher value.
863 */
864export function watch(vm: Vm, calc: Function, callback: Function): any {
865  const watcher = new Watcher(vm, calc, function(value, oldValue) {
866    if (typeof value !== 'object' && value === oldValue) {
867      return;
868    }
869    callback(value);
870  }, null);
871  return watcher.value;
872}
873
874/**
875 * Apply style to an element.
876 * @param {Vm} vm - Vm object.
877 * @param {Element} el - Element object.
878 */
879function applyStyle(vm: Vm, el: Element): void {
880  const css = vm.css || {};
881  const allStyle = el.style;
882  setAnimation(allStyle, css);
883}
884
885/**
886 * Check if it is an Array.
887 * @param {*} params - Any value.
888 * @return {boolean} Return true if it is an array. Otherwise return false.
889 */
890function isArray(params: any): params is Array<string> {
891  return Array.isArray(params);
892}
893