/**
* {@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);