1/***************************************************************************** 2// Firma: Hehner Reus Systems GmbH 3// Projekt: com.cerno.basis 4// Datei: messagehandler.js 5// Erstellung: 05.10.2022 6// Verwendung: Dieses Objekt verwaltet das Messagehandling auf der Seite. 7/**************************************************************************** */ 8 9"use strict"; 10 11/** 12 * Die möglichen Level einer Nachricht. 13 */ 14const HRMessageLevel = { 15 /* 16 * "Nur" eine Information. 17 */ 18 Info: "info", 19 20 /* 21 * Eine Warnung. Keine Aktion des Nutzers zwingend notwendig. 22 */ 23 Warning: "warning", 24 25 /* 26 * Eine Fehlerausgabe. Hier muss eine Korrektur erfolgen. 27 */ 28 Error: "error" 29} 30 31/** 32 * Die möglichen Arten der im JSON übergebenen Quellen der Nachricht. 33 */ 34const SourceTypes = { 35 /* 36 * Nachricht aus unbekannter Quelle. 37 */ 38 Unknown: "unknown", 39 40 /* 41 * Die Nachricht ist an einem Attribut aufgeschlagen. In diesem Fall wird versucht in der Oberfläche 42 * ein Eingabefeld zu finden, welches mit diesem Attribut verknüpft ist und bezüglich dessen Feldrahmen 43 * die Nachricht ausgegeben werden kann. 44 */ 45 Attribut: "attribut", 46 47 /* 48 * Die Nachricht ist von einer Anwendung ausgelöst worden. Es wird der Hashcode der entsprechenden Instanz 49 * übergeben und versucht die Nachricht unterhalb des div-Elements mit dem data-Attribut "data-instance-id" 50 * anzuzeigen. 51 */ 52 Anwendung: "anwendung" 53} 54 55/** 56 * Die möglichen Darstellungsformen der Nachrichten. 57 */ 58const HRMessageDisplayTypes = { 59 /* 60 * Nachrichten werden als p-Tags angelegt. Zur Verwendung dieser Darstellungsform muss über das jeweilige 61 * Konfigurationsobjekt zwingend ein parent-Element angegeben werden, unter welchem die Nachricht 62 * erzeugt werden soll. 63 */ 64 Inline: "inline", 65 66 /* 67 * Nachrichten werden als Toast-Notification angezeigt. 68 */ 69 Toast: "toast", 70 71 /* 72 * Nachrichten werden in einer i-Box angezeigt. 73 */ 74 IBox: "ibox", 75 76 /* 77 * Nachrichten werden im alert-Dialog des Browsers dargestellt. 78 */ 79 Alert: "alert", 80 81 /* 82 * Nachrichten werden als Tooltip angezeigt. Zur Verwendung dieser Darstellungsform muss über das jeweilige 83 * Konfigurationsobjekt zwingend ein parent-Element angegeben werden, an dem das Tooltip erzeugt werden soll. 84 */ 85 Tooltip: "tooltip" 86} 87 88var HRDefaultTextTypeError = { 89 RequiredCheckFailed: 'Bitte füllen Sie alle Pflichtfelder aus.' 90} 91 92/** 93 * Steuert das Anzeigen einer Nachricht. Die konkrete Darstellungsform (siehe HRMessageDisplayTypes) 94 */ 95class HRMessageHandler 96{ 97 /* 98 * Die Standard-Darstellungsform von Nachrichten. 99 */ 100 defaultDisplayType = HRMessageDisplayTypes.Inline; 101 102 /* 103 * Schalter für den Debug-Modus: Hier werden diverse Konsolenausgaben gemacht. 104 * Dazu einfach in der createDefaultHandler true übergeben. 105 */ 106 #debugMode = false; 107 108 /* 109 * Die Standard-Darstellungsform von Formularfeldern. 110 */ 111 defaultFieldDisplayType = HRMessageDisplayTypes.Inline; 112 113 /* 114 * Das HTML-Element, unter welches ohne Angabe einer parent-Konfiguraton die Nachrichten abgelegt werden. 115 */ 116 defaultParentElement; 117 118 /* 119 * In dieser Liste werden alle Nachrichten abgelegt. Ein Tupel besteht aus einer ID, die einem Hashcode bestehend aus diversen Nachrichteninfos entspricht, und 120 * einem Nachrichtenobjekt. Letzteres hängt vom Nachrichtenskin ab und kann z.B. eine Instanz der Toast-Lib sein oder das frisch erzeugte DOM-Element im Inline-Fall. 121 */ 122 messages = new Map(); 123 124 /* 125 * Verwaltende Instanz für die Darstellung von Toast-Notifications. 126 */ 127 #toastFactory; 128 129 /** 130 * Neuer Messagehandler. Es kann eine Standard-Darstellungsform übergeben werden. Eine Abweichung dieses Standards kann 131 * im konkreten Nachrichtenaufruf durch abweichende Angabe der displayType-Konfigurationseinstellung erreicht werden. 132 * 133 * @param displayType Zum Default abweichende Darstellungsform, siehe HRMessageDisplayTypes für alle Möglichkeiten. 134 * @param debugMode Schalter für den Debug-Modus: Hier werden diverse Konsolenausgaben gemacht. 135 */ 136 constructor(displayType, debugMode) 137 { 138 if (displayType) 139 this.defaultDisplayType = displayType; 140 141 this.#debugMode = debugMode; 142 } 143 144 /** 145 * Erzeugt den Default-Messagehandler unterhalb des window-Objektes. 146 * 147 * @param debugMode Im Developer-Modus werden diverse Debugging-Ausgaben auf der Konsole getätigt. 148 */ 149 static createDefaultHandler(debugMode) 150 { 151 if (!window.HRMessageHandler || !(window.HRMessageHandler instanceof HRMessageHandler)) 152 window.HRMessageHandler = new HRMessageHandler(null, debugMode); 153 } 154 155 /** 156 * Anzeigen einer Nachricht abhängig von der gewählten Darstellungsform. 157 * 158 * @param txt Die anzuzeigende Textnachricht. 159 * @param messageLevel Das Message-Level (info, warn, error). 160 * @param config [optional] Konfiguration für erweiterte Anzeigemöglichkeiten. 161 * <ul> 162 * <li>displayType: Zum Default abweichende Darstellungsform, siehe HRMessageDisplayTypes für alle Möglichkeiten. Default: null.</li> 163 * <li>parent: Ein Element oder ein Selektor, relativ zu dem die Nachricht angezeigt werden soll. Default: null. Wir zeigen die Nachricht also 164 * relativ zum window an.</li> 165 * <li>type: Über diese Angabe können mehrere Nachrichten einem gewisse
165n Nachrichtentyp zugehörig gruppiert werden (z.B. validation-error). 166 * Die Nachricht erhält dann ein data-Attribut "data-message-type" mit dem übergebenen Wert und eine entsprechende CSS-Klasse. Default: null.</li> 167 * <li>duration: Nach dieser Zeit in ms wird die Nachricht wieder ausgeblendet, sofern der displayType eine solche Eigenschaft unterstützt 168 * (Toasts etwa unterstützen die Eigenschaft, Alerts hingegen nicht). Default: 1000 [ms].</li> 169 * <li>processingFunction: Ist eine grundsätzlich andere Anzeige gewünscht, so kann hier eine abweichende verarbeitende Funktion hinterlegt werden. 170 * Diese hat eine höhere Priorität als die Default-Darstellungsform und die über dieses Konfigurationsobjekt gesetzte abweichende Darstellungsform. Default: null.</li> 171 * <li>className: Eine zusätzliche CSS-Klasse, die am p-Tag der Nachricht befestigt wird. Default: ''.</li> 172 * <li>id: Eine abweichende ID, die an dem p-Tag der Nachricht hinterlegt werden kann. Default: ''.</li> 173 * <li>markParentElement: Falls true, wird das parent-Element mit dem message-Level als Css-Klasse markiert. Default: false.</li> 174 * </ul> 175 */ 176 #showMessage(txt, messageLevel, config) 177 { 178 try 179 { 180 config = config || {}; 181 182 // Wurde eine vollkommen abweichende darstellende Funktion übergeben, so hat diese absoluten Vorrang. 183 if (config.processingFunction) 184 config.processingFunction(txt, config); 185 else 186 { 187 // Wurde keine alternative Darstellungsform übergeben, verwenden wir die Default-Darstellungsform. 188 let displayType = this.defaultDisplayType; 189 if (config.displayType && config.displayType.length > 0) 190 displayType = config.displayType; 191 192 // Wurde ein Default-Parent gesetzt übernehmen wir diesen. 193 if (!config.parent && this.defaultParentElement) 194 config.parent = this.defaultParentElement; 195 196 // Optionale Markierung am parent-Element vornehmen. 197 if (config.parent) 198 { 199 // Falls der Parent als Zeichenkette übergeben wurde, gehen wir davon aus, dass ein Selektor übergeben wurde. 200 if (typeof(config.parent) === 'string') 201 config.parent = document.querySelector(config.parent); 202 203 // Parent wird mit CSS-Klasse bestückt, Formularfelder wiederum werden an anderer Stelle behandelt. 204 if (config.markParentElement && !config.parent.hasAttribute('data-field-frame')) 205 { 206 if (messageLevel === HRMessageLevel.Error) 207 addCSSClass(config.parent, 'error-parent'); 208 else if (messageLevel === HRMessageLevel.Warning) 209 addCSSClass(config.parent, 'warning-parent'); 210 else 211 addCSSClass(config.parent, 'info-parent'); 212 } 213 } 214 215 if (txt && typeof(txt) === 'string') 216 { 217 // Nachricht abhängig vom Display-Type schicken. 218 if (displayType === HRMessageDisplayTypes.Inline) 219 this.#showMessageInline(txt, messageLevel, config); 220 else if (displayType === HRMessageDisplayTypes.Tooltip) 221 this.#showMessageTooltip(txt, messageLevel, config); 222 else if (displayType === HRMessageDisplayTypes.IBox && typeof(HROpenNoteInIBox) === 'function') 223 HROpenNoteInIBox(txt, messageLevel.toLowerCase()); 224 else if (displayType === HRMessageDisplayTypes.Alert) 225 alert(txt); 226 else 227 { 228 // Der Default ist das Anzeigen als Toast. 229 this.#showMessageToast(txt, messageLevel, false, config); 230 231 // Unbekannte Darstellungsform gewählt? 232 if (displayType !== HRMessageDisplayTypes.Toast && typeof(HRLogError) === 'function') 233 HRLogError(new Error('Die Darstellungsform "' + displayType + '" ist unbekannt.')); 234 } 235 236 // Zusätzliche typspezifische Fehlermeldung senden. Diese dient oft der Gruppierung mehrerer Fehler. 237 if (config.type && messageLevel === HRMessageLevel.Error && displayType !== HRMessageDisplayTypes.Toast) 238 { 239 if (config.type === 'RequiredCheckFailed') 240 { 241 // Falls es sich um einen typabhängigen Fehler handelt, soll man 242 // per Klick zu diesem Fehler scrollen können. Diese Meldung soll 243 // nur einmal pro Typ erzeugt werden. 244 this.#showMessageToast(HRDefaultTextTypeError.RequiredCheckFailed, HRMessageLevel.Error, true, { 245 closeOnClick: false, 246 scrollOnClick: `[data-message-type="${config.type.toLowerCase()}"]`, 247 messageType: config.type 248 }); 249 } 250 } 251 } 252 } 253 } 254 catch (e) 255 { 256 if (typeof(HRLogError) === 'function') 257 HRLogError(e); 258 } 259 } 260 261 /** 262 * Anzeigen einer Nachricht in der Inline-Darstellung. 263 * 264 * @see #showMessage(txt, messageLevel, config) 265 */ 266 #showMessageInline(txt, messageLevel, config) 267 { 268 // Ein parent-Element, an welches die Nachricht gehängt wird, ist Minimalanforderung. 269 if (config.parent) 270 { 271 // Einen Hashcode aus diversen Nachrichteninformationen ermitteln. Dieser soll die Nachricht eindeutig identifizieren. 272 const messageHashcode = this.hashCodeMessage(txt, messageLevel, config.type, config.parent); 273 if (config.parent.hasAttribute('data-field-frame')) // Nachricht an einem Formularfeld. 274 { 275 let messageContainerCssClassname; 276 if (messageLevel === HRMessageLevel.Error) 277 messageContainerCssClassname = 'formElementErrorTextHint'; 278 else if (messageLevel === HRMessageLevel.Warning) 279 messageContainerCssClassname = 'formElementWarnTextHint'; 280 else 281 messageContainerCssClassname = 'formElementInfoTextHint'; 282
283 // Zunächst, wenn nötig, das Container-Element für alle Nachrichten erzeugen. 284 let messageContainer = config.parent.getElementsByClassName(messageContainerCssClassname)[0]; 285 if (!messageContainer) 286 { 287 messageContainer = document.createElement('div'); 288 messageContainer.className = messageContainerCssClassname; 289 config.parent.appendChild(messageContainer); 290 } 291 292 // Die Nachricht selbst als Span in den Container legen. 293 const messageElement = document.createElement('span'); 294 messageElement.textContent = txt; 295 messageElement.className = messageLevel + (config.className ? ' ' + config.className : ''); 296 if (config.id) 297 messageElement.id = config.id; 298 if (config.type) 299 messageElement.dataset.messageType = config.type.trim().toLowerCase(); 300 messageElement.dataset.message = ''; 301 messageElement.dataset.messageId = messageHashcode; 302 messageContainer.appendChild(messageElement); 303 304 // Nachricht noch in die verwaltende Hashmap legen. 305 this.messages.set(messageHashcode, messageElement); 306 } 307 else // Allgemeine Nachricht unterhalb eines parent-HTML-Elements. 308 { 309 // Im allgemeinen ist das ein Container-Element nur für diese Nachrichten. 310 const messageElement = document.createElement('p'); 311 messageElement.innerHTML = txt; 312 if (config.id) 313 messageElement.id = config.id; 314 messageElement.className = messageLevel + (config.className ? ' ' + config.className : ''); 315 if (config.type) 316 messageElement.dataset.messageType = config.type.trim().toLowerCase(); 317 messageElement.dataset.message = ''; 318 config.parent.appendChild(messageElement); 319 320 // Nachricht noch in die verwaltende Hashmap legen. 321 this.messages.set(messageHashcode, messageElement); 322 } 323 } 324 else 325 { 326 if (typeof(HRLogError) === 'function') 327 HRLogError(new Error('Eine Inline-Darstellung ohne ein parent-Element, an welches man den Fehler hängen kann, wird nicht unterstützt.')); 328 } 329 } 330 331 /** 332 * Entfernt alle Nachrichten am Feld. 333 * 334 * @param field Das betreffende Formularfeld. 335 */ 336 removeFieldMessagesIfExist(field) 337 { 338 this.removeFieldErrorsIfExist(field); 339 this.removeFieldWarningsIfExist(field); 340 } 341 342 /** 343 * Entfernt alle Fehlermeldungen am Feld. 344 * 345 * @param field Das betreffende Formularfeld. 346 */ 347 removeFieldErrorsIfExist(field) 348 { 349 try 350 { 351 if (field && field.hasAttribute('data-error')) 352 { 353 if (field.parentElement && field.parentElement.hasAttribute('data-field-frame')) 354 removeCSSClass(field.parentElement, 'formElementError'); 355 356 const errorDiv = field.parentElement.getElementsByClassName("formElementErrorTextHint")[0]; 357 if (errorDiv) 358 { 359 // Fehler wieder aus der zentralen messages-Verwaltung austragen. 360 Array.from(errorDiv.querySelectorAll('.error')).forEach((error) => { 361 if (error.dataset.messageId && !this.messages.delete(parseInt(error.dataset.messageId)) && this.messages.length > 0 && typeof(HRLogError) === 'function') 362 HRLogError(new Error(`Fehler beim Entfernen einer Nachricht (message-id: ${parseInt(error.dataset.messageId)}). Message-Objekt konnte nicht in messages-Verwaltung gefunden werden.`)); 363 364 // Den Fehler am Feld selbst nun löschen. 365 error.remove(); 366 367 // Gibt es eine Nachricht, die Nachrichten des selben Typs (z.B. required-Verletzungen) gruppiert? 368 // Diese Nachricht muss dann auch noch entfernt werden. 369 this.messages.forEach((obj, id) => { 370 if (obj && typeof(HRToast) === 'function' && obj instanceof HRToast && error.hasAttribute('data-message-type') && obj.m_MessageType === error.dataset.messageType) // aktuell an dieser Stelle nur für Toasts implementiert, kann aber bel. erweitert werden. 371 { 372 // Wenn es keine weiteren Fehler mehr zu diesem Nachrichtentyp gibt, kann schließlich auch das gruppierende Toast geschlossen werden. 373 if (document.querySelectorAll(`.error[data-message-type="${obj.m_MessageType}"]`).length <= 0) 374 obj.close(); 375 } 376 }); 377 }); 378 379 // Schließlich noch das die Fehler gruppierende div entfernen. 380 errorDiv.remove(); 381 } 382 383 // Die data-Markierung noch entfernen. 384 field.removeAttribute('data-error'); 385 } 386 } 387 catch (e) 388 { 389 if (typeof(HRLogError) === 'function') 390 HRLogError(new Error('Fehler beim Entfernen einer Nachricht.')); 391 } 392 } 393 394 /** 395 * Entfernt alle Warnungen am Feld. 396 * 397 * @param field Das betreffende Formularfeld. 398 */ 399 removeFieldWarningsIfExist(field) 400 {
401 try 402 { 403 if (field && field.hasAttribute('data-warning')) 404 { 405 if (field.parentElement && field.parentElement.hasAttribute('data-field-frame')) 406 removeCSSClass(field.parentElement, 'formElementWarning'); 407 408 const warningDiv = field.parentElement.getElementsByClassName("formElementWarnTextHint")[0]; 409 if (warningDiv) 410 { 411 // Warnungen wieder aus der zentralen messages-Verwaltung austragen. 412 Array.from(warningDiv.querySelectorAll('.warning')).forEach((warning) => { 413 if (warning.dataset.messageId && !this.messages.delete(parseInt(warning.dataset.messageId)) && this.messages.length > 0 && typeof(HRLogError) === 'function') 414 HRLogError(new Error(`Fehler beim Entfernen einer Nachricht (message-id: ${parseInt(warning.dataset.messageId)}). Message-Objekt konnte nicht in messages-Verwaltung gefunden werden.`)); 415 416 // Die Warnung am Feld nun selbst löschen. 417 warning.remove(); 418 419 // Gibt es eine Nachricht, die Nachrichten des selben Typs (z.B. required-Verletzungen) gruppiert? 420 // Diese Nachricht muss dann auch noch entfernt werden. 421 this.messages.forEach((obj, id) => { 422 if (obj && typeof(HRToast) === 'function' && obj instanceof HRToast && warning.hasAttribute('data-message-type') && obj.m_MessageType === warning.dataset.messageType) // aktuell an dieser Stelle nur für Toasts implementiert, kann aber bel. erweitert werden. 423 { 424 // Wenn es keine weiteren Fehler mehr zu diesem Nachrichtentyp gibt, kann schließlich auch das gruppierende Toast geschlossen werden. 425 if (document.querySelectorAll(`.warning[data-message-type="${obj.m_MessageType}"]`).length <= 0) 426 obj.close(); 427 } 428 }); 429 }); 430 431 // Schließlich noch das die Fehler gruppierende div entfernen. 432 warningDiv.remove(); 433 } 434 } 435 } 436 catch (e) 437 { 438 if (typeof(HRLogError) === 'function') 439 HRLogError(new Error('Fehler beim Entfernen einer Nachricht.')); 440 } 441 } 442 443 /** 444 * Anzeigen einer Nachricht in der Toast-Notification-Darstellung. 445 * 446 * @see #showMessage(txt, messageLevel, config) 447 * @param singleton Falls true, wird die Nachricht nur ein einziges mal ausgegeben. 448 */ 449 #showMessageToast(txt, messageLevel, singleton, config) 450 { 451 if (typeof(HRToastFactory) === 'function') 452 { 453 const messageHashcode = this.hashCodeMessage(txt, messageLevel, config.type, config.parent); 454 if (messageHashcode && (!this.messages || !singleton || !this.messages.get(messageHashcode))) 455 { 456 const _this = this; 457 458 // Wenn noch nicht geschehen eine Factory für das Toasting erzeugen. 459 if (!this.#toastFactory) 460 { 461 this.#toastFactory = new HRToastFactory({ 462 duration: 3750, 463 limit: 3, 464 progressbar: false, 465 position: { 466 x: 'right', 467 y: 'bottom' 468 }, 469 sortMode: HRToastContainerSortMode.logLevelPrioritized, 470 closeOnClick: config.closeOnClick, 471 parent: config.parent, 472 scrollOnClick: config.scrollOnClick, 473 messageType: (config.messageType ? config.messageType.toLowerCase() : ''), 474 }); 475 } 476 477 let toastConfig = { 478 className: config.type, 479 onShow: (toast) => { 480 toast.m_Toast.dataset.id = messageHashcode; 481 482 // Den Toast in die zentrale messages-Verwaltung eintragen. 483 _this.messages.set(messageHashcode, toast); 484 }, 485 onClose: (toast) => { 486 // Toast wieder aus der zentralen messages-Verwaltung austragen. 487 // Anmerkung: Sollte der messagehandler entfernt oder neu angelegt werden, bevor der zu löschende 488 // Toast abgeräumt wurde (z.B. bei einem Ajax-Reload der Seite), kann die messages-Map leer sein. 489 if (!_this.messages.delete(parseInt(messageHashcode)) && _this.messages.length > 0 && typeof(HRLogError) === 'function') 490 HRLogError(new Error(`Fehler beim Entfernen einer Nachricht (message-id: ${parseInt(messageHashcode)}). Message-Objekt konnte nicht in messages-Verwaltung gefunden werden.`)); 491 } 492 }; 493 494 // Toast ausgeben. Anmerkung: Das parent-Element wird hier nicht berücksichtigt. Ein Toast wird 495 // aktuell immer bzgl. der ganzen Seite ausgegeben. 496 let toast; 497 if (messageLevel === HRMessageLevel.Error) 498 {
499 toastConfig.duration = config.duration ? config.duration : -1, 500 toastConfig.closeOnClick = false; 501 this.#toastFactory.error(txt, toastConfig); 502 } 503 else if (messageLevel === HRMessageLevel.Warning) 504 { 505 toastConfig.duration = config.duration ? config.duration : -1, 506 this.#toastFactory.warn(txt, toastConfig); 507 } 508 else 509 { 510 toastConfig.duration = config.duration ? config.duration : 3750, 511 this.#toastFactory.info(txt, toastConfig); 512 } 513 } 514 } 515 } 516 517 /** 518 * Anzeigen einer Nachricht in der Tooltip-Notification-Darstellung. 519 * 520 * @see #showMessage(txt, messageLevel, config) 521 */ 522 #showMessageTooltip(txt, messageLevel, config) 523 { 524 if (config.parent) 525 { 526 const messageHashcode = this.hashCodeMessage(txt, messageLevel, config.type, config.parent); 527 if (messageHashcode) 528 { 529 let messageContainerCssClassname; 530 if (messageLevel === HRMessageLevel.Error) 531 messageContainerCssClassname = 'formElementErrorTextHint'; 532 else if (messageLevel === HRMessageLevel.Warning) 533 messageContainerCssClassname = 'formElementWarnTextHint'; 534 else 535 messageContainerCssClassname = 'formElementInfoTextHint'; 536 537 // Zunächst, wenn nötig, das Container-Element für alle Nachrichten erzeugen. 538 let messageContainer = config.parent.getElementsByClassName(messageContainerCssClassname)[0]; 539 if (!messageContainer) 540 { 541 messageContainer = document.createElement('span'); 542 messageContainer.className = `tooltip ${messageContainerCssClassname}`; 543 config.parent.appendChild(messageContainer); 544 } 545 546 // Die Nachricht selbst als Span in den Container legen. 547 const messageElement = document.createElement('span'); 548 messageElement.textContent = txt; 549 messageElement.className = `${messageLevel} data-content ${config.className ? ' ' + config.className : ''}`; 550 if (config.id) 551 messageElement.id = config.id; 552 if (config.type) 553 messageElement.dataset.messageType = config.type.trim().toLowerCase(); 554 messageElement.dataset.messageId = messageHashcode; 555 messageContainer.appendChild(messageElement); 556 557 // Nachricht noch in die verwaltende Hashmap legen. 558 this.messages.set(messageHashcode, messageElement); 559 } 560 } 561 else 562 { 563 if (typeof(HRLogError) === 'function') 564 HRLogError(new Error('Eine Tooltip-Darstellung ohne ein parent-Element, an welches man den Text hängen kann, wird nicht unterstützt.')); 565 } 566 } 567 568 /** 569 * Anzeigen einer informativen Nachricht. 570 * 571 * @param txt Die anzuzeigende Textnachricht. 572 * @param config [optional] Konfiguration für erweiterte Anzeigemöglichkeiten. Siehe Kommentar showMessage. 573 */ 574 info(txt, config) 575 { 576 this.#showMessage(txt, HRMessageLevel.Info, config); 577 } 578 579 /** 580 * Anzeigen einer Warnung. 581 * 582 * @param txt Die anzuzeigende Textnachricht. 583 * @param config [optional] Konfiguration für erweiterte Anzeigemöglichkeiten. Siehe Kommentar showMessage. 584 */ 585 warn(txt, config) 586 { 587 this.#showMessage(txt, HRMessageLevel.Warning, config); 588 } 589 590 /** 591 * Anzeigen einer Fehlermeldung. 592 * 593 * @param txt Die anzuzeigende Textnachricht. 594 * @param config [optional] Konfiguration für erweiterte Anzeigemöglichkeiten. Siehe Kommentar showMessage. 595 */ 596 error(txt, config) 597 { 598 this.#showMessage(txt, HRMessageLevel.Error, config); 599 } 600 601 /** 602 * Anzeigen einer Fehlermeldung am übergebenen Formularfeld. 603 * 604 * @see #showMessageAtField(name, txt, messageLevel, config) 605 */ 606 errorAtField(field, txt, config) 607 { 608 this.#showMessageAtField(field, txt, HRMessageLevel.Error, config); 609 } 610 611 /** 612 * Anzeigen einer Warnung am übergebenen Formularfeld. 613 * 614 * @see #showMessageAtField(name, txt, messageLevel, config) 615 */ 616 warnAtField(field, txt, config) 617 { 618 this.#showMessageAtField(field, txt, HRMessageLevel.Warning, config); 619 } 620 621 /** 622 * Anzeigen einer Information am übergebenen Formularfeld. 623 * 624 * @see #showMessageAtField(name, txt, messageLevel, config) 625 */ 626 infoAtField(field, txt, config) 627 { 628 this.#showMessageAtField(field, txt, HRMessageLevel.Info, config); 629 } 630 631 /** 632 * Anzeigen einer Nachricht am durch den übergebenen Namen festgelegten Formularfeld. 633 * 634 * @param field Der Attributname zum Eingabefeld oder das Eingabefeld selbst. 635 * @param txt Die anzuzeigende Textnachricht. 636 * @param messageLevel Das Message-Level (info, warn, error). 637 * @param config [optional] Konfiguration für erweiterte Anzeigemöglichkeiten. Siehe Kommentar showMessage. 638 * <ul> 639 * <li>formName: Ein abweichender Name des Formulars, in dem sich das Feld befindet.</li> 640 * </ul> 641 */ 642 #showMessageAtField(field, txt, messageLevel, config) 643 { 644 if (field) 645 { 646 config = config || {}; 647 648 let inputEl = field; 649 if (typeof(field) === 'string') 650 { 651 let form = document.forms.submit; 652 if (config.formName) 653 form = document.forms[formName]; 654 inputEl = form.elements[field]; 655 } 656 if (inputEl) 657 { 658 // Falls kein Formularfeldrahmen existiert, versuchen wir einen Feldrahmen um das 659 // Eingabefeld und ggf. assoziierte Label-Elemente zu legen. 660 if (inputEl.parentElement && !inputEl.parentElement.hasAttribute('data-field-frame')) 661 { 662 this.#createFeldrahmen(field); 663 664 // Damit der Missstand aber auch direkt im Template behoben wird, gibt es trotzdem noch eine Warnung. 665 if (this.#debugMode) 666 console.warn('Für Messagehandling an Formularfeldern muss ein Feldrahmen gesetzt sein. Bitte ergänzen Sie diesen noch:', field); 667 } 668 669 if (messageLevel === HRMessageLevel.Error) 670 { 671 inputEl.dataset.error = ""; 672 addCSSClass(inputEl.parentElement, 'formElementError'); 673 } 674 else if (messageLevel === HRMessageLevel.Warning) 675 { 676 inputEl.dataset.warning = ""; 677 addCSSClass(inputEl.parentElement, 'formElementWarning'); 678 } 679 else 680 addCSSClass(inputEl.parentElement, 'formelementInfo'); 681 682 config.parent = inputEl.parentElement; 683 684 // Wurde keine alternative Darstellungsform übergeben, verwenden wir die Default-Darstellungsform für Formularfeldausgaben. 685 if (config.displayType && config.displayType.length > 0) 686 config.displayType = config.displayType; 687 else 688 config.displayType = this.defaultFieldDisplayType; 689 690 this.#showMessage(txt, messageLevel, config); 691 } 692 } 693 else 694 { 695 if (this.#debugMode) 696 { 697 console.error('Fehler beim Ausgeben der Nachricht: "', txt.trim(), '"'); 698 console.error('Das Feld konnte nicht gefunden werden. Prüfen Sie bitte: (a) Wird das Formularfeld angezeigt? (b) Hat es ein data-Attribut data-instance-id? (c) Wurde der korrekte Feldname angegeben?'); 699 } 700 701 // Fallback: Die Nachricht als Toast ausgeben, damit wenigstens irgendeine Mitteilung an den Nutzer ausgegeben wird. 702 this.#showMessageToast(txt, messageLevel, false, config); 703 } 704 } 705 706 /** 707 * Erzeugt einen Feldrahmen um das übergebene Feld. Mit dem Feld assoziierte Label-Elemente werden versucht aufzuspüren und mit 708 * in den Rahmen verschoben. 709 * 710 * @param field Das Formularfeld, um welches ein Feldrahmen erzeugt werden soll. 711 */ 712 #createFeldrahmen(field) 713 { 714 if (field) 715 { 716 const feldrahmen = document.createElement('div'); 717 feldrahmen.id = `${field.id}_Rahmen`; 718 feldrahmen.className = 'Feldrahmen';
719 feldrahmen.dataset.fieldFrame = ''; 720 721 // Das Feld in den Feldrahmen stecken. 722 HRWrap(field, feldrahmen); 723 724 // Alle Label in den Feldrahmen schieben. 725 let beforeInput = true; 726 Array.from(feldrahmen.parentElement.children).forEach(child => { 727 if (child.tagName === 'LABEL' && (child.htmlFor == field.id || child.htmlFor == field.name)) 728 { 729 if (beforeInput) 730 field.before(child); 731 else 732 field.after(child); 733 } 734 else if (child === field) 735 beforeInput = false; 736 }); 737 } 738 } 739 740 /** 741 * Erzeugt einen Hashcode, der die Nachricht möglichst eindeutig kennzeichnet. 742 * 743 * Darf nicht privat gemacht werden, da es mind. von der toast.js aufgerufen wird. 744 * 745 * @param txt Der Text der Nachricht. 746 * @param messageLevel Das Message-Level (info, warn, error). 747 * @param type Der Typ. 748 * @param parent Das parent-Element der Nachricht. 749 * @return Ein Hashcode der Nachricht. 750 */ 751 hashCodeMessage(txt, messageLevel, type, parent) 752 { 753 if (txt && typeof(String.prototype.hashCode) === 'function') 754 { 755 let message = txt; 756 if (messageLevel) 757 message += messageLevel; 758 if (type) 759 message += type; 760 761 // Das parent-Objekt eindeutig zu identifizieren ist noch ein wenig 762 // holprig. In Zukunft müssen wir hier mal genauer überlegen wie man 763 // einen DOM-Knoten sinnvoll hasht. 764 if (parent) 765 { 766 if (typeof(parent) === 'string') 767 message += parent; 768 else 769 { 770 if (parent.id) 771 message += parent.id; 772 if (parent.name) 773 message += parent.name; 774 if (parent.type) 775 message += parent.type; 776 if (typeof(parent.hasAttribute) === 'function' && parent.hasAttribute('data-instance-id')) 777 message += parent.dataset.instanceId; 778 } 779 } 780 781 return message.hashCode(); 782 } 783 784 return ''; 785 } 786 787 /** 788 * Anzeigen einer Nachricht (Info, Warnung, Error) mit den Daten aus dem serverseitig erzeugten Message-JSON. 789 * 790 * @param jsonTxt Das serverseitig erzeugte JSON als Zeichenkette. 791 * @param messageLevel Das Message-Level (info, warn, error). 792 */ 793 #showMessageFromJSON(jsonTxt, messageLevel) 794 { 795 if (jsonTxt) 796 { 797 try 798 { 799 const json = JSON.parse(jsonTxt); 800 if (json) 801 { 802 // Kurze Verifizierung, dass das übergebene Message-Level überhaupt stimmt. 803 if (!json.messageLevel || json.messageLevel === messageLevel) 804 { 805 // Wurde eine zuordbare Quelle übergeben? 806 let parent; 807 if (json.source && json.sourceType && json.sourceType === SourceTypes.Anwendung) 808 parent = '[data-instance-id="' + json.source + '"]'; 809 810 const config = { 811 parent: parent, 812 type : json.type ? json.type : '' 813 }; 814 815 if (json.sourceType === SourceTypes.Attribut) 816 { 817 // Den Attributnamen über den Hashcode der Attribut-Instanz ermitteln. 818 // Achtung: Damit das funktioniert, muss an den Feldern das data-Attribut 819 // data-instance-id gesetzt sein, welches über die print-Aufrufe immer gesetzt wird. 820 let field = document.querySelector(`[data-instance-id="${json.source}"]`); 821 if (!field && this.#debugMode) 822 console.error(`Fehler beim Ausgeben einer Nachricht am Attribut mit dem Hashcode "${json.source}". Bitte prüfen, ob das zugehörige HTML-Eingabefeld ein data-Attribut "data-instance-id" hat.`); 823 this.#showMessageAtField(field, json.message, messageLevel, config); 824 } 825 else 826 this.#showMessage(json.message, messageLevel, config); 827 } 828 else 829 { 830 if (typeof(HRLogError) === 'function')
831 HRLogError(new Error('Das im JSON transportiert Message-Level "' + json.messageLevel + '" stimmt nicht mit dem Level der aufgerufenen Funktion überein.')); 832 } 833 } 834 } 835 catch (e) 836 { 837 if (typeof(HRLogError) === 'function') 838 HRLogError(e); 839 } 840 } 841 } 842 843 /** 844 * Anzeigen einer Information mit den Daten aus dem serverseitig erzeugten Message-JSON. 845 * 846 * @param jsonTxt Das serverseitig erzeugte JSON als Zeichenkette. 847 */ 848 infoFromJSON(jsonTxt) 849 { 850 this.#showMessageFromJSON(jsonTxt, HRMessageLevel.Info); 851 } 852 853 /** 854 * Anzeigen einer Warnung mit den Daten aus dem serverseitig erzeugten Message-JSON. 855 * 856 * @param jsonTxt Das serverseitig erzeugte JSON als Zeichenkette. 857 */ 858 warnFromJSON(jsonTxt) 859 { 860 this.#showMessageFromJSON(jsonTxt, HRMessageLevel.Warning); 861 } 862 863 /** 864 * Anzeigen einer Fehlermeldung mit den Daten aus dem serverseitig erzeugten Message-JSON. 865 * 866 * @param jsonTxt Das serverseitig erzeugte JSON als Zeichenkette. 867 */ 868 errorFromJSON(jsonTxt) 869 { 870 this.#showMessageFromJSON(jsonTxt, HRMessageLevel.Error); 871 } 872 873 /** 874 * Stellt die Default-Darstellungsform für neue Nachrichten auf den übergebenen Wert um. 875 * 876 * @param newDefaultDisplayType Die neue Default-Darstellungsform, siehe HRMessageDisplayTypes. 877 */ 878 setDefaultDisplayType(newDefaultDisplayType) 879 { 880 this.defaultDisplayType = newDefaultDisplayType; 881 } 882 883 /** 884 * Stellt die Default-Darstellungsform für neue Nachrichten an Formularfeldern auf den übergebenen Wert um. 885 * 886 * @param newDefaultFieldDisplayType Die neue Default-Darstellungsform für Nachrichten an Formularfeldern, siehe HRMessageDisplayTypes. 887 */ 888 setDefaultFieldDisplayType(newDefaultFieldDisplayType) 889 { 890 this.defaultFieldDisplayType = newDefaultFieldDisplayType; 891 } 892 893 /** 894 * Legt fest, unter welches HTML-Element defaultmäßig alle Nachrichten gelegt werden. 895 * 896 * @param newParent Das HTML-Element, wahlweise auch als Selektor. 897 */ 898 setParentDefault(newParent) 899 { 900 this.defaultParentElement = newParent; 901 } 902 903 /** 904 * Entfernt alle Toast-Benachrichtigungen. 905 */ 906 resetToasts() 907 { 908 if (this.#toastFactory) 909 { 910 // Alle Nachrichten schließen. 911 this.#toastFactory.closeAll(); 912 913 // Die Toast-Factory inklusive der Queue der noch ausstehenden Nachrichten resetten. 914 this.#toastFactory.destroy(); 915 } 916 } 917} 918 919/** 920 * Den Default-Messagehandler erzeugen. 921 */ 922HRMessageHandler.createDefaultHandler();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.