/** * Copyright 2018 The Incremental DOM Authors. All Rights Reserved. * * Licensed 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. */ import { assert, assertCloseMatchesOpenTag, assertInAttributes, assertInPatch, assertNotInAttributes, assertNotInSkip, setInAttributes } from "./assertions"; import { updateAttribute } from "./attributes"; import { getArgsBuilder, getAttrsBuilder, close, open, text as coreText, currentElement } from "./core"; import { DEBUG } from "./global"; import { getData, NodeData } from "./node_data"; import { Key, NameOrCtorDef, Statics } from "./types"; import { createMap, truncateArray } from "./util"; import { calculateDiff } from "./diff"; /** * The offset in the virtual element declaration where the attributes are * specified. */ const ATTRIBUTES_OFFSET = 3; /** * Used to keep track of the previous values when a 2-way diff is necessary. * This object is reused. * TODO(sparhamI) Scope this to a patch so you can call patch from an attribute * update. */ const prevAttrsMap = createMap(); /** * @param element The Element to diff the attrs for. * @param data The NodeData associated with the Element. */ function diffAttrs(element: Element, data: NodeData) { const attrsBuilder = getAttrsBuilder(); const prevAttrsArr = data.getAttrsArr(attrsBuilder.length); calculateDiff(prevAttrsArr, attrsBuilder, element, updateAttribute); truncateArray(attrsBuilder, 0); } /** * Applies the statics. When importing an Element, any existing attributes that * match a static are converted into a static attribute. * @param node The Element to apply statics for. * @param data The NodeData associated with the Element. * @param statics The statics array. */ function diffStatics(node: Element, data: NodeData, statics: Statics) { if (data.staticsApplied) { return; } data.staticsApplied = true; if (!statics || !statics.length) { return; } if (data.hasEmptyAttrsArr()) { for (let i = 0; i < statics.length; i += 2) { updateAttribute(node, statics[i] as string, statics[i + 1]); } return; } for (let i = 0; i < statics.length; i += 2) { prevAttrsMap[statics[i] as string] = i + 1; } const attrsArr = data.getAttrsArr(0); let j = 0; for (let i = 0; i < attrsArr.length; i += 2) { const name = attrsArr[i]; const value = attrsArr[i + 1]; const staticsIndex = prevAttrsMap[name]; if (staticsIndex) { // For any attrs that are static and have the same value, make sure we do // not set them again. if (statics[staticsIndex] === value) { delete prevAttrsMap[name]; } continue; } // For any attrs that are dynamic, move them up to the right place. attrsArr[j] = name; attrsArr[j + 1] = value; j += 2; } // Anything after `j` was either moved up already or static. truncateArray(attrsArr, j); for (const name in prevAttrsMap) { updateAttribute(node, name, statics[prevAttrsMap[name]]); delete prevAttrsMap[name]; } } /** * Declares a virtual Element at the current location in the document. This * corresponds to an opening tag and a elementClose tag is required. This is * like elementOpen, but the attributes are defined using the attr function * rather than being passed as arguments. Must be folllowed by 0 or more calls * to attr, then a call to elementOpenEnd. * @param nameOrCtor The Element's tag or constructor. * @param key The key used to identify this element. This can be an * empty string, but performance may be better if a unique value is used * when iterating over an array of items. * @param statics An array of attribute name/value pairs of the static * attributes for the Element. Attributes will only be set once when the * Element is created. */ function elementOpenStart( nameOrCtor: NameOrCtorDef, key?: Key, statics?: Statics ) { const argsBuilder = getArgsBuilder(); if (DEBUG) { assertNotInAttributes("elementOpenStart"); setInAttributes(true); } argsBuilder[0] = nameOrCtor; argsBuilder[1] = key; argsBuilder[2] = statics; } /** * Allows you to define a key after an elementOpenStart. This is useful in * templates that define key after an element has been opened ie * `
`. * @param key The key to use for the next call. */ function key(key: string) { const argsBuilder = getArgsBuilder(); if (DEBUG) { assertInAttributes("key"); assert(argsBuilder); } argsBuilder[1] = key; } /** * Buffers an attribute, which will get applied during the next call to * `elementOpen`, `elementOpenEnd` or `applyAttrs`. * @param name The of the attribute to buffer. * @param value The value of the attribute to buffer. */ function attr(name: string, value: any) { const attrsBuilder = getAttrsBuilder(); if (DEBUG) { assertInPatch("attr"); } attrsBuilder.push(name); attrsBuilder.push(value); } /** * Closes an open tag started with elementOpenStart. * @return The corresponding Element. */ function elementOpenEnd(): HTMLElement { const argsBuilder = getArgsBuilder(); if (DEBUG) { assertInAttributes("elementOpenEnd"); setInAttributes(false); } const node = open(