Source: xxe/part/ValidateTool.js

/**
 * {@link XMLEditor} part used to display the validity status of 
 * the document being edited and validity error messages (if any).
 */
class ValidateTool extends HTMLElement {
    constructor() {
        super();

        const infos = [
            { severity: SEVERITY_NONE, icon: "ok-circled",
              color: "green", description: "Document is Valid" },
            { severity: SEVERITY_INVALID_REFERENCE, icon: "attention",
              color: "#FFEF00", description: "Invalid Reference" },
            { severity: SEVERITY_SEMANTIC_WARNING, icon: "minus-circled",
              color: "orange", description: "Semantic Warning" },
            { severity: SEVERITY_SEMANTIC_ERROR, icon: "minus-circled",
              color: "red", description: "Semantic Error" },
            { severity: SEVERITY_INVALID_DATA, icon: "cancel-squared",
              color: "orange", description: "Invalid Data" },
            { severity: SEVERITY_INVALID_STRUCTURE, icon: "cancel-squared",
              color: "red", description: "Invalid Structure" }
        ];
        this._severityInfo = {};
        for (let info of infos) {
            this._severityInfo[info.severity] = info;
        }
        
        this._xmlEditor = null;
        this._button = null;
        this._diagPopup = null;
        this._onDiagPopupClosed = this.onDiagPopupClosed.bind(this);
        this._onLinkClicked = this.onLinkClicked.bind(this);
    }

    // -----------------------------------------------------------------------
    // Custom element
    // -----------------------------------------------------------------------

    connectedCallback() {
        if (this.firstChild === null) {
            this._button = document.createElement("span");
            this._button.className = "xxe-tool-button xxe-valid-tool-button";
            this.showSeverity(-1);
            this.appendChild(this._button);

            let handler = this.onClick.bind(this);
            this._button.addEventListener("click", handler);
            this._button.addEventListener("contextmenu", handler);
        }
        // Otherwise, already connected.
    }

    // -----------------------------------------------------------------------
    // Event handlers
    // -----------------------------------------------------------------------

    onClick(event) {
        XUI.Util.consumeEvent(event);

        if (this._xmlEditor !== null &&
            !this._button.classList.contains("xui-control-disabled")) {
            this._xmlEditor.validateDocument()
                .then((result) => {
                    // null result: document cannot be checked for validity. 
                    this.showDiagnostics((result !== null)?
                                         result.diagnostics : null);
                })
                .catch((error) => {
                    console.error(`Request "validateDocument" has failed: \
${error}`);
                });
        }
    }

    showDiagnostics(diagnostics) {
        if (this._diagPopup === null) {
            if (diagnostics !== null && diagnostics.length > 0) {
                this._diagPopup = XUI.Dialogs.open({
                    form: this.createDiagPopup(diagnostics), type: "popup",
                    classes:
                    "xui-control xui-dialog xxe-tool-popup xxe-valid-tool-diag",
                    position: "startmenu", reference: this
                });
                this._diagPopup.addEventListener("dialogclosed",
                                                 this._onDiagPopupClosed);
            }
            // Otherwise, either cannot be checked or is valid: do not show
            // anything.
        } else {
            XUI.Dialogs.close(this._diagPopup);
        }
    }
    
    createDiagPopup(diagnostics) {
        // PREFERENCE "sortDiagnosticsBySeverity" NOT SUPPORTED.
        
        let list = document.createElement("div");
        list.className =
            "xui-control xxe-tool-popup-list xxe-valid-tool-diag-list";
        list.style.width =
            String(this.parentElement.getBoundingClientRect().width / 2) + "px";

        let count = 0;
        for (let diag of diagnostics) {
            let item = document.createElement("div");
            item.className = 
                "xxe-valid-tool-diag-item xxe-valid-tool-diag-" +
                String(diag.severity) + "-" + String(count % 2);

            let link1 = this.createDiagLink(diag.severity,
                                            String(1+count), diag.elementUID);
            item.appendChild(link1);

            let msg = document.createElement("div");
            msg.className = "xxe-valid-tool-diag-msg";
            msg.appendChild(document.createTextNode(diag.message));
            
            // Useful details are given not only for INVALID_REFERENCE (schema
            // validation) but also for SEMANTIC_WARNING (LinkChecker).
            const detail = diag.detail;
            if (detail !== null &&
                Array.isArray(detail) && detail.length === 3 &&
                (detail[0] === "DUPLICATE_ID" ||
                 detail[0] === "DUPLICATE_ANCHOR")) {
                msg.appendChild(document.createElement("br"));
                msg.appendChild(document.createTextNode(
                    `First occurrence of "${detail[1]}" is found `));

                let link2 = this.createDiagLink(-1, "here", detail[2]);
                msg.appendChild(link2);
            }
            
            item.appendChild(msg);

            list.appendChild(item);
            ++count;
        }
        
        return list;
    }

