From b9179871c4da42daa14c3ec09469c16d744f321f Mon Sep 17 00:00:00 2001 From: Max Berger Date: Mon, 9 Mar 2026 23:17:47 +0100 Subject: [PATCH] Create common error handler and input field validator --- .../web/internal_data/js/scenes/LoginScene.js | 77 +++++++++--------- radicale/web/internal_data/js/utils/error.js | 74 +++++++++++++++++ .../internal_data/js/utils/form_validator.js | 80 +++++++++++++++++++ 3 files changed, 190 insertions(+), 41 deletions(-) create mode 100644 radicale/web/internal_data/js/utils/error.js create mode 100644 radicale/web/internal_data/js/utils/form_validator.js diff --git a/radicale/web/internal_data/js/scenes/LoginScene.js b/radicale/web/internal_data/js/scenes/LoginScene.js index 40855397..f6cc6b91 100644 --- a/radicale/web/internal_data/js/scenes/LoginScene.js +++ b/radicale/web/internal_data/js/scenes/LoginScene.js @@ -23,7 +23,8 @@ import { get_principal } from "../api/api.js"; import { CollectionsScene } from "./CollectionsScene.js"; import { LoadingScene } from "./LoadingScene.js"; import { Scene, pop_scene, push_scene, scene_stack } from "./scene_manager.js"; - +import { ErrorHandler } from "../utils/error.js"; +import { FormValidator, validate_non_empty } from "../utils/form_validator.js"; /** * @constructor * @implements {Scene} @@ -42,8 +43,10 @@ export class LoginScene { /** @type {?number} */ let scene_index = null; let user = ""; - let error = ""; /** @type {?XMLHttpRequest} */ let principal_req = null; + let errorHandler = new ErrorHandler(error_form); + let validator = new FormValidator(errorHandler); + validator.addValidator(user_form, validate_non_empty(user_form, "Username")); function read_form() { user = user_form.value; @@ -52,52 +55,43 @@ export class LoginScene { function fill_form() { user_form.value = user; password_form.value = ""; - if (error) { - error_form.textContent = "Error: " + error; - error_form.classList.remove("hidden"); - } else { - error_form.classList.add("hidden"); - } } function onlogin() { try { read_form(); let password = password_form.value; - if (user) { - error = ""; - // setup logout - logout_view.classList.remove("hidden"); - logout_btn.onclick = onlogout; - refresh_btn.onclick = refresh; - logout_user_form.textContent = user + "'s Collections"; - // Fetch principal - let loading_scene = new LoadingScene(); - push_scene(loading_scene, false); - principal_req = get_principal(user, password, function (collection, error1) { - if (scene_index === null) { - return; - } - principal_req = null; - if (error1) { - error = error1; - pop_scene(scene_index); - } else { - // show collections - let saved_user = user; - user = ""; - let collections_scene = new CollectionsScene( - saved_user, password, collection, function (error1) { - error = error1; - user = saved_user; - }); - push_scene(collections_scene, true); - } - }); - } else { - error = "Username is empty"; - fill_form(); + if (!validator.validate()) { + return false; } + // setup logout + logout_view.classList.remove("hidden"); + logout_btn.onclick = onlogout; + refresh_btn.onclick = refresh; + logout_user_form.textContent = user + "'s Collections"; + // Fetch principal + let loading_scene = new LoadingScene(); + push_scene(loading_scene, false); + principal_req = get_principal(user, password, function (collection, error1) { + if (scene_index === null) { + return; + } + principal_req = null; + if (error1) { + errorHandler.setError(error1); + pop_scene(scene_index); + } else { + // show collections + let saved_user = user; + user = ""; + let collections_scene = new CollectionsScene( + saved_user, password, collection, function (error1) { + errorHandler.setError(error1); + user = saved_user; + }); + push_scene(collections_scene, true); + } + }); } catch (err) { console.error(err); } @@ -117,6 +111,7 @@ export class LoginScene { return false; } + function remove_logout() { logout_view.classList.add("hidden"); logout_btn.onclick = null; diff --git a/radicale/web/internal_data/js/utils/error.js b/radicale/web/internal_data/js/utils/error.js new file mode 100644 index 00000000..842d5791 --- /dev/null +++ b/radicale/web/internal_data/js/utils/error.js @@ -0,0 +1,74 @@ +/** + * This file is part of Radicale Server - Calendar Server + * Copyright © 2026-2026 Max Berger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * ErrorHandler manages error messages for a HTMLElement. + */ +export class ErrorHandler { + /** + * @param {HTMLElement} element + */ + constructor(element) { + /** @type {HTMLElement} */ this._element = element; + /** @type {string} */ this._lastHTML = "anything_but_blank"; + this.clearError(); + } + + /** + * Sets an error message for a given key. + * @param {string} errorMessage + */ + setError(errorMessage) { + this._update([errorMessage]); + } + + /** + * Sets multiple error messages. + * @param {string[]} errorMessages + */ + setErrors(errorMessages) { + this._update(errorMessages); + } + + clearError() { + this._update([]); + } + + + /** + * Updates the element visibility and text content. + * @param {string[]} errorMessages + * @private + */ + _update(errorMessages) { + let html = ""; + if (errorMessages.length > 0) { + html = errorMessages.join("
"); + } + + if (html !== this._lastHTML) { + this._element.innerHTML = html; + if (html) { + this._element.classList.remove("hidden"); + } else { + this._element.classList.add("hidden"); + } + this._lastHTML = html; + } + } +} diff --git a/radicale/web/internal_data/js/utils/form_validator.js b/radicale/web/internal_data/js/utils/form_validator.js new file mode 100644 index 00000000..96645296 --- /dev/null +++ b/radicale/web/internal_data/js/utils/form_validator.js @@ -0,0 +1,80 @@ +/** + * This file is part of Radicale Server - Calendar Server + * Copyright © 2026-2026 Max Berger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { ErrorHandler } from "./error.js"; + +/** + * Manages form validation by running validation functions on input fields. + */ +export class FormValidator { + + /** + * @param {ErrorHandler} error_handler + */ + constructor(error_handler) { + this.error_handler = error_handler; + this.validation_methods = []; + } + + /** + * @param {HTMLInputElement} field + * @param {function(): ?string} validation_method + */ + addValidator(field, validation_method) { + this.validation_methods.push({ field, validation_method }); + field.addEventListener("input", () => { + this.validate(); + }); + this.validate(); + } + + /** + * Validates all added validators. + * @returns true if all validators are valid + */ + validate() { + let errorMessages = []; + for (let { field, validation_method } of this.validation_methods) { + let errorMessage = validation_method(field); + if (errorMessage) { + errorMessages.push(errorMessage); + } + } + this.error_handler.setErrors(errorMessages); + return errorMessages.length === 0; + } +} + +/** + * Validates that the input is not empty. + * @param {HTMLInputElement} input + * @param {string} field_name + * @returns{function(): ?string} + */ +export function validate_non_empty(input, field_name) { + return () => { + if (input.value) { + return null; + } + return field_name + " is empty"; + }; +} + + + +