1153 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1153 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						||
 | 
						||
if ('serviceWorker' in navigator) {
 | 
						||
    console.log("Registering service worker");
 | 
						||
    navigator.serviceWorker.register('/sw.js');
 | 
						||
}
 | 
						||
 | 
						||
class CsvxmlValidator {
 | 
						||
 | 
						||
    fieldList;  // {}{}
 | 
						||
    toValidate; // []{}
 | 
						||
    errors;     // {}array
 | 
						||
 | 
						||
    constructor(fieldList, csvRaw) {
 | 
						||
 | 
						||
        this.errors = {
 | 
						||
            parsing: [],
 | 
						||
            mandatoryTags: [],
 | 
						||
            duplicateInvNos: [],
 | 
						||
            dependentColumns: [],
 | 
						||
            controlledLists: [],
 | 
						||
            mainImageResource: [],
 | 
						||
        };
 | 
						||
 | 
						||
        this.fieldList = Object.freeze(fieldList);
 | 
						||
 | 
						||
        const data = Papa.parse(csvRaw.trim(), {
 | 
						||
            delimiter: ";", // auto-detect
 | 
						||
            escapeChar: '"',
 | 
						||
            skipEmptyLines: true,
 | 
						||
            header: true,
 | 
						||
        });
 | 
						||
 | 
						||
        if (data.errors.length !== 0) {
 | 
						||
            console.log("Errors encountered: ");
 | 
						||
            console.error(data.errors);
 | 
						||
 | 
						||
            let msg = '';
 | 
						||
            for (let err of data.errors) {
 | 
						||
                msg += err.type + ': ' + err.message + "\n";
 | 
						||
            }
 | 
						||
            window.alert(msg);
 | 
						||
        }
 | 
						||
 | 
						||
        let toValidate = data.data;
 | 
						||
 | 
						||
        this.toValidate = toValidate;
 | 
						||
 | 
						||
        if (toValidate.length === 0) {
 | 
						||
            alert("Error: No lines of content identified");
 | 
						||
        }
 | 
						||
 | 
						||
        this.validate();
 | 
						||
    }
 | 
						||
 | 
						||
    validate() {
 | 
						||
 | 
						||
        this.validateMandatoryTagsPresent();
 | 
						||
        this.validateInvalidTagsPresent();
 | 
						||
        this.checkDuplicateInvNos();
 | 
						||
        this.checkDependentColumns();
 | 
						||
        this.checkControlledLists();
 | 
						||
        // this.checkMainImageResource();
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    validateMandatoryTagsPresent() {
 | 
						||
 | 
						||
        let mandatoryFields = [];
 | 
						||
        for (let fieldName in this.fieldList) {
 | 
						||
            if (this.fieldList[fieldName].required === true) {
 | 
						||
                mandatoryFields.push(fieldName);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        console.log(this.toValidate);
 | 
						||
        let lineCounter = 1;
 | 
						||
        for (let line of this.toValidate) {
 | 
						||
            for (let mandatoryField of mandatoryFields) {
 | 
						||
                if (line[mandatoryField] === undefined || line[mandatoryField] === null || line[mandatoryField] === '') {
 | 
						||
                    this.errors.mandatoryTags.push("Missing or empty mandatory tag " + mandatoryField + " on line " + lineCounter);
 | 
						||
                }
 | 
						||
            }
 | 
						||
            lineCounter++;
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    validateInvalidTagsPresent() {
 | 
						||
 | 
						||
        const headers = Object.keys(this.toValidate[0]);
 | 
						||
 | 
						||
        for (let header of headers) {
 | 
						||
            if (this.fieldList[header] === undefined) {
 | 
						||
                this.errors.parsing.push("Invalid column \"" + header + "\" detected! Please remove this column or use the appropriate name!");
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    checkDuplicateInvNos() {
 | 
						||
 | 
						||
        let invNoEncountered = [];
 | 
						||
        let lineCounter = 1;
 | 
						||
        for (let line of this.toValidate) {
 | 
						||
            if (invNoEncountered.includes(line.inventory_number)) {
 | 
						||
                this.errors.duplicateInvNos.push("Duplicate inventory number " + line.inventory_number + " on line " + lineCounter);
 | 
						||
            }
 | 
						||
            invNoEncountered.push(line.inventory_number);
 | 
						||
            lineCounter++;
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    checkDependentColumns() {
 | 
						||
 | 
						||
        const headers = Object.keys(this.toValidate[0]);
 | 
						||
 | 
						||
        for (let header of headers) {
 | 
						||
            if (this.fieldList[header] === undefined || this.fieldList[header].dependsOn === undefined || this.fieldList[header].dependsOn === null) continue;
 | 
						||
 | 
						||
            let dependencies = this.fieldList[header].dependsOn;
 | 
						||
            for (let dep of dependencies) {
 | 
						||
                if (headers.includes(dep) === false) {
 | 
						||
                    console.error("Dependency issue at column " + header + ": Corresponding column " + dep + " is missing");
 | 
						||
                    console.log(headers);
 | 
						||
                    this.errors.dependentColumns.push("Dependency issue at column " + header + ": Corresponding column " + dep + " is missing");
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        let lineCounter = 1;
 | 
						||
        for (let line of this.toValidate) {
 | 
						||
            for (let fieldName in line) {
 | 
						||
 | 
						||
                if (line[fieldName] === '') continue;
 | 
						||
                if (this.fieldList[fieldName] === undefined) continue; // This may be the case if invalid fields are present
 | 
						||
 | 
						||
                const dependencies = this.fieldList[fieldName].dependsOn;
 | 
						||
                if (dependencies === undefined) continue;
 | 
						||
 | 
						||
                for (let dependency of dependencies) {
 | 
						||
                    if (line[dependency] === '') {
 | 
						||
                        console.error("Dependency issue at column " + fieldName + ": Corresponding column " + dependency + " is missing [on line " + lineCounter + "]");
 | 
						||
                        console.log(line);
 | 
						||
                        this.errors.dependentColumns.push("Dependency issue at column " + fieldName + " (current value: " + line[fieldName] + "): Corresponding column " + dependency + " is empty [on line " + lineCounter + "]");
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            }
 | 
						||
            lineCounter++;
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    checkControlledLists() {
 | 
						||
 | 
						||
        let lineCounter = 1;
 | 
						||
        for (let line of this.toValidate) {
 | 
						||
            for (let fieldName in line) {
 | 
						||
 | 
						||
                if (this.fieldList[fieldName] === undefined) {
 | 
						||
                    console.log("Undefined but requested field " + fieldName);
 | 
						||
                    continue;
 | 
						||
                }
 | 
						||
 | 
						||
                const allowedValues = this.fieldList[fieldName].allowedValues;
 | 
						||
 | 
						||
                // No error if the field doesn't have a controlled list
 | 
						||
                if (allowedValues === undefined || allowedValues === null) continue;
 | 
						||
                // No error if the line's content is in the list
 | 
						||
                if (Object.values(allowedValues).length === 0 || Object.values(allowedValues).includes(line[fieldName])) {
 | 
						||
                    continue;
 | 
						||
                }
 | 
						||
 | 
						||
                if (line[fieldName] === '') continue;
 | 
						||
                this.errors.controlledLists.push("Disallowed value used for column " + fieldName + " at line " + lineCounter + " (Allowed values are: " + Object.values(allowedValues).join(", ") + "; current value is " + line[fieldName] + ")");
 | 
						||
            }
 | 
						||
            lineCounter++;
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    checkMainImageResource() {
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    isValid() {
 | 
						||
 | 
						||
        for (let errorClass in this.errors) {
 | 
						||
            if (this.errors[errorClass].length !== 0) return false;
 | 
						||
        }
 | 
						||
 | 
						||
        return true;
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Generates XML for the parsed lines
 | 
						||
     */
 | 
						||
    generateXml() {
 | 
						||
 | 
						||
        let output = [];
 | 
						||
 | 
						||
        let xmlDoc = document.implementation.createDocument(null, "record");
 | 
						||
        for (let line of this.toValidate) {
 | 
						||
            let root = xmlDoc.createElement("record");
 | 
						||
            for (let fieldName in line) {
 | 
						||
                const elem = xmlDoc.createElement(fieldName);
 | 
						||
                elem.textContent = line[fieldName];
 | 
						||
                root.appendChild(elem);
 | 
						||
            }
 | 
						||
            output.push(root)
 | 
						||
        }
 | 
						||
 | 
						||
        return output;
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
class CsvxmlTooltip {
 | 
						||
 | 
						||
    /**
 | 
						||
     * Function for setting the alignment of an element.
 | 
						||
     *
 | 
						||
     * @param {Event}      e    Event triggering the execution of this function.
 | 
						||
     * @param {DOMElement} elem Dom element to position.
 | 
						||
     *
 | 
						||
     * @return {void}
 | 
						||
     */
 | 
						||
    static getDirection(e, elem) {
 | 
						||
 | 
						||
        if (window.innerHeight < e.clientY + elem.clientHeight) {
 | 
						||
            elem.style.top    = "";
 | 
						||
            elem.style.bottom = (window.innerHeight - e.clientY) + "px";
 | 
						||
        }
 | 
						||
        else {
 | 
						||
            elem.style.bottom  = "";
 | 
						||
            elem.style.top     = (e.clientY + 4) + "px";
 | 
						||
        }
 | 
						||
        if (window.innerWidth < e.clientX + elem.clientWidth) {
 | 
						||
            elem.style.left  = "";
 | 
						||
            elem.style.right = (window.innerWidth - e.clientX) + "px";
 | 
						||
        } else {
 | 
						||
            elem.style.right = "";
 | 
						||
            elem.style.left  = (e.clientX + 3) + "px";
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    static positionMobile(newMain) {
 | 
						||
 | 
						||
        if (window.matchMedia && window.matchMedia('(max-width:75em)').matches) {
 | 
						||
 | 
						||
            newMain.style.left = "";
 | 
						||
            newMain.style.right = "";
 | 
						||
            newMain.style.top = "";
 | 
						||
            newMain.style.bottom = "";
 | 
						||
 | 
						||
            if (newMain.classList.contains("atBottom") === false) {
 | 
						||
                newMain.classList.add("atBottom");
 | 
						||
            }
 | 
						||
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    static triggerMouseMove(e) {
 | 
						||
        const newMain = document.getElementById("newToolTipMain");
 | 
						||
        if (newMain === undefined || newMain === null) return;
 | 
						||
        CsvxmlTooltip.getDirection(e, newMain);
 | 
						||
        CsvxmlTooltip.positionMobile(newMain);
 | 
						||
    }
 | 
						||
 | 
						||
    static triggerMouseOut(e) {
 | 
						||
 | 
						||
        const newMain = document.getElementById("newToolTipMain");
 | 
						||
        if (newMain !== undefined && newMain !== null) {
 | 
						||
            newMain.classList.remove("visible");
 | 
						||
            document.body.removeChild(newMain);
 | 
						||
        }
 | 
						||
        e.target.removeEventListener('mouseout', CsvxmlTooltip.triggerMouseOut);
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    static bindTooltipToElement(elem, tooltipTitle, tooltipContent) {
 | 
						||
 | 
						||
        elem.addEventListener('mouseover', function(e) {
 | 
						||
 | 
						||
            let newMain = document.getElementById("newToolTipMain");
 | 
						||
            if (newMain !== null) return;
 | 
						||
            newMain = document.createElement("div");
 | 
						||
            newMain.classList.add("newToolTip");
 | 
						||
            newMain.id = "newToolTipMain";
 | 
						||
 | 
						||
            // Insert contents loaded.
 | 
						||
 | 
						||
            newMain.setAttribute("data-title", tooltipTitle);
 | 
						||
 | 
						||
            newMain.appendChild(tooltipContent);
 | 
						||
 | 
						||
            document.body.appendChild(newMain);
 | 
						||
            newMain.classList.add("visible");
 | 
						||
            CsvxmlTooltip.getDirection(e, newMain);
 | 
						||
            CsvxmlTooltip.positionMobile(newMain);
 | 
						||
 | 
						||
            newMain.addEventListener("mouseout", function(e) {
 | 
						||
                const newMain = document.getElementById("newToolTipMain");
 | 
						||
                if (newMain !== undefined && newMain !== null) {
 | 
						||
                    newMain.classList.remove("visible");
 | 
						||
                    document.body.removeChild(newMain);
 | 
						||
                }
 | 
						||
            });
 | 
						||
 | 
						||
            elem.addEventListener("mouseout", CsvxmlTooltip.triggerMouseOut);
 | 
						||
 | 
						||
        }, {passive: true});
 | 
						||
 | 
						||
        elem.addEventListener("mousemove", CsvxmlTooltip.triggerMouseMove, {passive: true});
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
class CsvxmlDialogue {
 | 
						||
 | 
						||
    static closeDialogue(e) {
 | 
						||
 | 
						||
        if (e !== undefined) {
 | 
						||
            e.preventDefault();
 | 
						||
            e.stopPropagation();
 | 
						||
        }
 | 
						||
 | 
						||
        let dialogueArea = document.getElementById("dialogueArea");
 | 
						||
        if (dialogueArea !== null && dialogueArea !== false) {
 | 
						||
            while (dialogueArea.firstChild) {
 | 
						||
                dialogueArea.removeChild(dialogueArea.firstChild);
 | 
						||
            }
 | 
						||
            dialogueArea.parentElement.removeChild(dialogueArea);
 | 
						||
 | 
						||
            document.removeEventListener('keydown', CsvxmlDialogue.closeDialogueByEscape, false);
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    static closeDialogueByEscape(e) {
 | 
						||
 | 
						||
        if (e.keyCode === 27) { // 27 = Esc
 | 
						||
 | 
						||
            CsvxmlDialogue.closeDialogue(e);
 | 
						||
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Function for drawing a dialogue and attaching it to the body elem.
 | 
						||
     *
 | 
						||
     * @param {DOMElement} contents Contents.
 | 
						||
     */
 | 
						||
    static drawDialogue(contents) {
 | 
						||
        let dialogueArea = document.createElement("div");
 | 
						||
        dialogueArea.id = "dialogueArea";
 | 
						||
 | 
						||
        let dialogue = document.createElement("div");
 | 
						||
        dialogue.id = "dialogue";
 | 
						||
 | 
						||
        dialogue.appendChild(contents);
 | 
						||
        dialogueArea.appendChild(dialogue);
 | 
						||
 | 
						||
        document.body.appendChild(dialogueArea);
 | 
						||
 | 
						||
        document.addEventListener('keydown', CsvxmlDialogue.closeDialogueByEscape);
 | 
						||
 | 
						||
        return dialogue;
 | 
						||
    }
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
class CsvxmlPage {
 | 
						||
 | 
						||
    fieldList;
 | 
						||
    fieldListFlat;
 | 
						||
    tls;
 | 
						||
    domHelpWrapper;
 | 
						||
    domUploaderWrapper;
 | 
						||
    domMainWrapper;
 | 
						||
    selectedFields;
 | 
						||
 | 
						||
    csvBySelectionButton;
 | 
						||
    unsetSelectionButton;
 | 
						||
 | 
						||
    constructor(fieldList, tls) {
 | 
						||
        this.fieldList = Object.freeze(fieldList);
 | 
						||
 | 
						||
        let list = {};
 | 
						||
        for (let sectionName in fieldList) {
 | 
						||
            list = Object.assign(list, fieldList[sectionName]);
 | 
						||
        }
 | 
						||
        this.fieldListFlat = Object.freeze(list);
 | 
						||
 | 
						||
        this.tls = Object.freeze(tls);
 | 
						||
 | 
						||
        let domHelpWrapper = document.createElement("div");
 | 
						||
        domHelpWrapper.id = "helpSection";
 | 
						||
        this.domHelpWrapper = domHelpWrapper;
 | 
						||
 | 
						||
        let domUploaderWrapper = document.createElement("div");
 | 
						||
        domUploaderWrapper.id = "uploader";
 | 
						||
        domUploaderWrapper.classList.add("uploader");
 | 
						||
        this.domUploaderWrapper = domUploaderWrapper;
 | 
						||
 | 
						||
        let domMainWrapper = document.createElement("main");
 | 
						||
        this.domMainWrapper = domMainWrapper;
 | 
						||
 | 
						||
        this.selectedFields = [];
 | 
						||
    }
 | 
						||
 | 
						||
    generateCsv(selectedFields = []) {
 | 
						||
 | 
						||
        let line1 = [];
 | 
						||
        let line2 = [];
 | 
						||
        let line3 = [];
 | 
						||
 | 
						||
        for (let fieldName in this.fieldListFlat) {
 | 
						||
            console.log(fieldName);
 | 
						||
            console.log(selectedFields);
 | 
						||
 | 
						||
            if (selectedFields.length !== 0 && selectedFields.includes(fieldName) === false) continue;
 | 
						||
            const field = this.fieldListFlat[fieldName];
 | 
						||
 | 
						||
            line1.push(fieldName);
 | 
						||
            line2.push(field.name_human_readable);
 | 
						||
 | 
						||
            if (field.allowedValues !== undefined) {
 | 
						||
                // Join for object
 | 
						||
                let values = [];
 | 
						||
                for (let key in field.allowedValues) {
 | 
						||
                    values.push(field.allowedValues[key]);
 | 
						||
                }
 | 
						||
                line3.push(values.join(","));
 | 
						||
            }
 | 
						||
            else line3.push("");
 | 
						||
 | 
						||
        }
 | 
						||
 | 
						||
        const csvLine1 = '"' + line1.join('";"') + '"';
 | 
						||
        const csvLine2 = '"' + line2.join('";"') + '"';
 | 
						||
        const csvLine3 = '"' + line3.join('";"') + '"';
 | 
						||
 | 
						||
        const toStore = csvLine1 + "\n" + csvLine2 + "\n" + csvLine3;
 | 
						||
 | 
						||
        // Download
 | 
						||
        const triggerLink = document.createElement('a');
 | 
						||
        triggerLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(toStore));
 | 
						||
        triggerLink.setAttribute('download', "csvxml_museum-digital_template.csv");
 | 
						||
 | 
						||
        triggerLink.style.display = 'none';
 | 
						||
 | 
						||
        document.body.appendChild(triggerLink);
 | 
						||
        triggerLink.click();
 | 
						||
        document.body.removeChild(triggerLink);
 | 
						||
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    zipUploadToXml(validator) {
 | 
						||
 | 
						||
        // Wrap zipping in function to postload it
 | 
						||
        function runZipping() {
 | 
						||
 | 
						||
            let zip = new JSZip();
 | 
						||
 | 
						||
            let xmlFiles = validator.generateXml();
 | 
						||
            const serializer = new XMLSerializer();
 | 
						||
            let lineCounter = 0;
 | 
						||
            for (let xml of xmlFiles) {
 | 
						||
                zip.file(lineCounter + ".xml", serializer.serializeToString(xml));
 | 
						||
                lineCounter++;
 | 
						||
            }
 | 
						||
 | 
						||
            zip.generateAsync({type:"blob"})
 | 
						||
            .then(function(content) {
 | 
						||
                const triggerLink = document.createElement('a');
 | 
						||
                triggerLink.href = window.URL.createObjectURL(content);
 | 
						||
                triggerLink.setAttribute('download', "csvxml.zip");
 | 
						||
 | 
						||
                triggerLink.style.display = 'none';
 | 
						||
 | 
						||
                document.body.appendChild(triggerLink);
 | 
						||
                triggerLink.click();
 | 
						||
                document.body.removeChild(triggerLink);
 | 
						||
            });
 | 
						||
 | 
						||
        }
 | 
						||
 | 
						||
        if (typeof JSZip === "undefined") {
 | 
						||
 | 
						||
            const loadScript = document.createElement("script");
 | 
						||
            loadScript.setAttribute("src", "assets/js/jszip/dist/jszip.min.js");
 | 
						||
            loadScript.addEventListener('load', function() {
 | 
						||
                // console.log("Post-loaded OpenLayers");
 | 
						||
                runZipping();
 | 
						||
            }, {passive: true, once: true});
 | 
						||
 | 
						||
            document.body.appendChild(loadScript);
 | 
						||
        }
 | 
						||
        else {
 | 
						||
            runZipping();
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    generateDialogueCloseButton() {
 | 
						||
 | 
						||
        const cancelB = document.createElement("a");
 | 
						||
        cancelB.classList.add("icons");
 | 
						||
        cancelB.classList.add("iconsClose");
 | 
						||
        cancelB.classList.add("dialogueCloseX");
 | 
						||
        cancelB.id = "dialogueClose";
 | 
						||
        cancelB.textContent = "X";
 | 
						||
        cancelB.title = "Close";
 | 
						||
        cancelB.href = "#" + location.href;
 | 
						||
        cancelB.addEventListener('click', CsvxmlDialogue.closeDialogue);
 | 
						||
        return cancelB;
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    listValidationErrors(validator) {
 | 
						||
 | 
						||
        console.log("Listing validation errors");
 | 
						||
 | 
						||
        const dialogueContent = document.createElement("div");
 | 
						||
 | 
						||
        const headline = document.createElement("h3");
 | 
						||
        headline.textContent = this.tls.validation_errors;
 | 
						||
        headline.appendChild(this.generateDialogueCloseButton());
 | 
						||
        dialogueContent.appendChild(headline);
 | 
						||
 | 
						||
        const domErrorsSection = document.createElement("div");
 | 
						||
 | 
						||
        for (let errorType in validator.errors) {
 | 
						||
 | 
						||
            if (validator.errors[errorType].length === 0) continue;
 | 
						||
 | 
						||
            const ulHl = document.createElement("h4");
 | 
						||
            ulHl.textContent = this.tls['errors_' + errorType] + " (" + validator.errors[errorType].length + ")";
 | 
						||
            ulHl.style.cursor = "pointer";
 | 
						||
            domErrorsSection.appendChild(ulHl);
 | 
						||
 | 
						||
            const ul = document.createElement("ul");
 | 
						||
 | 
						||
            for (let error of validator.errors[errorType]) {
 | 
						||
                const li = document.createElement("li");
 | 
						||
                li.textContent = error;
 | 
						||
                ul.appendChild(li);
 | 
						||
            }
 | 
						||
 | 
						||
            ulHl.addEventListener('click', function() {
 | 
						||
                ul.classList.toggle("minimized");
 | 
						||
            });
 | 
						||
 | 
						||
            domErrorsSection.appendChild(ul);
 | 
						||
 | 
						||
        }
 | 
						||
 | 
						||
        dialogueContent.appendChild(domErrorsSection);
 | 
						||
 | 
						||
        const domDlSection = document.createElement("div");
 | 
						||
        const domDlA = document.createElement("span");
 | 
						||
        domDlA.textContent = this.tls.download;
 | 
						||
        domDlA.classList.add("buttonLike");
 | 
						||
        let app = this;
 | 
						||
        domDlA.addEventListener('click', function() {
 | 
						||
            app.zipUploadToXml(validator);
 | 
						||
        });
 | 
						||
        domDlSection.appendChild(domDlA);
 | 
						||
        dialogueContent.appendChild(domDlSection);
 | 
						||
 | 
						||
        dialogue = CsvxmlDialogue.drawDialogue(dialogueContent);
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    uploadFileForValidation(file) {
 | 
						||
 | 
						||
        const reader = new FileReader();
 | 
						||
        let utf8 = true;
 | 
						||
        reader.readAsText(file, utf8 ? 'UTF-8' : 'CP1251');
 | 
						||
 | 
						||
        let app = this;
 | 
						||
 | 
						||
        document.body.classList.add("loading");
 | 
						||
 | 
						||
        reader.onload = function() {
 | 
						||
 | 
						||
            function handleValidation() {
 | 
						||
 | 
						||
                (async function() {
 | 
						||
                    const result = reader.result;
 | 
						||
                    if (utf8 && result.includes('<27>')) {
 | 
						||
                        window.alert('The file encoding appears to not be UTF-8!\n\nTry exporting the file using the format "CSV (UTF-8)" if you use MS Excel or set the encoding to UTF-8 when exporting through LibreOffice!');
 | 
						||
                    }
 | 
						||
                })();
 | 
						||
 | 
						||
                // On loading success, check if the upload is valid JSON
 | 
						||
                console.log("Read file");
 | 
						||
                // Validate the file
 | 
						||
                let validator = new CsvxmlValidator(app.fieldListFlat, reader.result);
 | 
						||
                document.body.classList.remove("loading");
 | 
						||
                if (validator.isValid() === true) {
 | 
						||
                    alert("Document is valid. Press ok to download.");
 | 
						||
                    app.zipUploadToXml(validator);
 | 
						||
                }
 | 
						||
                else {
 | 
						||
                    console.log("Identified invalid upload document");
 | 
						||
                    app.listValidationErrors(validator);
 | 
						||
                }
 | 
						||
 | 
						||
            }
 | 
						||
 | 
						||
            console.log("Postload papaparse");
 | 
						||
            if (typeof Papa === "undefined") {
 | 
						||
 | 
						||
                const loadScript = document.createElement("script");
 | 
						||
                loadScript.setAttribute("src", "assets/js/papaparse/papaparse.min.js");
 | 
						||
                loadScript.addEventListener('load', function() {
 | 
						||
                    // console.log("Post-loaded OpenLayers");
 | 
						||
                    handleValidation();
 | 
						||
                }, {passive: true, once: true});
 | 
						||
                document.body.appendChild(loadScript);
 | 
						||
 | 
						||
            }
 | 
						||
            else {
 | 
						||
                handleValidation();
 | 
						||
            }
 | 
						||
 | 
						||
        };
 | 
						||
        reader.onerror = function() {
 | 
						||
            alert(reader.error);
 | 
						||
        };
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    renderGenHeader() {
 | 
						||
 | 
						||
        const header = document.createElement("header");
 | 
						||
        header.id = "mainHeader";
 | 
						||
 | 
						||
        const logoArea = document.createElement("a");
 | 
						||
        logoArea.id = "logoArea";
 | 
						||
        logoArea.href = "https://www.museum-digital.org/";
 | 
						||
 | 
						||
        const logoImg = document.createElement("img");
 | 
						||
        logoImg.src = "assets/img/mdlogo-code-128px.png";
 | 
						||
        logoImg.alt = "Logo of museum-digital";
 | 
						||
        logoArea.appendChild(logoImg);
 | 
						||
 | 
						||
        const h2 = document.createElement("h2");
 | 
						||
        h2.textContent = "museum-digital";
 | 
						||
        logoArea.appendChild(h2);
 | 
						||
 | 
						||
        header.appendChild(logoArea);
 | 
						||
 | 
						||
        // Right side of the header
 | 
						||
        const nav = document.createElement("nav");
 | 
						||
 | 
						||
        const lAbout = document.createElement("a");
 | 
						||
        lAbout.href = "https://en.about.museum-digital.org/about";
 | 
						||
        lAbout.textContent = this.tls.about;
 | 
						||
        nav.appendChild(lAbout);
 | 
						||
 | 
						||
        const lContactList = document.createElement("div");
 | 
						||
 | 
						||
        const lContact = document.createElement("a");
 | 
						||
        lContact.textContent = this.tls.contact;
 | 
						||
        lContact.href = "https://en.about.museum-digital.org/contact/";
 | 
						||
        lContactList.appendChild(lContact);
 | 
						||
 | 
						||
        const lContactDiv = document.createElement("div");
 | 
						||
 | 
						||
        const lImprint = document.createElement("a");
 | 
						||
        lImprint.textContent = this.tls.imprint;
 | 
						||
        lImprint.href = "https://en.about.museum-digital.org/impressum";
 | 
						||
        lContactDiv.appendChild(lImprint);
 | 
						||
 | 
						||
        const lPrivacy = document.createElement("a");
 | 
						||
        lPrivacy.textContent = this.tls.privacy_policy;
 | 
						||
        lPrivacy.href = "https://en.about.museum-digital.org/privacy/";
 | 
						||
        lContactDiv.appendChild(lPrivacy);
 | 
						||
 | 
						||
        lContactList.appendChild(lContactDiv);
 | 
						||
        nav.appendChild(lContactList);
 | 
						||
 | 
						||
        const lNews = document.createElement("a")
 | 
						||
        lNews.textContent = this.tls.news;
 | 
						||
        lNews.href = "https://blog.museum-digital.org/";
 | 
						||
        nav.appendChild(lNews);
 | 
						||
 | 
						||
        header.appendChild(nav);
 | 
						||
        document.body.appendChild(header);
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    renderHeader() {
 | 
						||
 | 
						||
        const appHeader = document.createElement("header");
 | 
						||
 | 
						||
        const h1 = document.createElement("h1");
 | 
						||
 | 
						||
        const img = document.createElement("img");
 | 
						||
        img.width = "70";
 | 
						||
        img.height = "70";
 | 
						||
        img.src = "assets/img/mdlogo-csvxml.svg";
 | 
						||
        img.alt = "";
 | 
						||
        h1.appendChild(img);
 | 
						||
 | 
						||
        const h1Span = document.createElement("span");
 | 
						||
        h1Span.textContent = "museum-digital:csvxml";
 | 
						||
        h1.appendChild(h1Span);
 | 
						||
 | 
						||
        appHeader.appendChild(h1);
 | 
						||
 | 
						||
        document.body.appendChild(appHeader);
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    renderHelpTexts() {
 | 
						||
 | 
						||
        let app = this;
 | 
						||
        (async function() {
 | 
						||
 | 
						||
            function appendQA(question, answer) {
 | 
						||
 | 
						||
                const div = document.createElement("div");
 | 
						||
                div.classList.add("qaDiv");
 | 
						||
 | 
						||
                const qElem = document.createElement("h3");
 | 
						||
                qElem.textContent = question;
 | 
						||
                qElem.style.cursor = "pointer";
 | 
						||
                div.appendChild(qElem);
 | 
						||
 | 
						||
                qElem.addEventListener('click', function() {
 | 
						||
 | 
						||
                    console.log("Listing validation errors");
 | 
						||
 | 
						||
                    const dialogueContent = document.createElement("div");
 | 
						||
 | 
						||
                    const headline = document.createElement("h3");
 | 
						||
                    headline.textContent = question;
 | 
						||
                    headline.appendChild(app.generateDialogueCloseButton());
 | 
						||
                    dialogueContent.appendChild(headline);
 | 
						||
 | 
						||
                    const answerDiv = document.createElement("div");
 | 
						||
                    answerDiv.textContent = answer;
 | 
						||
                    dialogueContent.appendChild(answerDiv);
 | 
						||
                    CsvxmlDialogue.drawDialogue(dialogueContent);
 | 
						||
                });
 | 
						||
 | 
						||
                return div;
 | 
						||
 | 
						||
            }
 | 
						||
 | 
						||
            const div = document.createElement("div");
 | 
						||
 | 
						||
            div.appendChild(appendQA(app.tls.help_where_am_i, app.tls.help_where_am_i_content));
 | 
						||
            div.appendChild(appendQA(app.tls.help_what_is_csv, app.tls.help_what_is_csv_content));
 | 
						||
            div.appendChild(appendQA(app.tls.help_how_to_format_csv, app.tls.help_how_to_format_csv_content));
 | 
						||
 | 
						||
            app.domHelpWrapper.appendChild(div);
 | 
						||
 | 
						||
        })();
 | 
						||
 | 
						||
        document.body.appendChild(this.domHelpWrapper);
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    renderUploader() {
 | 
						||
 | 
						||
        let app = this;
 | 
						||
        (async function() {
 | 
						||
 | 
						||
            const h2 = document.createElement("h2");
 | 
						||
            h2.textContent = app.tls.upload;
 | 
						||
            app.domUploaderWrapper.appendChild(h2);
 | 
						||
 | 
						||
            const form = document.createElement("form");
 | 
						||
 | 
						||
            const label = document.createElement("label");
 | 
						||
            label.textContent = app.tls.select_csv_file_for_upload;
 | 
						||
            label.setAttribute("for", "fileToUpload");
 | 
						||
            form.appendChild(label);
 | 
						||
 | 
						||
            const input = document.createElement("input");
 | 
						||
            input.type = "file";
 | 
						||
            input.id = "fileToUpload";
 | 
						||
            input.setAttribute("tabindex", "1");
 | 
						||
            input.accept = ".csv";
 | 
						||
            input.required = "required";
 | 
						||
            input.addEventListener('change', async function() {
 | 
						||
                app.uploadFileForValidation(input.files[0]);
 | 
						||
            });
 | 
						||
            form.appendChild(input);
 | 
						||
 | 
						||
            /*
 | 
						||
            const button = document.createElement("button");
 | 
						||
            button.textContent = "Upload"; // TODO
 | 
						||
            button.type = "submit";
 | 
						||
            form.appendChild(button);
 | 
						||
            */
 | 
						||
 | 
						||
            app.domUploaderWrapper.appendChild(form);
 | 
						||
 | 
						||
        })();
 | 
						||
 | 
						||
        document.body.appendChild(this.domUploaderWrapper);
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    // Takes a callback function
 | 
						||
    doForFieldList(callback) {
 | 
						||
        let fieldLists = document.getElementsByClassName("fieldList");
 | 
						||
        for (let i = 0, max = fieldLists.length; i < max; i++) {
 | 
						||
 | 
						||
            let fields = fieldLists[i].getElementsByTagName("li");
 | 
						||
            for (let j = 0, maxj = fields.length; j < maxj; j++) {
 | 
						||
 | 
						||
                callback(fields[j]);
 | 
						||
 | 
						||
            }
 | 
						||
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    toggleListFieldSelectionState(field) {
 | 
						||
 | 
						||
        let app = this;
 | 
						||
 | 
						||
        let newValue = field.getAttribute("data-alt");
 | 
						||
        field.setAttribute("data-alt", field.textContent);
 | 
						||
        field.textContent = newValue;
 | 
						||
        field.classList.toggle("humanTLToggled");
 | 
						||
 | 
						||
        if (field.classList.contains("humanTLToggled") === false) return;
 | 
						||
 | 
						||
        let dependencies = this.fieldListFlat[field.id].dependsOn;
 | 
						||
        if (dependencies !== undefined && dependencies !== null) {
 | 
						||
            let linkedFields = this.fieldListFlat[field.id].dependsOn;
 | 
						||
            for (let i = 0, max = linkedFields.length; i < max; i++) {
 | 
						||
                let linkedField = document.getElementById(linkedFields[i]);
 | 
						||
                if (linkedField.classList.contains("humanTLToggled") === true) continue;
 | 
						||
                this.toggleListFieldSelectionState(linkedField);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    checkCSVBySelectionAccessibility() {
 | 
						||
 | 
						||
        let selected = document.getElementsByClassName("humanTLToggled");
 | 
						||
        if (selected.length === 0) {
 | 
						||
            this.csvBySelectionButton.classList.add("invisible");
 | 
						||
            this.unsetSelectionButton.classList.add("invisible");
 | 
						||
        }
 | 
						||
        else {
 | 
						||
            this.csvBySelectionButton.classList.remove("invisible");
 | 
						||
            this.unsetSelectionButton.classList.remove("invisible");
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    getOptionsSection() {
 | 
						||
 | 
						||
        function genButton(id, text, link = "") {
 | 
						||
 | 
						||
            const output = document.createElement("span");
 | 
						||
            output.id = id;
 | 
						||
            output.setAttribute("tabindex", "1");
 | 
						||
            output.textContent = text;
 | 
						||
            output.classList.add("buttonLike");
 | 
						||
            if (link !== "") output.href = link;
 | 
						||
            return output;
 | 
						||
 | 
						||
        }
 | 
						||
 | 
						||
        const options = document.createElement("div");
 | 
						||
        options.classList.add("options");
 | 
						||
 | 
						||
        const app = this;
 | 
						||
 | 
						||
        const dlAllButton = genButton("dlAll", this.tls.download_csv_all);
 | 
						||
        dlAllButton.cursor = "pointer";
 | 
						||
        dlAllButton.addEventListener('click', function(e) {
 | 
						||
            e.preventDefault();
 | 
						||
            app.generateCsv();
 | 
						||
        });
 | 
						||
        options.appendChild(dlAllButton);
 | 
						||
 | 
						||
        this.csvBySelectionButton = genButton("csvBySelection", this.tls.download_csv_by_selection);
 | 
						||
        this.csvBySelectionButton.classList.add("invisible");
 | 
						||
        options.appendChild(this.csvBySelectionButton);
 | 
						||
 | 
						||
        const optionSelectRequired = genButton("selectRequired", this.tls.select_required_fields);
 | 
						||
        options.appendChild(optionSelectRequired);
 | 
						||
 | 
						||
        const optionSelectAll = genButton("selectAll", this.tls.select_all_fields);
 | 
						||
        options.appendChild(optionSelectAll);
 | 
						||
 | 
						||
        this.unsetSelectionButton = genButton("unsetSelection", this.tls.unset_selection);
 | 
						||
        this.unsetSelectionButton.classList.add("invisible");
 | 
						||
        options.appendChild(this.unsetSelectionButton);
 | 
						||
 | 
						||
        this.csvBySelectionButton.addEventListener('click', function(e) {
 | 
						||
            e.preventDefault();
 | 
						||
 | 
						||
            let selected = document.getElementsByClassName("humanTLToggled");
 | 
						||
            let selectedFields = [];
 | 
						||
            for (let i = 0, max = selected.length; i < max; i++) {
 | 
						||
                selectedFields += selected[i].getAttribute("data-value");
 | 
						||
            }
 | 
						||
            app.generateCsv(selectedFields);
 | 
						||
 | 
						||
        });
 | 
						||
 | 
						||
        optionSelectRequired.addEventListener('click', function(e) {
 | 
						||
            e.preventDefault();
 | 
						||
 | 
						||
            app.doForFieldList(function(field) {
 | 
						||
                if (field.classList.contains("requiredField") === false) return;
 | 
						||
                if (field.classList.contains("humanTLToggled") === true) return;
 | 
						||
 | 
						||
                app.toggleListFieldSelectionState(field);
 | 
						||
                app.checkCSVBySelectionAccessibility();
 | 
						||
 | 
						||
            });
 | 
						||
 | 
						||
        });
 | 
						||
 | 
						||
        optionSelectAll.addEventListener('click', function(e) {
 | 
						||
            e.preventDefault();
 | 
						||
 | 
						||
            app.doForFieldList(function(field) {
 | 
						||
                if (field.classList.contains("humanTLToggled") === true) return;
 | 
						||
 | 
						||
                app.toggleListFieldSelectionState(field);
 | 
						||
                app.checkCSVBySelectionAccessibility();
 | 
						||
 | 
						||
            });
 | 
						||
 | 
						||
        });
 | 
						||
 | 
						||
        this.unsetSelectionButton.addEventListener('click', function(e) {
 | 
						||
            e.preventDefault();
 | 
						||
 | 
						||
            app.doForFieldList(function(field) {
 | 
						||
                if (field.classList.contains("humanTLToggled") === false) return;
 | 
						||
 | 
						||
                app.toggleListFieldSelectionState(field);
 | 
						||
                app.checkCSVBySelectionAccessibility();
 | 
						||
 | 
						||
            });
 | 
						||
 | 
						||
        });
 | 
						||
 | 
						||
        return options;
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    renderMain() {
 | 
						||
 | 
						||
        const domH2 = document.createElement("h2");
 | 
						||
        domH2.textContent = this.tls.currently_approved_tags;
 | 
						||
        this.domMainWrapper.appendChild(domH2);
 | 
						||
 | 
						||
        this.domMainWrapper.appendChild(this.getOptionsSection());
 | 
						||
 | 
						||
        for (let sectionName in this.fieldList) {
 | 
						||
 | 
						||
            const domDiv = document.createElement("div");
 | 
						||
 | 
						||
            const domH3 = document.createElement("h3");
 | 
						||
            domH3.textContent = sectionName;
 | 
						||
            domDiv.appendChild(domH3);
 | 
						||
 | 
						||
            const domUl = document.createElement("ul");
 | 
						||
            domUl.classList.add("fieldList");
 | 
						||
 | 
						||
            const sectionFields = this.fieldList[sectionName];
 | 
						||
            for (let fieldName in sectionFields) {
 | 
						||
                const field = sectionFields[fieldName];
 | 
						||
 | 
						||
                const domLi = document.createElement("li");
 | 
						||
                domLi.textContent = fieldName;
 | 
						||
                domLi.id = fieldName;
 | 
						||
                domLi.setAttribute("data-alt", field.name_human_readable)
 | 
						||
                domLi.setAttribute("data-value", fieldName)
 | 
						||
                if (field.required === true) domLi.classList.add("requiredField");
 | 
						||
                domUl.appendChild(domLi);
 | 
						||
 | 
						||
                const tooltipContent = document.createElement("div");
 | 
						||
                const explicaP = document.createElement("p");
 | 
						||
                explicaP.textContent = field.explica;
 | 
						||
                tooltipContent.appendChild(explicaP);
 | 
						||
 | 
						||
                if (field.remarks !== undefined && field.remarks !== '') {
 | 
						||
 | 
						||
                    const remarkHl = document.createElement("h4");
 | 
						||
                    remarkHl.textContent = this.tls.remarks;
 | 
						||
                    tooltipContent.appendChild(remarkHl)
 | 
						||
                    const remarkCont = document.createElement("p");
 | 
						||
                    remarkCont = field.remarks;
 | 
						||
                    tooltipContent.appendChild(remarkCont);
 | 
						||
 | 
						||
                }
 | 
						||
 | 
						||
                if (field.allowedValues !== undefined && Object.values(field.allowedValues).length !== 0) {
 | 
						||
                    const allowedHl = document.createElement("h4");
 | 
						||
                    allowedHl.textContent = this.tls.allowed_values;
 | 
						||
                    tooltipContent.appendChild(allowedHl);
 | 
						||
                    const allowedList = document.createElement("p");
 | 
						||
                    allowedList.textContent = Object.values(field.allowedValues).join(', ');
 | 
						||
                    tooltipContent.appendChild(allowedList);
 | 
						||
                }
 | 
						||
 | 
						||
                CsvxmlTooltip.bindTooltipToElement(domLi, field.name_human_readable, tooltipContent);
 | 
						||
 | 
						||
            }
 | 
						||
 | 
						||
            domDiv.appendChild(domUl);
 | 
						||
 | 
						||
            this.domMainWrapper.appendChild(domDiv);
 | 
						||
 | 
						||
        }
 | 
						||
 | 
						||
        document.body.appendChild(this.domMainWrapper);
 | 
						||
 | 
						||
        let app = this;
 | 
						||
        this.doForFieldList(function(field) {
 | 
						||
 | 
						||
            // Each field should switch its visible content and human-readable
 | 
						||
            // translation on a click.
 | 
						||
            field.addEventListener('click', function(e) {
 | 
						||
 | 
						||
                app.toggleListFieldSelectionState(field);
 | 
						||
                app.checkCSVBySelectionAccessibility();
 | 
						||
 | 
						||
            });
 | 
						||
 | 
						||
        });
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    renderFooter() {
 | 
						||
 | 
						||
        const footer = document.createElement("footer");
 | 
						||
 | 
						||
        const licenseStatement = document.createElement("p");
 | 
						||
        licenseStatement.textContent = "This work is licensed under the GNU Affero Public License Version 3.";
 | 
						||
        footer.appendChild(licenseStatement);
 | 
						||
 | 
						||
        const footerOptions = document.createElement("div");
 | 
						||
 | 
						||
        const codeLink = document.createElement("a");
 | 
						||
        codeLink.textContent = "Source code";
 | 
						||
        codeLink.href = "https://gitea.armuli.eu/museum-digital/csvxml";
 | 
						||
        footerOptions.appendChild(codeLink);
 | 
						||
 | 
						||
        if ('serviceWorker' in navigator) {
 | 
						||
        const refreshB = document.createElement("span");
 | 
						||
        refreshB.textContent = "Reload application";
 | 
						||
        refreshB.setAttribute("tabindex", 1);
 | 
						||
        refreshB.addEventListener('click', function(e) {
 | 
						||
 | 
						||
            Promise.all(['csvxml-cache-v1'].map(function(cache) {
 | 
						||
                caches.has(cache).then(function(hasCache) {
 | 
						||
                    if (hasCache === true) {
 | 
						||
                        caches.delete(cache).then(function(deletionStatus) {});
 | 
						||
                    }
 | 
						||
                })
 | 
						||
            }))
 | 
						||
            location.reload()
 | 
						||
 | 
						||
        }, {passive: true, once: true});
 | 
						||
        footerOptions.appendChild(refreshB);
 | 
						||
        }
 | 
						||
 | 
						||
        footer.appendChild(footerOptions);
 | 
						||
        document.body.appendChild(footer);
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
(async function() {
 | 
						||
 | 
						||
    function getLang() {
 | 
						||
 | 
						||
        const allowedLangs = document.documentElement.getAttribute("data-allowed-langs").split(',');
 | 
						||
 | 
						||
        if (navigator.language === undefined) return 'en';
 | 
						||
 | 
						||
        const browserLang = navigator.language.toLowerCase().substr(0, 2);
 | 
						||
        console.log(browserLang);
 | 
						||
 | 
						||
        if (allowedLangs.includes(browserLang)) return browserLang;
 | 
						||
        else return 'en';
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    const lang = getLang();
 | 
						||
    document.documentElement.setAttribute("lang", lang);
 | 
						||
 | 
						||
    document.body.classList.add("loading");
 | 
						||
 | 
						||
    let loaded = 0;
 | 
						||
 | 
						||
    let fieldList;
 | 
						||
    let tls;
 | 
						||
 | 
						||
    function loadPage() {
 | 
						||
 | 
						||
        document.body.classList.remove("loading");
 | 
						||
 | 
						||
        const page = new CsvxmlPage(fieldList, tls);
 | 
						||
        page.renderGenHeader();
 | 
						||
        page.renderHeader();
 | 
						||
        page.renderHelpTexts();
 | 
						||
        page.renderUploader();
 | 
						||
        page.renderMain();
 | 
						||
        page.renderFooter();
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    window.fetch('/json/fields.' + lang + '.json', {
 | 
						||
        method: 'GET', cache: 'no-cache', credentials: 'same-origin',
 | 
						||
    }).then(function(response) {
 | 
						||
        return response.json();
 | 
						||
    }).then(function(elements) {
 | 
						||
        fieldList = elements;
 | 
						||
        loaded++;
 | 
						||
        if (loaded === 2) loadPage();
 | 
						||
    });
 | 
						||
 | 
						||
    window.fetch('/json/tls.' + lang + '.json', {
 | 
						||
        method: 'GET', cache: 'no-cache', credentials: 'same-origin',
 | 
						||
    }).then(function(response) {
 | 
						||
        return response.json();
 | 
						||
    }).then(function(elements) {
 | 
						||
        tls = elements;
 | 
						||
        loaded++;
 | 
						||
        if (loaded === 2) loadPage();
 | 
						||
    });
 | 
						||
 | 
						||
})();
 |