All files / src/renderer createTemplate.ts

100% Statements 62/62
100% Branches 28/28
100% Functions 5/5
100% Lines 60/60

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176      24x         24x     24x   24x   24x 24x 24x 24x                       24x   67x         67x   67x   67x                 67x   67x   67x   6x           61x   61x     61x     61x   61x   141x   141x   141x   75x   6x   6x     75x   75x   75x   10x       65x   9x     65x         66x   10x   56x   19x     66x   66x         61x   41x     61x   61x   61x   61x               202x   202x     124x     202x   202x   71x     202x         75x   75x   572x   75x     497x     75x  
/**
 * Marks the end of a string and the beginning of a value
 */
export const beginMarker = "_$bm_";
 
/**
 * Marks the end of a value and the beginning of a string
 */
export const endMarker = "_$em_";
 
// Referenced to avoid converting the value of the attribute for templates
export const attributeMarkerPrefix = "_$attr:";
 
export const eventMarkerPrefix = "_$evt:";
 
export enum NodePatcherRuleTypes {
    PATCH_NODE = 1, // Patches either a single node or a collection of nodes
    PATCH_ATTRIBUTE,
    PATCH_EVENT
}
 
export interface TemplateData {
 
    templateString: string;
 
    template: HTMLTemplateElement;
 
    keyIndex: number;
}
 
export default function createTemplate(strings: TemplateStringsArray): TemplateData {
 
    const template = document.createElement('template');
 
    const {
        templateString,
        keyIndex
    } = createTemplateString(strings);
 
    template.innerHTML = templateString;
 
    return {
        templateString,
        template,
        keyIndex
    };
}
 
function createTemplateString(strings: TemplateStringsArray): { templateString: string, keyIndex: number } {
 
    let keyIndex: number = undefined; // The index of the key to get the value from the values array
 
    const parts: string[] = [];
 
    if (strings.length === 1) { // No values ... literal only
 
        return {
            templateString: strings[0],
            keyIndex
        };
    }
 
    const length = strings.length - 1; // Exclude the last one
 
    let s: string = undefined;
 
    // Flag to indicate state whether we are dealing with a value for an attribute or an event
    let beginAttributeOrEvent: boolean = false;
 
    // Flag when a begin marker has been emitted but we transitioned to an attribute so we need to emit the end marker as well
    let beginMarkerSet: boolean = false;
 
    for (let i = 0; i < length; ++i) {
 
        s = strings[i];
 
        s = trimNode(s);
 
        if (s.endsWith('=')) { // It is an attribute or an event
 
            if (beginMarkerSet === true) {
 
                parts.push(`<!--${endMarker}-->`); // End the previous node
 
                beginMarkerSet = false;
            }
 
            const name = getAttributeName(s);
 
            beginAttributeOrEvent = true;
 
            if (name[0] === 'o' && name[1] === 'n') { // It is an event handler
 
                parts.push(`${s}"${eventMarkerPrefix}${name}"`);
            }
            else { // Not an event
 
                if (name === 'key') {
 
                    keyIndex = i;
                }
 
                parts.push(`${s}"${attributeMarkerPrefix}${name}"`);
            }
        }
        else { // It is the beginning of a text or element
 
            if (beginAttributeOrEvent === true) {
 
                beginAttributeOrEvent = false; // Clear the attribute flag
            }
            else if (i > 0) { // Ignore the first string
 
                parts.push(`<!--${endMarker}-->`); // End the previous node
            }
 
            parts.push(`${s}<!--${beginMarker}-->`);
 
            beginMarkerSet = true; // Flag that we set the begin marker
        }
    }
 
    // Add the ending string
    if (beginAttributeOrEvent === false) { // End of a text or element
 
        parts.push(`<!--${endMarker}-->`);
    }
 
    s = strings[length];
 
    s = trimNode(s);
 
    parts.push(s);
 
    return {
        templateString: parts.join(''),
        keyIndex
    };
}
 
function trimNode(s: string): string {
 
    const trimmedStart = s.trimStart();
 
    if (trimmedStart.startsWith('<') ||
        trimmedStart === '') {
 
        s = trimmedStart; // Remove any empty text nodes at the start so there are not extra unnecessary children
    }
 
    const trimmedEnd = s.trimEnd();
 
    if (trimmedEnd.endsWith('>')) {
 
        s = trimmedEnd;
    }
 
    return s;
}
 
function getAttributeName(s: string): string {
 
    let b: string[] = [];
 
    for (let i = s.lastIndexOf('=') - 1; i >= 0; --i) {
 
        if (s[i] === ' ') { // Finished with the name of the attribute
 
            break;
        }
 
        b = [s[i], ...b]; // Prepend
    }
 
    return b.join('');
}