diff --git a/html/classhelper.css b/html/classhelper.css index 6ef82e94..a3625658 100644 --- a/html/classhelper.css +++ b/html/classhelper.css @@ -116,3 +116,12 @@ table tr:hover { flex-grow: 1; } +.search-error input { + border: 2px solid red; +} + +.search-error > .error-message { + color: red; + font-size: 14px; + margin-left: 4px; +} diff --git a/html/classhelper.js b/html/classhelper.js index 175d7de9..01addf5b 100644 --- a/html/classhelper.js +++ b/html/classhelper.js @@ -607,6 +607,16 @@ class ClassHelper extends HTMLElement { search.addEventListener("click", (e) => { e.preventDefault(); let fd = new FormData(form); + + let hasError = this.popupRef.document.getElementsByClassName("search-error").item(0); + if (hasError != null) { + let current = fd.get(hasError.dataset.errorField) + let prev = hasError.dataset.errorValue; + if (current === prev) { + return; + } + } + this.dispatchEvent(new CustomEvent("search", { detail: { value: fd @@ -1163,7 +1173,7 @@ class ClassHelper extends HTMLElement { } /** method when search is performed within classhelper, here we need to update the classhelper table with search results - * @param {URL | string} apiURL + * @param {URL} apiURL * @param {HelpUrlProps} props * @throws {Error} when fetching or parsing data from roundup rest api fails */ @@ -1198,6 +1208,42 @@ class ClassHelper extends HTMLElement { throw new Error(message, { cause: error }); } + if (!resp.ok && resp.status === 400) { + // In the error message we will have the field name that caused the error. + // and the value that caused the error, in a double quoted string + // "(value)" "(key)", this regex is a capture group + // that captures the value and key in the error message. + let regexCaptureDoubleQuotedString = /"(.*?)"/g; + let iterator = json.error.msg.matchAll(regexCaptureDoubleQuotedString); + let results = Array.from(iterator); + + if (results.length == 2) { + let value = results[0][1]; + let field = results[1][1]; + + // Find the input element with the name of the key + let input = this.popupRef.document.getElementsByName(field).item(0); + if (input) { + let parent = input.parentElement; + parent.classList.add("search-error"); + parent.dataset.errorValue = value; + parent.dataset.errorField = field; + // remove if there was already an error message + parent.getElementsByClassName("error-message").item(0)?.remove(); + let span = document.createElement("span"); + span.classList.add("error-message"); + span.textContent = `Invalid value: ${value}`; + parent.appendChild(span); + return; + } + } + } + + if (!resp.ok && resp.status === 403) { + this.popupRef.alert(json.error.msg); + return; + } + if (!resp.ok) { let message = `Unexpected response\n`; message += `url: ${apiURL.toString()}\n`; @@ -1228,6 +1274,13 @@ class ClassHelper extends HTMLElement { const popupBody = this.popupRef.document.body; const pageIndex = selfPageURL.searchParams.get("@page_index"); + // remove any previous error messages + let errors = Array.from(popupDocument.getElementsByClassName("search-error")); + errors.forEach(element => { + element.classList.remove("search-error"); + element.getElementsByClassName("error-message").item(0)?.remove(); + }); + const oldPaginationFrag = popupDocument.getElementById("popup-pagination"); let newPaginationFrag = this.getPaginationFragment(prevPageURL, nextPageURL, pageIndex, props.pageSize, collection.length); popupBody.replaceChild(newPaginationFrag, oldPaginationFrag);