/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * 2021.01.08 - Rewrite some functions and remove some redundant judgments to fit framework. * Copyright (c) 2021 Huawei Device Co., Ltd. */ /** * @fileOverview * Directive Parser */ import { typof, camelize, Log } from '../../utils/index'; import Watcher from '../reactivity/watcher'; import { setDescendantStyle } from './selector'; import { getDefaultPropValue } from '../util/props'; import { matchMediaQueryCondition } from '../extend/mediaquery/mediaQuery'; import { TemplateInterface, FragBlockInterface, AttrInterface } from './compiler'; import Vm from './index'; import Element from '../../vdom/Element'; const SETTERS = { attr: 'setAttr', style: 'setStyle', data: 'setData', event: 'addEvent', idStyle: 'setIdStyle', tagStyle: 'setTagStyle' }; /** * Bind id, attr, classnames, style, events to an element. * @param {Vm} vm - Vm object. * @param {Element} el - Element to be bind. * @param {TemplateInterface} template - Structure of the component. * @param {Element | FragBlockInterface} parentElement - Parent element of current element. */ export function bindElement(vm: Vm, el: Element, template: TemplateInterface, parentElement: Element | FragBlockInterface): void { // Set descendant style. setDescendantStyle( vm.selector, { id: template.id, class: template.classList, tag: template.type }, parentElement, vm, function(style: {[key: string]: any}) { if (!style) { return; } const css = vm.css || {}; setAnimation(style, css); setFontFace(style, css); setStyle(vm, el, style); } ); // inherit 'show' attribute of custom component if (el.isCustomComponent) { const value = vm['show']; if (template.attr && value !== undefined) { if (typeof value === 'function') { // vm['show'] is assigned to this.show in initPropsToData() template.attr['show'] = function() { return this.show; }; } else { template.attr['show'] = value; } } } setId(vm, el, template.id, vm); setAttr(vm, el, template.attr); setStyle(vm, el, template.style); setIdStyle(vm, el, template.id); setClass(vm, el, template.classList); setTagStyle(vm, el, template.type); applyStyle(vm, el); bindEvents(vm, el, template.events); bindEvents(vm, el, template.onBubbleEvents, ''); bindEvents(vm, el, template.onCaptureEvents, 'capture'); bindEvents(vm, el, template.catchBubbleEvents, 'catchbubble'); bindEvents(vm, el, template.catchCaptureEvents, 'catchcapture'); if (!vm.isHide && !vm.init) { el.addEvent('hide'); vm.isHide = true; } } /** *
Bind all props to sub vm and bind all style, events to the root element
*of the sub vm if it doesn't have a replaced multi-node fragment.
* @param {Vm} vm - Vm object. * @param {Vm} subVm - Sub vm. * @param {TemplateInterface} template - Structure of the component. * @param {Object} repeatItem - Item object. */ export function bindSubVm(vm: Vm, rawSubVm: Vm, rawTemplate: TemplateInterface, repeatItem: object): void { const subVm: any = rawSubVm || {}; const template: any = rawTemplate || {}; const options: any = subVm.vmOptions || {}; let props = options.props; if (isArray(props) || !props) { if (isArray(props)) { props = props.reduce((result, value) => { result[value] = true; return result; }, {}); } mergeProps(repeatItem, props, vm, subVm); mergeProps(template.attr, props, vm, subVm); } else { const attrData = template.attr || {}; const repeatData = repeatItem || {}; Object.keys(props).forEach(key => { const prop = props[key]; let value = attrData[key] || repeatData[key] || undefined; if (value === undefined) { value = getDefaultPropValue(vm, prop); } mergePropsObject(key, value, vm, subVm); }); } } /** * Merge class and styles from vm to sub vm. * @param {Vm} vm - Vm object. * @param {Vm} subVm - Sub vm. * @param {TemplateInterface} template - Structure of the component. * @param {Element | FragBlockInterface} target - The target of element. */ export function bindSubVmAfterInitialized(vm: Vm, subVm: Vm, template: TemplateInterface, target: Element | FragBlockInterface): void { mergeClassStyle(template.classList, vm, subVm); mergeStyle(template.style, vm, subVm); if (target.children) { target.children[target.children.length - 1]._vm = subVm; } else { target.vm = subVm; } bindSubEvent(vm, subVm, template); } /** * Bind custom event from vm to sub vm for calling parent method. * @param {Vm} vm - Vm object. * @param {Vm} subVm - Sub vm. * @param {TemplateInterface} template - Structure of the component. */ function bindSubEvent(vm: Vm, subVm: Vm, template: TemplateInterface): void { if (template.events) { for (const type in template.events) { subVm.$on(camelize(type), function() { const args = []; for (const i in arguments) { args[i] = arguments[i]; } if (vm[template.events[type]] && typeof vm[template.events[type]] === 'function') { vm[template.events[type]].apply(vm, args); } }); } } } /** * Merge props from vm to sub vm. * @param {string} key - Get vm object by key. * @param {*} value - Default Value. * @param {Vm} vm - Vm object. * @param {Vm} subVm - Sub vm. * @return {*} Sub vm object. */ function mergePropsObject(key: string, value: any, vm: Vm, subVm: Vm): any { subVm.props.push(key); if (typeof value === 'function') { const returnValue = watch(vm, value, function(v) { subVm[key] = v; }); // 'show' attribute will be inherited by elements in custom component if (key === 'show') { subVm[key] = value; } else { subVm[key] = returnValue; } } else { const realValue = value && value.__hasDefault ? value.__isDefaultValue : value; subVm[key] = realValue; } return subVm[key]; } /** * Bind props from vm to sub vm and watch their updates. * @param {Object} target - Target object. * @param {*} props - Vm props. * @param {Vm} vm - Vm object. * @param {Vm} subVm - Sub vm. */ function mergeProps(target: object, props: any, vm: Vm, subVm: Vm): void { if (!target) { return; } for (const key in target) { if (!props || props[key] || key === 'show') { subVm.props.push(key); const value = target[key]; if (typeof value === 'function') { const returnValue = watch(vm, value, function(v) { subVm[key] = v; }); // 'show' attribute will be inherited by elements in custom component if (key === 'show') { subVm[key] = value; } else { subVm[key] = returnValue; } } else { subVm[key] = value; } } } } /** * Bind style from vm to sub vm and watch their updates. * @param {Object} target - Target object. * @param {Vm} vm - Vm object. * @param {Vm} subVm - Sub vm. */ function mergeStyle(target: { [key: string]: any }, vm: Vm, subVm: Vm): void { for (const key in target) { const value = target[key]; if (typeof value === 'function') { const returnValue = watch(vm, value, function(v) { if (subVm.rootEl) { subVm.rootEl.setStyle(key, v); } }); subVm.rootEl.setStyle(key, returnValue); } else { if (subVm.rootEl) { subVm.rootEl.setStyle(key, value); } } } } /** * Bind class and style from vm to sub vm and watch their updates. * @param {Object} target - Target object. * @param {Vm} vm - Vm object. * @param {Vm} subVm - Sub vm. */ function mergeClassStyle(target: Function | string[], vm: Vm, subVm: Vm): void { const css = vm.css || {}; if (!subVm.rootEl) { return; } /** * Class name. * @constant {string} */ const CLASS_NAME = '@originalRootEl'; css['.' + CLASS_NAME] = subVm.rootEl.classStyle; function addClassName(list, name) { if (typof(list) === 'array') { list.unshift(name); } } if (typeof target === 'function') { const value = watch(vm, target, v => { addClassName(v, CLASS_NAME); setClassStyle(subVm.rootEl, css, v); }); addClassName(value, CLASS_NAME); setClassStyle(subVm.rootEl, css, value); } else if (target !== undefined) { addClassName(target, CLASS_NAME); setClassStyle(subVm.rootEl, css, target); } } /** * Bind id to an element. Note: Each id is unique in a whole vm. * @param {Vm} vm - Vm object. * @param {Element} el - Element object. * @param {Function | string} id - Unique vm id. * @param {Vm} target - Target vm. */ export function setId(vm: Vm, el: Element, id: Function | string, target: Vm): void { const map = Object.create(null); Object.defineProperties(map, { vm: { value: target, writable: false, configurable: false }, el: { get: () => el || target.rootEl, configurable: false } }); if (typeof id === 'function') { const handler = id; const newId = handler.call(vm); if (newId || newId === 0) { setElementId(el, newId); vm.ids[newId] = map; } watch(vm, handler, (newId) => { if (newId) { setElementId(el, newId); vm.ids[newId] = map; } }); } else if (id && typeof id === 'string') { setElementId(el, id); vm.ids[id] = map; } } /** * Set id to Element. * @param {Element} el - Element object. * @param {string} id - Element id. */ function setElementId(el: Element, id: string): void { if (el) { el.id = id; } } /** * Bind attr to an element. * @param {Vm} vm - Vm object. * @param {Element} el - Element. * @param {AttrInterface} attr - Attr to bind. */ function setAttr(vm: Vm, el: Element, attr: PartialSet a series of members as a kind of an element.
*for example: style, attr, ...
*if the value is a function then bind the data changes.
* @param {Vm} vm - Vm object. * @param {Element} el - ELement component. * @param {string} name - Method name. * @param {Object} data - Data that needed. */ function bindDir(vm: Vm, el: Element, name: string, data: object): void { if (!data) { return; } const keys = Object.keys(data); let i = keys.length; if (!i) { return; } const methodName = SETTERS[name]; const method = el[methodName]; const isSetStyle = methodName === 'setStyle'; while (i--) { const key = keys[i]; const value = data[key]; if (key === 'ref') { vm.$refs[value] = el; } const isSetFont = isSetStyle && key === 'fontFamily'; const setValue = function(value) { if (isSetFont) { value = filterFontFamily(vm, value); } method.call(el, key, value); }; if (typeof value === 'function') { bindKey(vm, el, setValue, value); } else { setValue(value); } } } /** * Bind data changes to a certain key to a name series in an element. * @param {Vm} vm - Vm object. * @param {Element} el - ELement component. * @param {Function} setValue - Set value. * @param {Function} calc - Watch the calc and return a value by calc.call(). */ function bindKey(vm: Vm, el: Element, setValue: Function, calc: Function): void { // Watch the calc, and returns a value by calc.call(). const watcher = newWatch(vm, calc, (value) => { function handler() { setValue(value); } const differ = vm && vm.app && vm.app.differ; if (differ) { differ.append('element', el.ref, handler); } else { handler(); } }); el.watchers.push(watcher); setValue(watcher.value); } /** * FontFamily Filter. * @param {Vm} vm - Vm object. * @param {string} fontFamilyName - FontFamily name. * @return {*} FontFamily Filter. */ export function filterFontFamily(vm: Vm, fontFamilyName: string): any[] { const css = vm.css || {}; return _getFontFamily(css, fontFamilyName); } /** * Watch the calc. * @param {Vm} vm - Vm object. * @param {Function} calc - Watch the calc, and returns a value by calc.call(). * @param {Function} callback - Callback callback Function. * @return {Watcher} New watcher for rhe calc value. */ export function newWatch(vm: Vm, calc: Function, callback: Function): Watcher { const watcher = new Watcher(vm, calc, function(value, oldValue) { if (typeof value !== 'object' && value === oldValue) { return; } callback(value); }, null); return watcher; } /** * Watch a calc function and callback if the calc value changes. * @param {Vm} vm - Vm object. * @param {Function} calc - Watch the calc, and returns a value by calc.call(). * @param {Function} callback - Callback callback Function. * @return {*} Watcher value. */ export function watch(vm: Vm, calc: Function, callback: Function): any { const watcher = new Watcher(vm, calc, function(value, oldValue) { if (typeof value !== 'object' && value === oldValue) { return; } callback(value); }, null); return watcher.value; } /** * Apply style to an element. * @param {Vm} vm - Vm object. * @param {Element} el - Element object. */ function applyStyle(vm: Vm, el: Element): void { const css = vm.css || {}; const allStyle = el.style; setAnimation(allStyle, css); } /** * Check if it is an Array. * @param {*} params - Any value. * @return {boolean} Return true if it is an array. Otherwise return false. */ function isArray(params: any): params is Array