    createDiagLink(severity, text, elementUID) {
        let link = document.createElement("span");
        link.className = "xxe-valid-tool-diag-link";
        link.setAttribute("data-uid", elementUID);
        
        if (severity >= SEVERITY_INVALID_REFERENCE &&
            severity <= SEVERITY_INVALID_STRUCTURE) {
            const desc = this._severityInfo[severity].description;
            link.setAttribute("title",
            `${desc} #${text}\nClick to select the element having this error.`);
            
            const icon = this.createDiagIcon(severity);
            link.appendChild(icon);
            link.appendChild(document.createTextNode("\u00A0"));
        }
        
        let label = document.createElement("span");
        label.className = "xxe-valid-tool-diag-label";
        label.appendChild(document.createTextNode(text));
        link.appendChild(label);
        
        link.addEventListener("click", this._onLinkClicked);

        return link;
    }
    
    createDiagIcon(severity) {
        let iconChar = '\u25CF'; // Black Circle
        let iconColor = "silver";
        switch (severity) {
        case SEVERITY_INVALID_STRUCTURE:
            iconChar = '\u25A0'; // Black Square
            //FALLTHROUGH
        case SEVERITY_SEMANTIC_ERROR:
            iconColor = "red";
            break;
        case SEVERITY_INVALID_DATA:
            iconChar = '\u25A0'; // Black Square
            //FALLTHROUGH
        case SEVERITY_SEMANTIC_WARNING:
            iconColor = "orange";
            break;
        case SEVERITY_INVALID_REFERENCE:
            iconChar = '\u25B4'; // Black Up-Pointing Small Triangle
            iconColor = "#E4D00A"; // Citrine
            break;
        }
        
        const icon = document.createElement("span");
        icon.setAttribute("style", `color:${iconColor}`);
        icon.appendChild(document.createTextNode(iconChar));

        return icon;
    }
    
    onLinkClicked(event) {
        XUI.Util.consumeEvent(event);
        
        if (this._xmlEditor !== null) {
            let docView = this._xmlEditor.documentView;

            let uid = event.currentTarget.getAttribute("data-uid"); //NOT.target
            if (uid) {
                let view = docView.getNodeView(uid, /*reportError*/ false);
                if (view !== null) {
                    docView.selectNode(view, /*show*/ true);
                }
            }
        }
    }
    
    onDiagPopupClosed(event) {
        this._diagPopup = null;
        
        if (this._xmlEditor !== null) {
            this._xmlEditor.documentView.requestFocus();
        }
    }
    
    // -----------------------------------------------------------------------
    // Used by XMLEditor
    // -----------------------------------------------------------------------

    set xmlEditor(editor) {
        this._xmlEditor = editor;
    }

    validityStateChanged(event) {
        // When a document is opened or closed, XMLEditor invokes this method
        // with a null event ==> check validity is always initially disabled.
        //
        // After the document is opened, if it can be checked for validity, an
        // actual DocumentValidatedEvent is always sent by the server, even if
        // this event just says: it's all good.
        
        let severity = -1;
        if (event !== null) {
            severity = event.severity;
        }
        this.showSeverity(severity);
    }
    
    showSeverity(severity) {
        let title = "Check Document Validity";
        let icon = "ok-circled";
        let color = null;
        
        if (severity >= SEVERITY_NONE &&
            severity <= SEVERITY_INVALID_STRUCTURE) {
            this._button.classList.remove("xui-control-disabled");
            
            const info = this._severityInfo[severity];
            title += "\n\n" + info.description;
            icon = info.icon;
            color = info.color;
        } else {
            this._button.classList.add("xui-control-disabled");
        }

        this._button.setAttribute("title", title);
        this._button.textContent = XUI.StockIcon[icon];
        this._button.style.color = color; // null removes CSS prop "color".
    }
}

window.customElements.define("xxe-validate-tool", ValidateTool);