JSKid: modale Fenster

0 48

modale Fenster

  1. 0
  2. 0
    1. 0
      1. 0
        1. 0
          1. 0
          2. 0
          3. 0
      2. 0
  3. 4
    1. 0
    2. 1
      1. 1
        1. 1
          1. 0
            1. 0
              1. 0
          2. 0
            1. 0
      2. 0
        1. 0
          1. 0
            1. 0
    3. 1
      1. 0
        1. 0
          1. 0
            1. 0
              1. 0
                1. 0
                  1. 0
                    1. 1
    4. 0
      1. 0
      2. 0
        1. 1
          1. 0
            1. 0
          2. 0
            1. 0
    5. 0
      1. 0
        1. 0
          1. 1
            1. 0
              1. 0
              2. 1

Hallo zusammen

Modale Fenster mit JS: dazu gibt es zuhauf Einträge im Netz. Ich habe früher Visual Basic 6 programmiert und auch dort gibt es modale Fenster und zwar in dem Sinne

Die Form, die den modalen Dialog aufruft, übernimmt nach dem Klicken die Eintragungen des Benutzers im Dialog, wenn der Benutzer OK geklickt hat, oder verwirft sie beim Klicken auf die Abbrechen-Schaltfläche

Bei allem was ich im Internet gefunden habe, ist mir nicht klar geworden, ob Javascript den Programmfluss anhalten kann, wenn ein modales Fenster aufgerufen wurde und die dort gemachten Eintragungen dann später im Hauptfenster verarbeiten kann.

Also wenn mir jemand das einfach beantworten kann und am besten noch mit einem Beispiel verdeutlichen, dann wäre ich sehr dankbar dafür.

Viele Grüße

  1. Hallo JSKid,

    Bei allem was ich im Internet gefunden habe, ist mir nicht klar geworden, ob Javascript den Programmfluss anhalten kann, wenn ein modales Fenster aufgerufen wurde und die dort gemachten Eintragungen dann später im Hauptfenster verarbeiten kann.

    Es gibt window.confirm, window.alert und window.prompt, die genau das tun: den Programmfluss anhalten, bis der User seine Eingabe getätigt hat.

    Das vorausgesagt wirst du auf Probleme stoßen, wenn du das benutzt. Z.B. werden diese Dialogfenster im Chrome automatisch geschlossen (dismissed), wenn der Tab gewechselt wird. Wenn der Tab nicht aktiv ist, werden sie gar nicht erst angezeigt. Google und Mozilla raten explizit von der Verwendung ab.

    LG,
    CK

    Folgende Beiträge verweisen auf diesen Beitrag:

  2. Tach!

    Bei allem was ich im Internet gefunden habe, ist mir nicht klar geworden, ob Javascript den Programmfluss anhalten kann, wenn ein modales Fenster aufgerufen wurde und die dort gemachten Eintragungen dann später im Hauptfenster verarbeiten kann.

    Nein, kann es nicht. Javascript ist single-threaded und du musst den Thread wieder freigeben, damit der Nutzer den Dialog selbst bedienen kann. Es gibt dann irgendwann ein Event, wenn das Formular abgesendet wurde oder der OK/Cancel-Button aktiviert wurde oder Backdrop geklickt oder Esc gedrückt oder was auch immer. In diesem Event kann man das Ergebnis des Dialogs aufbereiten und ruft dann eine Aktion auf, die damit was tun soll. Somit ist aber das Aufrufen des Dialogs und das Abarbeiten des Ergebnisses an getrennten Stellen im Code. Besser ist, wenn der Dialog-Aufruf ein Callback übergibt (oder zwei: eins für OK, eins für Cancel) und oben genanntes Event dieses Callback aufruft. Und dann kommt man an die Stelle, an der der Einsatz eines Promise sinnvoll ist. Die sind für asynchrones Arbeiten geschaffen, und so ein Dialog arbeitet ja auch nach diesem Prinzip. Der OK-Button (und verwandte Bedienhandlungen) löst das Promise (resolve), Cancel und Co. weisen es zurück (reject).

    dedlfix.

    Folgende Beiträge verweisen auf diesen Beitrag:

    1. Ok, Vielen Dank für die Erlärung. Wie gesagt im Netz gibt es etliche Erklärungen und Beschreibungen wie modale Fenster erzeugt und benutzt werden. Das würde doch bedeuten, dass ein modales Fenster dann erst am Ende des Programm-Flußes angezeigt wird und lediglich eine Info beinhaltet.

      1. Hallo

        Ok, Vielen Dank für die Erlärung. Wie gesagt im Netz gibt es etliche Erklärungen und Beschreibungen wie modale Fenster erzeugt und benutzt werden.

        Könntest du bitte einmal erklären, was du unter einem „modalen Fenster“ verstehst‽

        In den Antworten ist nämlich von zwei verschiedenen Techniken die Rede.

        1. Die modalen Fenster, die JavaScript selbst bereitstellt. Das wäre window.confirm, window.alert und window.prompt. Die warten, wie Christian bereits beschrieb, aber ihre ebenfalls beschriebenen Tücken haben.
        2. Modale Dialoge, die mit HTML-Elementen in einem vorhandenen HTML-Dokument erzeugt werden, wie sie in diesem Forum zum Beispiel für das erzeugen eines Links in einem Posting benutzt werden. In diesem Fall trifft dedlfix' Beschreibung zu, die da sagt, bei einem Event diese Elemente zu erzeugen und einzublenden sowie zum Ende der Programmausführung auch neue Events festzulegen, die bei Aktionen innerhalb des modalen Dialogs ausgelöst werden. Wenn eines dieser neuen Events anschlägt, wird ein neues Programm/eine neue Funktion ausgeführt, um beispielsweise eine Formulareingabe zu prüfen, an den Server zu senden und den Dialog zu schließen.

        Das würde doch bedeuten, dass ein modales Fenster dann erst am Ende des Programm-Flußes angezeigt wird und lediglich eine Info beinhaltet.

        Oder eben interaktive Elemente, mit denen später etwas geschehen soll/kann.

        Tschö, Auge

        --
        Ein echtes Alchimistenlabor musste voll mit Glasgefäßen sein, die so aussahen, als wären sie beim öffentlichen Schluckaufwettbewerb der Glasbläsergilde entstanden.
        Hohle Köpfe von Terry Pratchett
        1. Ich denke ich spreche von der Technik unter 1.) Nur sind die drei genannten "Fenster" nicht sehr schön und ich hätte sie gerne mit einem eigenen Stil versehen und halt den Progamm-Fluß genauso unterbrechen, bis der User etwas eingibt.

          Aber so wie ich dedlfix verstanden habe ist das nicht möglich.

          1. Tach!

            Ich denke ich spreche von der Technik unter 1.) Nur sind die drei genannten "Fenster" nicht sehr schön und ich hätte sie gerne mit einem eigenen Stil versehen und halt den Progamm-Fluß genauso unterbrechen, bis der User etwas eingibt.

            Aber so wie ich dedlfix verstanden habe ist das nicht möglich.

            Richtig. confirm() und alert() halten das was im DOM passiert an. Der angezeigte Dialog findet unabhängig davon statt. Erst nach Bestätigung wird die DOM-Arbeit fortgesetzt.

            Ein selbst ersteller Dialog mit DOM-Elementen hingegen ist auf ein funktionierendes DOM angewiesen und kann dessen Tätigkeiten nicht anhalten, ohne selbst betroffen zu sein. Deswegen können Dialoge abseits von confirm() und alert() nicht synchron gestaltet werden.

            dedlfix.

          2. Hallo

            Ich denke ich spreche von der Technik unter 1.)

            Gut, also die JS-eigenen Dialoge mit der Gefahr, dass ihre Anzeige von den Browsern von vornherein unterdrückt wird.

            Nur sind die drei genannten "Fenster" nicht sehr schön und ich hätte sie gerne mit einem eigenen Stil versehen …

            Womit wir bei Technik Nr. 2, HTML-Elemente als Dialog zu erzeugen, wären.

            … und halt den Progamm-Fluß genauso unterbrechen, bis der User etwas eingibt.

            Ich weiß nicht, warum du dich so sehr auf die Unterbrechung des Programmflusses kaprizierst. Dass das bei Technik Nr. 2 nicht geht, ist klar. Dass diese Unterbrechnung unnötig ist und durch einen geteilten Programmablauf ersetzt wird, hat dedlfix lang und breit erklärt.

            Beispiel:

            <!DOCTYPE html>
            <html lang="de">
             <head>
              <meta charset="utf-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <!-- mehr Elemente -->
              <title>Dialogtest</title>
             </head>
             <body>
              <header>
               <h1>Dialogtest</h1>
               <nav id="mainmenu">
                <ul>
                 <!-- diverse Listenelemente -->
                 <li><a href="?dataset=search" class="indoc">Adressen suchen</a></li>
                </ul>
               </nav>
              </header>
              <main>
              <!-- diverse Elemente -->
              </main>
              <template id="template-search">
               <dialog id="popupform" class="entryform simple" open>
               <header class="titlebar">
                <h2>Suche von Adressen</h2>
               </header>
                <p>Teileingaben sind mit angehängtem Sternchen (*) möglich. Ohne Stern wird <em>exakt</em> nach dem eingegebenen Wert gesucht.</p>
                <form method="post" id="entryform">
                 <fieldset>
                  <legend>Suchparameter</legend>
                  <div>
                   <label for="id-strasse">Straße, Hausnummer</label>
                   <input type="text" id="id-strasse" name="fld-strasse">
                  </div>
                  <div>
                   <label for="id-plz">PLZ</label>
                   <input type="text" id="id-plz" name="fld-plz">
                  </div>
                  <div>
                   <label for="id-ort">Ort</label>
                   <input type="text" id="id-ort" name="fld-ort">
                  </div>
                 </fieldset>
                 <p>
                  <button name="btn-search" id="id-btn-search">Suche ausführen</button>
                  <button type="reset">Eingaben zurücksetzen</button>
                  <button type="button" id="close-popupform">Formular schließen</button>
                 </p>
                </form>
               </dialog>
               <div class="backdrop"></div>
              </template>
             </body>
            </html>
            

            In einem HTML-Dokument wird per Template ein Suchformular bereitgehalten. Per JS wird ein Eventhandler für den Link im Hauptmenü der Seite registriert …

            document.addEventListener('DOMContentLoaded', function() {
            	document.getElementById('mainmenu').addEventListener('click', runActionMainMenu, true);
            	document.getElementById('mainmenu').addEventListener('touchstart', runActionMainMenu, true);
            });
            

            … damit auf den Klick des Links reagiert werden kann, um den Dialog einzublenden.

            function runActionMainMenu(event) {
            	var c, i, em = null;
            	var elem = event.target;
            	if (elem.classList.contains('indoc')) {
            		var url = elem.getAttribute('href');
            		if (url == null) {
            			return null;
            		}
            		var urlTarget = gup('dataset', url); // gup: Hilfsfunktion, um an den Wert des URL-Parameters zu kommen
            		if (urlTarget == 'search') {
            			var em = document.querySelector('#template-search').content; // finde das richtige Template
            		}
            		if (em !== null) {
            			var clone = document.importNode(em, true); // importiere den Inhalt des Templates in die Variable clone
            			document.querySelector('main').appendChild(clone); // hänge den Inhalt des Templates ins DOM ein
            /**
             * Der Dialog wurde bereitgestellt, das Programm kann beendet werden, wenn …
             * … die Events zum absenden und schließen des Dialogs registriert wurden
             */
                  document.getElementById('entryform').addEventListener('submit', searchDatasets, true);
            			document.getElementById('close-popupform').addEventListener('click', closePopupForm, true);
            		} else {
            			console.log("kein zur Aktion (" + urlTarget + ") passendes Template gefunden.");
            		}
            	event.preventDefault(); // verhindere, dass dem angegebenen Linkziel gefolgt wird
            	}
            }
            

            In closePopupForm werden die Events zum schließen des Dialogs deregistriert und der Dialog aus dem DOM entfernt und in searchDatasets wird zudem zuvor per Ajax/Fetch die Suchanfrage an den Server gestellt. Und auch dort geht man nach dem selben Prinzip vor, eine Aufgabe zu erledigen, und zu definieren, was bei der irgendwannn auftretenden reaktion zu erfolgen hat (asynchrone Abarbeitung).

            function closePopupForm(event) {
            	var elem = event.target;
            	var dialog = document.getElementById('popupform');
            	var backdrop = document.getElementsByClassName('backdrop');
            	if (elem.id == 'close-popupform' || elem.id == 'close-popupform-icon') {
            		document.getElementById('close-popupform').removeEventListener('click', closePopupForm, true);
            		dialog.remove();
            		for (var i = 0; i < backdrop.length; i++) {
            			backdrop[i].remove();
            		}
            	}
            	return false;
            }
            

            Zu guter Letzt gehört der Dialog zum Rest der Seite passend per CSS formatiert.

            dialog {
                position: fixed;
                left: 0;
                right: 0;
                top: 2em;
                margin: auto;
                padding: 1rem;
                border: 1px solid #cbd4d8;
                border-radius: 0.25rem;
                background-color: #fff;
                display: block;
                z-index: 3;
            }
            dialog + .backdrop {
                position: fixed;
                top: 0;
                right: 0;
                bottom: 0;
                left: 0;
                background: rgba(0, 0, 0, 0.375);
                z-index: 2;
            }
            

            Du brauchst also keine Möglichkeit, dass das Programm auf etwas wartet und bis dahin keine anderen Aufgaben erfüllt, sondern nur eine Trennung der Aufgaben in Erzeugung des Dialogs und die Reaktionen auf die möglichen Eingaben.

            Das Ganze geht bestimmt auch eleganter und eine Bedienung des Formulars per Touch habe ich im Codebeispiel nicht berücksichtigt, aber zur Verdeutlichung des Prinzips sollte es, so hoffe ich, reichen.

            Tschö, Auge

            --
            Ein echtes Alchimistenlabor musste voll mit Glasgefäßen sein, die so aussahen, als wären sie beim öffentlichen Schluckaufwettbewerb der Glasbläsergilde entstanden.
            Hohle Köpfe von Terry Pratchett
          3. Liebe(r) JSKid,

            Aber so wie ich dedlfix verstanden habe ist das nicht möglich.

            richtig, wenn Du die window-Methoden alert, confirm und prompt verschönern willst, musst Du sie komplett mit anderen Mitteln nachbauen und dabei eine asynchrone Vorgehensweise einsetzen.

            Liebe Grüße,

            Felix Riesterer.

      2. Tach!

        Ok, Vielen Dank für die Erlärung. Wie gesagt im Netz gibt es etliche Erklärungen und Beschreibungen wie modale Fenster erzeugt und benutzt werden. Das würde doch bedeuten, dass ein modales Fenster dann erst am Ende des Programm-Flußes angezeigt wird und lediglich eine Info beinhaltet.

        Nein. Das modale Fenster ist erstmal nur eine Änderung des DOM, dergestalt dass die bisherigen Elemente der Seite überlagert und oft auch unbedienbar gemacht werden. Ansonsten verhalten sich die Dialog-Elemente nicht anders als andere HTML-Elemente, was die Bedienbarkeit und Interaktivität angeht. Nachdem diese Dialog-Elemente ins DOM eingebracht wurden, beendet man zunächst seine Arbeit (sprich: den Eventhandler, der zur Erstellung der Dialog-Elemente geführt hat) und der Programmfluss geht wieder an den Browser zurück, der sich wieder der Abarbeitung der Event Loop widmet oder idlet, wenn darin nichts zu finden ist. Es ist nun möglich, dass der Anwender Bedienhandlungen vornehmen kann, die der Browser wie üblich behandelt. Wenn es beispielsweise ein Klick auf den OK-Button ist, dann löst er dafür ein Event aus. Du kannst einen entsprechenden Eventhandler schreiben, der daraufhin die Elemente des Dialogs aus dem DOM entfernt, so dass die ursprünglichen Elemente wieder sichtbar und bedienbar werden. Der Eventhandler kann hier nun einfach enden, wenn nichts weiter zu tun ist. Oder aber, er ruft eine vorher definierte Funktion auf, die irgendwas macht, was auch immer getan werden soll. Das Aufrufen einer vordefinierten Funktion ist aber recht unflexibel, weswegen man generell lieber einen beim Erstellen übergebenen Callback aufruft, oder zu Mechanismen wie dem Promise greift.

        dedlfix.

  3. Liebe(r) JSKid,

    ich habe zum Thema modale Fenster und JavaScript einen Artikel erstellt: Eigene modale Dialogfenster. Dieser Artikel bietet zunächst einen Ersatz für alert, confirm und prompt an, wird aber in einer späteren Erweiterung komplexere eigene Dialoge präsentieren. Die dazu notwendigen Grundlagen und Voraussetzungen werden aber schon in der jetzigen Fassung erklärt und dargestellt.

    Liebe Grüße,

    Felix Riesterer.

    1. Servus!

      ich habe zum Thema modale Fenster und JavaScript einen Artikel erstellt: Eigene modale Dialogfenster.

      Den kannte ich noch gar nicht. Ach so: Niegel-nagelneu!

      Vielen Dank!

      Herzliche Grüße

      Matthias Scharwies

      --
      "I don’t make typos. I make new words."
    2. Hallo Felix,

      ich finde es gut, das du dich den Thema gewidmet hast. Ich habe den Artikel jetzt nur überflogen, aber dabei sind mir zwei Dinge aufgefallen:

      • Du verwendest Einbuchstaben-Variablen, z.B. "s" für den Anzeigestring. Ich mache das auch gerne, aber in einem Tutorial sollten die Variablennamen sprechend sein.

      • Du hast das Thema "Den Focus fangen" ausgeklammert. (Das habe ich bei meinen Warnfenstern auch gemacht.) Die Frage ist: sollten das in diesen Artikel mit rein, oder ist das so unwichtig, dass man da erst mal drauf verzichten kann?

      Gruß
      Jürgen

      1. Hallo JürgenB,

        • Du hast das Thema "Den Focus fangen" ausgeklammert. (Das habe ich bei meinen Warnfenstern auch gemacht.) Die Frage ist: sollten das in diesen Artikel mit rein, oder ist das so unwichtig, dass man da erst mal drauf verzichten kann?

        Focus-Management für Modals ist ein essentielles A11y-Feature. Auf die Behandlung sollte man auf keinen Fall verzichten.

        LG,
        CK

        1. Hallo Christian,

          dann sollte man Felix zumindestens mit Ideen versorgen.

          Meine spontane ungetestete Idee: auf das Blur-Event von der Modal-Box lauschen und dann den Focus zurückholen.

          Gruß
          Jürgen

          1. Hallo JürgenB,

            dann sollte man Felix zumindestens mit Ideen versorgen.

            Ja. Leider bin ich gerade mal wieder unterwegs, deshalb hatte/habe ich keine Zeit für eine ausführlichere Antwort… im Wesentlichen läuft es darauf hinaus, dass man bei aktivem Modal überwachen muss, dass der Fokus nicht auf ein Element ausserhalb des Modals fällt. Es gibt da ein paar Beispiel-Projekte, etwa das accessible modal dialog oder das a11y-dialog, ein Fork und eine Weiterentwicklung von dem Accessible Modal Dialog.

            Meine spontane ungetestete Idee: auf das Blur-Event von der Modal-Box lauschen und dann den Focus zurückholen.

            Weiss nicht recht, ob das reicht. Die beiden verlinkten Projekte machen schon etwas mehr. Ein Blick in deren Code lohnt sich.

            LG,
            CK

            1. Lieber Christian,

              im Wesentlichen läuft es darauf hinaus, dass man bei aktivem Modal überwachen muss, dass der Fokus nicht auf ein Element ausserhalb des Modals fällt. Es gibt da ein paar Beispiel-Projekte, etwa das accessible modal dialog oder das a11y-dialog, ein Fork und eine Weiterentwicklung von dem Accessible Modal Dialog.

              das sind doch prima Links um unter "Weblinks" oder "Siehe auch" hinzugefügt zu werden! Im Rahmen des Tutorials kann ich die Problematik nur aufzeigen, keinesfalls aber zur Zufriedenheit aller möglicher Profis vollumfänglich lösen. Daher mein Dank für diese Anstöße, ich werde sehen, was ich damit tue.

              Liebe Grüße,

              Felix Riesterer.

              1. Hallo Ingrid,

                das sind doch prima Links um unter "Weblinks" oder "Siehe auch" hinzugefügt zu werden!

                na wie gut, dass sie dort jetzt stehen.

                Liebe Grüße,

                Felix Riesterer.

          2. Lieber JürgenB,

            dann sollte man Felix zumindestens mit Ideen versorgen.

            dieser Dein Satz wärmt mein Herz! Sagt er doch in etwa: Wenn ihr kritisiert, dann bitte konstruktiv!

            Liebe Grüße,

            Felix Riesterer.

            1. Hallo Felix,

              dann sollte man Felix zumindestens mit Ideen versorgen.

              dieser Dein Satz wärmt mein Herz! Sagt er doch in etwa: Wenn ihr kritisiert, dann bitte konstruktiv!

              Du hast recht. Tut mir leid. Ich war, als ich das geschrieben habe, im Zug unterwegs und musste mein Zeug zusammenpacken, deshalb war das so kurz.

              LG,
              CK

      2. Lieber JürgenB,

        • Du verwendest Einbuchstaben-Variablen, z.B. "s" für den Anzeigestring. Ich mache das auch gerne, aber in einem Tutorial sollten die Variablennamen sprechend sein.

        wenn der Erklärtext direkt nach dem Code nicht genügt, werde ich s zu str erweitern.

        • Du hast das Thema "Den Focus fangen" ausgeklammert. (Das habe ich bei meinen Warnfenstern auch gemacht.) Die Frage ist: sollten das in diesen Artikel mit rein, oder ist das so unwichtig, dass man da erst mal drauf verzichten kann?

        Ich habe keine Ahnung wohin ich den Fokus nach dem Schließen des Dialogs setzen soll. Woher soll ich denn wissen, wo er davor war?

        Etwas anderes ist es, den Fokusverlust bei einem offenen Dialogfenster zu unterbinden bzw. reparieren. Wohin soll er denn als Default zurück geführt werden? Auf den Schließen-Button wie schon zu Anfang beim Anzeigen des Dialogs?

        Liebe Grüße,

        Felix Riesterer.

        1. Hallo Felix,

          ... werde ich s zu str erweitern."

          ich dachte da eher an „Anzeigetext“, „Callback_bei_OK“, …

          Ich habe keine Ahnung wohin ich den Fokus nach dem Schließen des Dialogs setzen soll. Woher soll ich denn wissen, wo er davor war?

          Idee: über das Focus-Event merken, wer den Focus hatte, bis das modale Fenster geöffnet wurde.

          Etwas anderes ist es, den Fokusverlust bei einem offenen Dialogfenster zu unterbinden bzw. reparieren. Wohin soll er denn als Default zurück geführt werden? Auf den Schließen-Button wie schon zu Anfang beim Anzeigen des Dialogs?

          bei Shift-Tab zum letzten fokussierbaren Element springen, sonst zum ersten; oder auch immer zum beim Öffnen fokussierten.

          Gruß
          Jürgen

          1. Tach!

            Ich habe keine Ahnung wohin ich den Fokus nach dem Schließen des Dialogs setzen soll. Woher soll ich denn wissen, wo er davor war?

            Idee: über das Focus-Event merken, wer den Focus hatte, bis das modale Fenster geöffnet wurde.

            Im Zweifelsfall ist das der Button, dessen Betätigung zum Öffnen des Dialogs geführt hat. Ob es sinnvoll ist, den Focus wieder dorthin zu setzen, ist vielleicht auch eine Frage des Anwendungsfalles.

            dedlfix.

            1. Hallo dedlfix,

              Ich habe keine Ahnung wohin ich den Fokus nach dem Schließen des Dialogs setzen soll. Woher soll ich denn wissen, wo er davor war?

              Idee: über das Focus-Event merken, wer den Focus hatte, bis das modale Fenster geöffnet wurde.

              Im Zweifelsfall ist das der Button, dessen Betätigung zum Öffnen des Dialogs geführt hat. Ob es sinnvoll ist, den Focus wieder dorthin zu setzen, ist vielleicht auch eine Frage des Anwendungsfalles.

              wenn das Öffnen auf eine Useraktion erfolgte, ja. Wenn das Fenster aber z.B. eine automatisch geöffnete Fehlermeldung ist, dann wird es schwieriger.

              Gruß
              Jürgen

    3. Tach!

      ich habe zum Thema modale Fenster und JavaScript einen Artikel erstellt: Eigene modale Dialogfenster. Dieser Artikel bietet zunächst einen Ersatz für alert, confirm und prompt an, wird aber in einer späteren Erweiterung komplexere eigene Dialoge präsentieren. Die dazu notwendigen Grundlagen und Voraussetzungen werden aber schon in der jetzigen Fassung erklärt und dargestellt.

      Sehr schön bis hier her, und nun zur Kritik. Ein alert() hat keine Unterscheidung zwischen OK und Abbrechen, es gibt nur die Lesebestätigung. Man sollte da also nur einen Handler angeben müssen/können.

      Als Fortsetzung bietet sich an, über Promises nachzudenken.

      dedlfix.

      1. Lieber dedlfix,

        Als Fortsetzung bietet sich an, über Promises nachzudenken.

        um das - für mich bisher nur dem Namen nach bekannte - Thema vorerst nicht anschneiden zu müssen, habe ich diesen ergänzenden Callback verwendet. Aber ja, das Thema liegt hier definitiv auf der Hand. Sobald ich mehr davon verstehe und wieder mehr Zeit habe, werde ich mir eine passende Ergänzung überlegen. Außerdem müsste man dann den Artikel auf mehrere Seiten auftrennen. Die Seite ist jetzt schon sehr lang...

        Liebe Grüße,

        Felix Riesterer.

        1. Tach!

          Als Fortsetzung bietet sich an, über Promises nachzudenken.

          Aber ja, das Thema liegt hier definitiv auf der Hand. Sobald ich mehr davon verstehe und wieder mehr Zeit habe, werde ich mir eine passende Ergänzung überlegen.

          Als Tipp für den Einstieg: Meiner Erfahrung nach sollte man sich dem Thema Promises von zwei Seiten her nähern. Zum einen als Verwender und das ist der einfache Teil. Die meisten werden nur mit so etwas in Berührung kommen:

          dialog.open(parameters)
            .then(result => ...)
            .catch(error => ...);
          

          Wenn man damit klarkommt (man kann das zum Beispiel mit der Fetch-API üben), kommt die andere Seite dran, ein Promise zu erstellen.

          function start () {
            return new Promise((resolve, reject) => {
              if (window.confirm('Ja/Nein')) {
                resolve(42);
              } else {
                reject(0);
              }
            });
          }
          
          start()
            .then(result => {
              console.log('Die Antwort ist', result);
            })
            .catch(error => {
              console.log('Abgebrochen');
            });
          

          Sieht auch noch easy aus, aber wir müssen ja die Steuerung unverrichteter Dinge zurückgeben und bekommen das Zepter nur aufgrund von Events oder ähnlichem wieder, um das Promise erfüllen oder zurückweisen zu können. Das müssen wir dann etwas schachteln, damit wir eine Closure bekommen, die und das resolve und reject festhält. Das sieht dem Prinzip nach so aus:

          function start () {
            return new Promise((resolve, reject) => {
              ok_button.addEventListener('click', () => {
                resolve(42);
              });
          
              cancel_button.addEventListener('click', () => {
                reject(0);
              });
          
              dialog.open();
            });
          }
          
          start()
            .then(result => {
              console.log('Die Antwort ist', result);
            })
            .catch(error => {
              console.log('Abgebrochen');
            });
          

          dedlfix.

          1. Lieber dedlfix,

            function start () {
              return new Promise((resolve, reject) => {
                ok_button.addEventListener('click', () => {
                  resolve(42);
                });
            
                cancel_button.addEventListener('click', () => {
                  reject(0);
                });
            
                dialog.open();
              });
            }
            
            start()
              .then(result => {
                console.log('Die Antwort ist', result);
              })
              .catch(error => {
                console.log('Abgebrochen');
              });
            

            AHA! Also muss die aufgerufene Fuktion ein Promise-Objekt zurückliefern. Diesem Promise-Objekt kann man mit der then-Methode eine (Callback-)Funktion für den resolve-Fall geben, mit der catch-Methode eine für den reject-Fall.

            Ist das so richtig? Wenn ja, dann sieht das schon ziemlich aus wie meine callBack-Sammlung, nur dass diese im Zweifel noch zwischen "cancel" und "abort" unterscheiden kann, also neben resolve und reject noch abort liefert. Ein Promise-Objekt kann das also nicht? Das fände ich dann für mein Tutorial unzureichend!

            Ich lese gerade nach und sehe, dass man mehrere then-Methodenaufrufe machen kann, und dass Promise-Objekte wesentlich komplexer sind (Status-Werte, innere und äußere Promises, Exceptions) als meine Callback-Lösung, die ich doch als recht elegant und für den vorliegenden Fall als bequem empfinde. Wenn man jetzt noch die Buttons frei benennen könnte, wäre sie sogar luxuriös, aber das will ich in diesem Tutorial nicht auch noch behandeln.

            Liebe Grüße,

            Felix Riesterer.

            1. Tach!

              AHA! Also muss die aufgerufene Fuktion ein Promise-Objekt zurückliefern. Diesem Promise-Objekt kann man mit der then-Methode eine (Callback-)Funktion für den resolve-Fall geben, mit der catch-Methode eine für den reject-Fall.

              Ist das so richtig?

              Ja.

              Wenn ja, dann sieht das schon ziemlich aus wie meine callBack-Sammlung, nur dass diese im Zweifel noch zwischen "cancel" und "abort" unterscheiden kann, also neben resolve und reject noch abort liefert. Ein Promise-Objekt kann das also nicht? Das fände ich dann für mein Tutorial unzureichend!

              Was ist der Unterschied zwischen Cancel und Abort? Für mich ist ein Dialog erfolgreich, wenn Daten zurückzugeben sind und abgewiesen (rejected), wenn der Nutzer ihn abgebrochen hat. Meist muss man auf das Abbrechen nicht reagieren, wenn doch, kann man den Fall catch()en. Wenn es wichtig ist, unterschiedliche Abbruch-Situationen unterscheiden zu können, kann man das zwar einerseits über den error-Wert tun. Aber sind das dann andererseits immer noch Abbrüche? Ich denke, dass das dann eher unterschiedliche Rückgabewerte für den Erfolgsfall sind (resolve). Der Rückgabewert kann durchaus komplexer natur sein, also zum Beispiel auch ein Objekt mit Status-Eigenschaft und Content-Eigenschaft. Also wenn der Dialog ein Eingabefeld hat und mehrere Buttons, dann käme die Information zum ausgelösten Button in die Status-Eigenschaft und der Eingabewert ist der Content.

              Ich lese gerade nach und sehe, dass man mehrere then-Methodenaufrufe machen kann, und dass Promise-Objekte wesentlich komplexer sind (Status-Werte, innere und äußere Promises, Exceptions) als meine Callback-Lösung, die ich doch als recht elegant und für den vorliegenden Fall als bequem empfinde. Wenn man jetzt noch die Buttons frei benennen könnte, wäre sie sogar luxuriös, aber das will ich in diesem Tutorial nicht auch noch behandeln.

              Wir wollen das nicht zu einem Tutorial über Promises machen. Es soll lediglich gezeigt werden, wie durch die Verwendung von Promises der Verwender seinen Code besser strukturieren kann. Statt herumliegender Callback-Funktionen, die man ansonsten für nichts anderes braucht, hängen diese direkt über then/catch am Dialog-Öffner. Zugegeben, man könnte die Callbacks auch direkt als Funktion im Aufruf von myAlert() etc. notieren, aber sowas macht die Geschichte nicht lesbarer.

              Schau dir mal an, wie ein Ajax-Request herkömmlich verglichen mit fetch() aussieht.

              var req = new XMLHttpRequest();
              req.addEventListener("load", machwas_mit_daten);
              req.open(url);
              req.send();
              
              //----
              
              fetch(url)
                .then(machwas_mit_daten);
              

              Der (hier gekürzte) fetch()-Aufruf liest sich auch gleich flüssiger. Statt einzelner Anweisungen ist das schon fast ein Satz. Vor allem kommt die Reihenfolge besser zur Geltung: erst den Request abarbeiten, dann mit den Daten arbeiten.

              Mehrere then() brauchst du für den Dialog eher nicht. Das ist kein switch-Ersatz, sondern kann man für nacheinander abzuarbeitende Tätigkeiten nutzen. Das nächste then() bekommt das Ergebnis des vorhergehenden reingereicht und nicht das Ergebnis des Promises. Der Verwender kann das nutzen, wenn er meint, in seinem Fall etwas verketten zu müssen. Du brauchst das nicht weiter zu berücksichtigen, wenn du das Promise für den Dialog erstellst.

              Eine Verbesserung könnte sein: In deinem Code prüfst du, ob die Callbacks Funktionen sind, bevor du sie aufrufst. Das kann beim Promise wegfallen, weil der Callback im then() angegeben wird. Wenn der Verwender da was Komisches angibt, dann ignoriert das Promise das einfach, ohne einen Fehler zu werfen (ist so definiert). Es ist ja sowieso sein Problem, was der Verwender mit dem Ergebnis anstellt, und wenn er damit Mist macht, muss du das nicht weiter beachten noch dem vorbeugen.

              dedlfix.

              1. Lieber dedlfix,

                Ist das so richtig?

                Ja.

                juhu! Etwas über Promise gelernt!

                Was ist der Unterschied zwischen Cancel und Abort?

                Frage: Sind Sie ein guter Mensch?

                |              [X]|  
                |   [Ja] [Nein]   |
                

                Hier sehe ich drei Möglichkeiten. Das "Nein" entspricht dem "Abbrechen". Natürlich ist von der natürlich Sprache ein "Abbrechen" mit einem "Schließen" gleich zu setzen. Wenn nun aber die beiden Buttons als Ja/Nein-Frage benutzt werden, ist ein "nein" nicht automatisch auch ein "abgebrochen"...

                Wir wollen das nicht zu einem Tutorial über Promises machen.

                Einverstanden. Der Denkanstoß ist angekommen. Danke dafür.

                Es soll lediglich gezeigt werden, wie durch die Verwendung von Promises der Verwender seinen Code besser strukturieren kann.

                Es sei denn, er baut sich etwas ähnliches, das das auch leistet.

                Eine Verbesserung könnte sein: In deinem Code prüfst du, ob die Callbacks Funktionen sind, bevor du sie aufrufst.

                Du meinst also anstelle von

                dialog.setCallback = function (key, f) {
                  callBacks[key] = f;
                };
                

                sollte da besser stehen:

                dialog.setCallback = function (key, f) {
                  if (typeof f == "function") {
                    callBacks[key] = f;
                  }
                };
                

                Ja, das könnte ich noch aufnehmen. Aber wer da absichtlich Müll hineinschreibt, dem ist ohnehin nicht zu helfen.

                Liebe Grüße,

                Felix Riesterer.

                1. Tach!

                  Was ist der Unterschied zwischen Cancel und Abort?

                  Frage: Sind Sie ein guter Mensch?

                  |              [X]|  
                  |   [Ja] [Nein]   |
                  

                  Hier sehe ich drei Möglichkeiten. Das "Nein" entspricht dem "Abbrechen". Natürlich ist von der natürlich Sprache ein "Abbrechen" mit einem "Schließen" gleich zu setzen. Wenn nun aber die beiden Buttons als Ja/Nein-Frage benutzt werden, ist ein "nein" nicht automatisch auch ein "abgebrochen"...

                  Auch ein Ja schließt den Dialog. Somit kann er durch jede der drei Möglichkeiten geschlossen werden. Das ist also nicht das Kriterium, das zur Entscheidung zwischen resolve und reject führen kann. In dem Fall sehe ich Ja und Nein als gültige Antworten, die resolve() aufrufen sollten. Das Ja gibt in dem Fall dem resolve() dann ein true mit auf den Weg, das Nein ein false. Der X-Button oder Esc oder Klick auf den Backdrop sind keine gültigen Antworten und landen auf reject().

                  Wir wollen das nicht zu einem Tutorial über Promises machen.

                  Einverstanden. Der Denkanstoß ist angekommen. Danke dafür.

                  Es soll lediglich gezeigt werden, wie durch die Verwendung von Promises der Verwender seinen Code besser strukturieren kann.

                  Es sei denn, er baut sich etwas ähnliches, das das auch leistet.

                  Eine Verbesserung könnte sein: In deinem Code prüfst du, ob die Callbacks Funktionen sind, bevor du sie aufrufst.

                  Du meinst also anstelle von

                  dialog.setCallback = function (key, f) {
                    callBacks[key] = f;
                  };
                  

                  sollte da besser stehen:

                  dialog.setCallback = function (key, f) {
                    if (typeof f == "function") {
                      callBacks[key] = f;
                    }
                  };
                  

                  Nein, ich meinte anstelle von

                  if (typeof OK == "function") {
                        dialog.setCallback("ok", function () {
                          OK();
                          dialog.setCallback("ok", function () {});
                        });
                      }
                  

                  braucht es nur

                  dialog.setCallback("ok", function () {
                    resolve();
                  });
                  
                  // in dem Fall auch noch kürzer
                  // dialog.setCallback("ok", resolve);
                  

                  Abgesehen davon kann man sich wohl auch im obigen Code das Setzen des Callbacks auf eine Funktion sparen. Zum einen wäre null kürzer und du testest im Dialog-Polyfill sowieso dass der Callback eine Funktion ist. Zum anderen dürfte es auch egal sein, ob da nach dem OK noch irgendwas dem Callback zugewiesen wird, weil eh kein HTML-Element mehr da/sichtbar ist, dass zu einem Aufruf führen könnte.

                  dedlfix.

                  1. Lieber dedlfix,

                    In dem Fall sehe ich Ja und Nein als gültige Antworten, die resolve() aufrufen sollten. Das Ja gibt in dem Fall dem resolve() dann ein true mit auf den Weg, das Nein ein false. Der X-Button oder Esc oder Klick auf den Backdrop sind keine gültigen Antworten und landen auf reject().

                    verstehe. Überzeugt mich. :-)

                    Zum anderen dürfte es auch egal sein, ob da nach dem OK noch irgendwas dem Callback zugewiesen wird, weil eh kein HTML-Element mehr da/sichtbar ist, dass zu einem Aufruf führen könnte.

                    Das habe ich mir auch schon gedacht. Naja, weg kann es immer noch.

                    Liebe Grüße,

                    Felix Riesterer.

                    1. Hallo Ingrid,

                      ich habe die Vorschläge ins Tutorial und in die Live-Beispiele eingearbeitet.

                      Liebe Grüße,

                      Felix Riesterer.

    4. Hallo Felix,

      ich habe zum Thema modale Fenster und JavaScript einen Artikel erstellt: Eigene modale Dialogfenster.

      ich habe den Artikel nur überflogen, ich glaube man kann ihn etwas vereinfachen, indem man mit der Methode HtmlDialogElement.showModal() arbeitet. Ich vermute, dass Browser dann auch das Fokus-Handling übernehmen, hab es aber nicht ausprobiert. Leider ist die Browser-Unterstützung noch unbefriedigend, aber ich glaube da wäre dem Artikel mit einem Hinweis auf einen Polyfill mehr gedient als mit einem handgestrickten Workaround.

      Viele Grüße

      1. Hallo,

        ich habe gerade mal dieses Beispiel auch im Chrome ausprobiert, da wird der Fokus auch nicht gefangen.

        Gruß
        Jürgen

      2. Lieber 1unitedpower,

        ich glaube man kann ihn etwas vereinfachen,

        denke daran, dass die Zielgruppe für das Tutorial zunächst verinnerlichen muss, dass man hier nicht mehr linear programmiert. Das ist das eigentliche Kernthema. Die Sache mit den Dialog-Boxen ist in meinen Augen Semantik-Wichserei mit a11y-Religion. Hat sicherlich beste Gründe, warum man das so machen wollen können sollte, aber vor zehn Jahren hat man sich auch schon zu behelfen gewusst: Mit simplen <div>-Elementen und gleichermaßen passendem CSS - JavaScript natürlich inklusive. Insofern ist an diesem Artikel eigentlich nichts wirklich neues dabei, außer das Gehampel mit Polyfill für schönes <dialog>-Benutzen.

        indem man mit der Methode HtmlDialogElement.showModal() arbeitet. Ich vermute, dass Browser dann auch das Fokus-Handling übernehmen, hab es aber nicht ausprobiert. Leider ist die Browser-Unterstützung noch unbefriedigend,

        Das nenne ich nicht "konstruktive Kritik"! Erst etwas bemängeln, dann zugeben, dass Du es nicht ausprobiert hast und dann noch obendrein zugeben, dass das aufgrund der aktuellen Unterstützung durch die Browser ohnehin noch Zukunftsmusik ist. Nee, konstruktiv geht definitiv anders!

        aber ich glaube da wäre dem Artikel mit einem Hinweis auf einen Polyfill mehr gedient als mit einem handgestrickten Workaround.

        Du darfst gerne den Artikel in Deinem Sinne verbessern. Nimm Dir einfach dazu die notwendige Zeit. Dann kannst Du vorführen, wie es besser geht.

        Auf der anderen Seite musst Du vielleicht überlesen haben, dass ich auf den Wiki-Artikel mit den zugänglichen Dialog-Boxen mehrfach hinweise, in dem die Sache mit der momentanen Notwendigkeit eines Polyfills ja hinlänglich besprochen wird. Dass ich einen "Workaround" gebastelt hätte, verbitte ich mir. Ich spreche konkret von einem Polyfill. Wenn der einmal nicht mehr notwendig sein sollte, kann man alles, was mit dem von mir zur Verfügung gestellten Polyfill nicht mehr gebraucht wird, wegkürzen, um nur noch das Handling mit den Callbacks übrig zu lassen. Darum kommt man so oder so nicht herum - naja, vielleicht doch mit den von @dedlfix schon erwähnten Promises. Aber darauf bist Du hier ja nicht eingegangen.

        Ich erinnere Dich hiermit in aller Deutlichkeit an Deine Kritik an Jörgs Login-System-Tutorial, in der Du auch sehr vage und könnte/müsste/würde formuliert hast. Das macht Deine Kritik wertlos, da sie nicht konkret und damit nicht mehr konstruktiv ist. Auch wenn Du ein in der Sache begründetes Teilanliegen haben magst, entwertest Du durch solche Formulierungsweise in Deiner Kritik das Engagement anderer. Das hat kein Wiki-Autor verdient!

        Wenn Du Dir zur Zeit keine Freizeit abringen kannst, um Wiki-Artikel zu bearbeiten/verbessern oder sogar neue zu schreiben, dann überlege Dir, ob es einen Sinn hat, Deine Zeit in herabwürdigende Kritik zu investieren. Es mag vielleicht sein, dass Du auch hierfür möglicherweise doch keine Zeit hast...

        Liebe Grüße,

        Felix Riesterer.

        1. Das nenne ich nicht "konstruktive Kritik"! Erst etwas bemängeln, dann zugeben, dass Du es nicht ausprobiert hast und dann noch obendrein zugeben, dass das aufgrund der aktuellen Unterstützung durch die Browser ohnehin noch Zukunftsmusik ist. Nee, konstruktiv geht definitiv anders!

          Du hast Recht, ich hätte mich intensiver mit deinem Aritkel beschäftigen müssen und bin dir eine ausführlichere Antwort schuldig. Dass man mit einer Kritik auch ihre Limitierungen benennt, halte ich grundsätzlich für guten Stil, aber nichtsdestotrotz ist meine Kritik zu kurz ausgefallen. Dafür möchte ich mich zunächst bei dir entschuldigen.

          Ich habe den Test von showModal nachgeholt und bei mir wird der Fokus in dem Dialog gefangen. @JürgenB hat bei seinem Test im Nachbar-Thread allerdings etwas anderes beobachtet, dafür habe ich zur Zeit keine Erklärung. Möglicherweise ist das ein Versionssunterschudied, ich habe mit Chrome 74 getestet.

          Auf der anderen Seite musst Du vielleicht überlesen haben, dass ich auf den Wiki-Artikel mit den zugänglichen Dialog-Boxen mehrfach hinweise, in dem die Sache mit der momentanen Notwendigkeit unserer Browser eines Polyfills ja hinlänglich besprochen wird.

          Auch damit bist du im Recht.

          Dass ich einen "Workaround" gebastelt hätte, verbitte ich mir. Ich spreche konkret von einem Polyfill.

          Das war von mir nicht abschätzig gemeint. Workarounds sind ein notwendiges Übel, in deren Entwicklung viel Know-How fließen muss. Polyfills sind auch Workarounds, die dazu dienen (noch) nicht nativ implementierte Features nachzurüsten. Ich hätte das Adjektiv "handgestrickt" aber besser mit einem wertschätzenderen Begriff ausgetauscht.

          Wenn der einmal nicht mehr notwendig sein sollte, kann man alles, was mit dem von mir zur Verfügung gestellten Polyfill nicht mehr gebraucht wird, wegkürzen, um nur noch das Handling mit den Callbacks übrig zu lassen.

          Vorschlag zur Güte: Du hast ja selber vorgeschlagen den Artikel aufzuteilen, ich glaube das Polyfill wäre eine sinnvolle Schnittmarke und könnte in einem eigenen Artikel untergebracht werden.

          Außerdem möchte ich vorschlagen, statt setCallback das native Event-System mit addEventListener zu benutzen. Die Spec definiert für das dialog-Element zwei mögliche Events: close und cancel.

          Dafür müssten im Code des Polyfills die folgenden Zeilen gestrichen werden.

              dialog.setCallback = function (key, f) {
                callBacks[key] = f;
              };
          
              dialog.triggerCallback = function (key) {
                if (typeof callBacks[key] == "function") {
                  callBacks[key]();
                }
              };
          

          Und anstelle von dialog.triggerCallback("cancel") müsste der Aufruf wie folgt lauten dialog.dispatchEvent(new Event('cancel')). Ebenso für das open-Event.

          Auf der aufrufenden Seite, muss der Aufruf dialog.setCallback('cancel', fn) durch dialog.addEventListener('cancel', fn) getauscht werden.

          Ich erinnere Dich hiermit in aller Deutlichkeit an Deine Kritik an Jörgs Login-System-Tutorial, in der Du auch sehr vage und könnte/müsste/würde formuliert hast.

          Die Eskalation damals bedaure ich auch heute noch und habe, wie du ja auch weißt, Jörg dafür ebenfalls um Entschuldigung gebeten. Dass meine Kritik zu vage war, weise ich allerdings zurück. Ich habe mich regelmäßig an den Diskussionen um den Artikel beteiligt, dabei habe ich auf mehrere konkrete Sicherheitslücken hingewiesen. Zum Beispiel die Anfälligkeit für Session-Fixation-Angriffe, der Konflikt mit der Apache Implementierung von HTTP Basic Auth und die Verwendung eines Algorithmus für Pseudozufälle anstelle von kryptografisch sicheren Zufallsgeneratoren. Darüber hinaus habe ich mehrfach und ausführlich die schlechte Lesbarkeit des Codes und die Abwesenheit von Qualitätssicherungs-Maßnahmen bemängelt.

          1. Hallo,

            Ich habe den Test von showModal nachgeholt und bei mir wird der Fokus in dem Dialog gefangen. @JürgenB hat bei seinem Test im Nachbar-Thread allerdings etwas anderes beobachtet, dafür habe ich zur Zeit keine Erklärung. Möglicherweise ist das ein Versionssunterschudied, ich habe mit Chrome 74 getestet.

            ich habe den Test wiederholt:

            Browser: Chrome Version 74.0.3729.157 (Offizieller Build) (64-Bit) (Windows 10) Seite: https://wiki.selfhtml.org/extensions/Selfhtml/example.php/Beispiel:JS-Anw-accessible-dialog.html

            Wenn ich die Seite öffne und dann den Tabulator drücke, verlässt der Focus den Dialog.

            Gruß
            Jürgen

            1. ich habe den Test wiederholt:

              Browser: Chrome Version 74.0.3729.157 (Offizieller Build) (64-Bit) (Windows 10) Seite: https://wiki.selfhtml.org/extensions/Selfhtml/example.php/Beispiel:JS-Anw-accessible-dialog.html

              Wenn ich die Seite öffne und dann den Tabulator drücke, verlässt der Focus den Dialog.

              Aah, dann haben vermutlich aneinander vorbei geredet. Ich meinte, dass der Fokus gefangen wird, wenn der Dialog mit der Methode showModal geöffnet wird. In dem verlinkten Beispiel wird der Dialog manuell mit setAttribute('open','open') geöffnet, da wird der Fokus dann nicht gefangen.

          2. Lieber 1unitedpower,

            vielen Dank für die ausführliche Rückmeldung. Jetzt kann ich mit Deinen Kritikpunkten umgehen.

            Vorschlag zur Güte: Du hast ja selber vorgeschlagen den Artikel aufzuteilen, ich glaube das Polyfill wäre eine sinnvolle Schnittmarke und könnte in einem eigenen Artikel untergebracht werden.

            @Matthias Scharwies und ich arbeiten an einer solchen Aufteilung. Und ja, die Sache mit dem Polyfill verdient dabei eine ganze eigene Sektion.

            Außerdem möchte ich vorschlagen, statt setCallback das native Event-System mit addEventListener zu benutzen. Die Spec definiert für das dialog-Element zwei mögliche Events: close und cancel.

            Auf den ersten Blick klingt das überzeugend. Aber wenn ich etwas mehr darüber nachdenke, dann habe ich folgenden Zweifel:

            EventListener kann man an Elemente binden und wieder lösen. Wenn ich nun eine Funktion für das cancel-Event anbinde, muss ich die später wieder lösen, sonst wird sie bei Wiederbenutzung der Dialog-Box erneut ausgeführt, was ich ja nicht will, wenn diese Funktionalität dynamisch übertragen werden soll.

            Auf der aufrufenden Seite, muss der Aufruf dialog.setCallback('cancel', fn) durch dialog.addEventListener('cancel', fn) getauscht werden.

            Dann verwaltet also nicht meine setCallback-Methode, welche Funktion gerade an das cancel-Event gebunden ist, sondern das DOM selbst. Das ist unhandlicher, denn ich müsste ja nach der Ausführung des Events den EventListener wieder entfernen. So habe ich diesen einmal eingerichtet, aber was er jeweils tut, regelt meine Komfort-Funktion, die das einfach in einer lokalen Variable (Closure) verwaltet.

            Ich könnte mir also vorstellen, für die beiden Events close und cancel jeweils einen EventListener anzubinden, der meine Komfort-Funktion bedient, damit auf der einen Seite weiterhin das Handling der Callback-Funktionen so einfach geregelt werden kann, aber auf der anderen Seite die (geplanten) nativen Merkmale im Sinne eines Polyfills unterstützt werden.

            Dass meine Kritik [am Login-System] zu vage war, weise ich allerdings zurück. Ich habe mich regelmäßig an den Diskussionen um den Artikel beteiligt, dabei habe ich auf mehrere konkrete Sicherheitslücken hingewiesen.

            Ich erinnere mich nur noch an eine Sache wirklich konkret, nämlich an einen Vorwurf, dass die ganze Sache für eine Veröffentlichung im Wiki deshalb zu unsicher und daher löschungswürdig sei, weil es keine automatisierten Tests gäbe. In dieser vagen und absoluten Form habe ich das in Erinnerung. Mag sein, dass die mittlerweile sehr gelitten hat und sich im Nachhinein manche Dinge in die eine oder andere Richtung verklärt haben, aber genau hierin hat mich Deine hiesige Kritik an den damaligen Vorgang erinnert.

            Darüber hinaus habe ich mehrfach und ausführlich die schlechte Lesbarkeit des Codes und die Abwesenheit von Qualitätssicherungs-Maßnahmen bemängelt.

            Richtig. Aber ein reines Bemängeln der Abwesenheit von QA-Maßnahmen ist das eine. Die Schlussfolgerung, dass daraus prinzipiell eine Unsicherheit des Systems abzuleiten sei, die eine Löschung aus dem Wiki nach sich zieht, ist eine ganz andere Form von Kritik. Mir ist nicht bekannt, dass Du eine konkrete Sicherheitslücke damals hättest benennen können, sondern nur allgemein auf eben diese Abwesenheit von automatisierten Tests als Begründung verwiesen hattest.

            Gerade bei solchen sicherheitsrelevanten Dingen können automatisierte Tests ja auch nur das testen, was der Testersteller im Sinn hat. Für den Login-System-Artikel hätte es einen regelrechten Fuzzer gebraucht, von dem ich nicht einmal im Ansatz wüsste, wie man so etwas erstellt. Aus meiner Sicht heute ging Dein Bemängeln damals also sogar nicht weit genug (Test vs. Fuzzer). Aber deshalb im Umkehrschluss zu behaupten, dass ein Login-Script deswegen unsicher sei, und vor allem, dass deshalb eine Veröffentlichung im Wiki insgesamt schadhaft wäre, wage ich bis heute nicht.

            Liebe Grüße

            Felix Riesterer

            1. EventListener kann man an Elemente binden und wieder lösen. Wenn ich nun eine Funktion für das cancel-Event anbinde, muss ich die später wieder lösen, sonst wird sie bei Wiederbenutzung der Dialog-Box erneut ausgeführt, was ich ja nicht will, wenn diese Funktionalität dynamisch übertragen werden soll.

              Ich glaube, ich verstehe worauf du hinaus möchtest. Der Event-Handling-Mechanismus mit addEventListener macht den Fall aufwendiger einen Event-Handler auszutauschen, er macht es aber auch einfacherer einen Event-Handler hinzuzufügen. Traditionell unterstüzt das DOM daher auch zwei verschiedene Event-Handling-Mechanismen: Den klassischen mit oncancel und den modernen mit addEventListener (Beispiel). Deine Variante mit setCallback entspricht in etwa dem klassischen System.

              Ich könnte mir also vorstellen, für die beiden Events close und cancel jeweils einen EventListener anzubinden, der meine Komfort-Funktion bedient, damit auf der einen Seite weiterhin das Handling der Callback-Funktionen so einfach geregelt werden kann, aber auf der anderen Seite die (geplanten) nativen Merkmale im Sinne eines Polyfills unterstützt werden.

              Ja, das halte ich ebefalls für gut.

              Darüber hinaus habe ich mehrfach und ausführlich die schlechte Lesbarkeit des Codes und die Abwesenheit von Qualitätssicherungs-Maßnahmen bemängelt.

              Richtig. Aber ein reines Bemängeln der Abwesenheit von QA-Maßnahmen ist das eine. Die Schlussfolgerung, dass daraus prinzipiell eine Unsicherheit des Systems abzuleiten sei, die eine Löschung aus dem Wiki nach sich zieht, ist eine ganz andere Form von Kritik.

              Ich möchte mal versuchen von den absoluten Begrifflichkeiten "sicher" und "unsicher" wegzukommen. Grundsätzlich unterliegen Software-Lösungen Unsicherheiten (im Sinne des englischen Uncertainty, nicht im Sinne von Insecurity). Die Risiken, die mit solchen Ungewissheiten einhergehen, lassen sich durch vielfältige Maßnahmen minimieren. Je mehr und je sorgfältiger diese Risiko-Minimierung betrieben wird, desto höher ist der Konfidenz-Level, dass sich das System zuverlässig verhält. Im Umkehrschluss gilt, wenn man nur wenig Risiko-Minimierung betreibt, besteht eine hohe Ungewissheit bzgl. der Zuverlässigkeit des Systems.

              Jetzt gibt es mehrere Ebenen, auf denen man diese Risko-Mimimierung betreiben kann. Die erste Ebene betrifft den Entwickler persönlich, seine Qualifikation, seine Selbstreflexion, -kontrolle und Coding-Disziplin. Dieser Faktor ist von außen nur schwer mittelbar, es gibt aber Alarmsignale, z.B. wenn der Entwickler versucht sich durch überhöhte Statements, wie "mein Code ist noch nie gehackt worden" oder "mein System ist viel sicherer als Wordpress", Geltung zu verschaffen.

              Die zweite Ebene betrifft die Einbeziehung und die Kommunikation mit Dritten. Ein klassisches Medium für diese Kommunikation ist der Code-Review, davon habe ich ein paar zu dem genannten Artikel geschrieben. Die sind allerdings nicht gut aufgenommen worden, zum Teil habe ich das auch selbst zu zuverschulden, indem ich damals nicht die richtige Tonart gewählt habe. Das Gegenteil von gut ist "gut gemeint".

              Die dritte Ebene befasst sich mit technischen Maßnahmen zur Qualitätssicherung. Darunter fallen automatisierte Tests, statische Analyseverfahren und Programm-Verfikation. Um diese Verfahren zu ermöglichen, muss der Code aber bereits bestimmte Hygiene-Vorschriften erfüllen, die damals nicht gegeben waren. Ein Teil meiner Kritik damals bezog sich vor allem auch darauf, dass nicht einmal die Grundvoraussetzungen bestehen, um technsiche QS-Maßnahmen zu ergreifen unabhängig davon, ob sie denn nun wirklich ergriffen werden.

              Zusammenfassend, ist es uns also nicht gelungen den Konfidenz-Level in die Vertrauenswürdigkeit des Login-Systems zu erhöhen, und inhaltlich stelle ich mich deshalb auch heute wieder auf den Punkt, dass es eine Fahrlässigkeit gewesen wäre, den Artikel unserer Leserschaft weiter anzubieten.

              Mir ist nicht bekannt, dass Du eine konkrete Sicherheitslücke damals hättest benennen können, sondern nur allgemein auf eben diese Abwesenheit von automatisierten Tests als Begründung verwiesen hattest.

              Auf drei konkrete Sicherheitslücken haben ich ja in meinem vorherigen Posting verlinkt.

              Gerade bei solchen sicherheitsrelevanten Dingen können automatisierte Tests ja auch nur das testen, was der Testersteller im Sinn hat. Für den Login-System-Artikel hätte es einen regelrechten Fuzzer gebraucht, von dem ich nicht einmal im Ansatz wüsste, wie man so etwas erstellt. Aus meiner Sicht heute ging Dein Bemängeln damals also sogar nicht weit genug (Test vs. Fuzzer).

              Zum Beispiel mit eris, das ist eine Erweiterung für PHPUnit für generatives Testen.

    5. Hallo Ingrid,

      Dieser Artikel [...] wird aber in einer späteren Erweiterung komplexere eigene Dialoge präsentieren.

      [x] done

      Liebe Grüße,

      Felix Riesterer.

      1. Tach!

        [x] done

        Habs mir mal angeschaut. Dazu ein paar Anmerkungen.

        Die Variable p wird durch einen lokalen Scope überlagert.

                // jedes <ul> und <p> entfernen, außer <p class="button-row">
                Array.prototype.slice.call(
                    dialog.querySelectorAll("ul, p:not(.button-row)")
                ).forEach(function (p) {
                    p.parentNode.removeChild(p);
                });
        

        Ich würde das lokale p etwas sprechender (um)benennen, zum Beispiel node, denn es sind ja nicht nur ps zu entfernen . Zudem muss p nicht "global" in myDialog rumliegen sondern kann als const p = ... innerhalb der For-Schleife im dortigen lokalen Scope existieren. Der Wert wird nie verändert, weswegen sie als const deklariert werden kann. Ebenso geht es der Variable element und elements, die jeweils nur innerhalb eines eines Blocks existiert. Alle anderen var können auch zu const umgeschrieben werden, weil sich ihr Inhalt nicht ändert.

        Außerdem lässt sich obiger Code problemlos kürzen zu:

                dialog.querySelectorAll("ul, p:not(.button-row)")
                    .forEach(function (node) {
                        node.parentNode.removeChild(node);
                    });
        

        Die von querySelectorAll() zurückgegebenen NodeList ist non-live und braucht keine slice()-Kopie, um damit gefahrlos arbeiten zu können. Und obwohl sie kein Array ist, forEach() hat sie jedenfalls.

        Bei beiden for (prop in data) möchte das in lieber ein of sein, damit nur direkte Eigenschaften und keine eventuell aus dem Prototyp geerbten berücksichtigt werden. Das prop ist auch wieder nur lokal verwendet und kann als for (let prop of data) notiert werden, so dass man es ebenfalsl nicht "global" in myDialog braucht.

        Zudem meckert meine IDE an, man solle bei diesen Stellen den typsicheren Vergleich nehmen.

        if (data[prop].type && data[prop].type == "info")
        

        Aber das ist ziemlich egal. Ob der nun Vergleich fehlschlägt, weil irgendein x-beliebiger Wert auch nach Typumwandlung nicht gleich dem gegebenen String ist oder die Typen der Werte nicht stimmen, kommt aufs gleiche raus.

        Für das nachfolgende Aufrufbeispiel gilt ebenfalls das oben gesagte sinngemäß.

        Generell wäre noch anzumerken, dass die Individualität sehr enge Grenzen hat. Mit einigermaßen Aufwand werden ein paar wenige Elemente unterstützt. Eine freie Gestaltung des HTML-Inhalts ist so nicht möglich. Hier wäre eine Lösung mit <template> sowohl einfacher als auch flexibler. Als Bonus kann man das HTML des Templates mit allen Finessen des HTML-Editors erstellen. Zu lösen wäre dabei die Frage, wie mit Formularelementen umgegangen werden soll. Ich würde da grundlegend nur anbieten, dass beim OK ein übergebener Callback aufgerufen werden kann, wo der Verwender das in seinem Fall benötigte Datenauslesen tun kann. Der Callback sollte ein Objekt zurückgeben, das dann vom üblichen Dialog-Handling an den Aufrufer übergeben wird. - Man kann ja immer noch für den Komfort eine vordefinierte Callback-Funktion anbieten, die ein generisches Formulardaten-Auslesen bietet. Andererseits könnte das auch gleich ein Beispiel sein, wie dieser Callback auszusehen hat.

        dedlfix.

        1. Lieber dedlfix,

          Habs mir mal angeschaut. Dazu ein paar Anmerkungen.

          wunderbar! Vielen Dank!

          Die Variable p wird durch einen lokalen Scope überlagert. [...] Ich würde das lokale p etwas sprechender (um)benennen, zum Beispiel node, denn es sind ja nicht nur ps zu entfernen .

          Ja, das wäre sehr sinnvoll.

          Zudem muss p nicht "global" in myDialog rumliegen sondern kann als const p = ... innerhalb der For-Schleife im dortigen lokalen Scope existieren. Der Wert wird nie verändert, weswegen sie als const deklariert werden kann. Ebenso geht es der Variable element und elements, die jeweils nur innerhalb eines eines Blocks existiert. Alle anderen var können auch zu const umgeschrieben werden, weil sich ihr Inhalt nicht ändert.

          Dieses const kenne ich noch nicht so lange wie var. Beide sind für mich nur dazu da, eine Variable nicht als Eigenschaft von window, also als globale Variable zu definieren, sondern als lokale. Dabei ist mir das var ebenso lieb, wie das const, nur dass ich mich an letzteres noch nicht gewöhnt habe. Mir ist es völlig egal, ob man den Inhalt von const später mit einem anderen Datentyp befüllen kann, oder nicht - wegen mir bräuchte es das überhaupt nicht. Aber für ein Tutorial sollte man diese Unterscheidung durchaus nutzen.

          Die von querySelectorAll() zurückgegebenen NodeList ist non-live und braucht keine slice()-Kopie, um damit gefahrlos arbeiten zu können.

          Deswegen tue ich es auch nicht.

          Und obwohl sie kein Array ist, forEach() hat sie jedenfalls.

          In wirklich allen Browsern? Zuletzt meine ich mich zu erinnern, wollte es im IE nicht so recht. Ist das jetzt nicht mehr so?

          Bei beiden for (prop in data) möchte das in lieber ein of sein, damit nur direkte Eigenschaften und keine eventuell aus dem Prototyp geerbten berücksichtigt werden.

          Wieder so etwas neumodisches. grins Von mir aus darf es for..in oder auch for..of sein. Ich sehe jetzt nicht, wo prototypische Eigenschaften hoch kommen sollten. Aber man muss es ja nicht erst darauf anlegen.

          Das prop ist auch wieder nur lokal verwendet und kann als for (let prop of data) notiert werden, so dass man es ebenfalsl nicht "global" in myDialog braucht.

          Ich empfand das immer als "unsauber", weil eine so definierte Variable nicht am Anfang meines Programms steht. Aber der Scope innerhalb einer Schleife geht ja nicht nach außen, daher würde prop tatsächlich so nur innerhalb der Schleife existieren. Also besser.

          Zudem meckert meine IDE an, man solle bei diesen Stellen den typsicheren Vergleich nehmen.

          Du meinst ein Boolean true würde sonst auch akzeptiert? Kann das als Bezeichner einer Eigenschaft überhaupt sein?

          if (data[prop].type && data[prop].type == "info")
          

          Aber das ist ziemlich egal. Ob der nun Vergleich fehlschlägt, weil irgendein x-beliebiger Wert auch nach Typumwandlung nicht gleich dem gegebenen String ist oder die Typen der Werte nicht stimmen, kommt aufs gleiche raus.

          So betrachtet schon. Ich habe mir immer angewöhnt, auf das prinzipielle Vorhandensein zu prüfen. Daher die erste Bedingung. Sollte also jemand vergessen haben, dass das Objekt noch eine type-Eigenschaft benötigt, und würde die erste Bedingung fehlen, dann würde der Code mit einer Fehlermeldung in der Konsole abgebrochen. So aber wird das Objekt schlicht nicht verarbeitet und die Ausführung nicht unterbrochen. Ist ein silent fail an dieser Stelle wirklich schlechter, als eine echte Fehlermeldung in der Konsole?

          Wenn man das Ganze noch ordentlich weitertreibt, dann würde man die Datenobjekte nicht von Hand als Literal notieren, sondern mittels einer Klasse bauen lassen, die dafür Sorge trägt, dass alle notwendigen Eigenschaften auch vorhanden sind - und als default-Typ eben info verwendet.

          Generell wäre noch anzumerken, dass die Individualität sehr enge Grenzen hat. Mit einigermaßen Aufwand werden ein paar wenige Elemente unterstützt. Eine freie Gestaltung des HTML-Inhalts ist so nicht möglich. Hier wäre eine Lösung mit <template> sowohl einfacher als auch flexibler.

          Ja, wenn jemand damit umgehen kann. So bietet das Tutorial eine von vielen möglichen Ideen, die hoffentlich zu weiteren neuen und vielleicht sogar besseren Ideen führt.

          Als Bonus kann man das HTML des Templates mit allen Finessen des HTML-Editors erstellen.

          Der Phantasie sind eben keine Grenzen gesetzt. Man kann mit seiner Programmlogik alles Denkbare umsetzen.

          Zu lösen wäre dabei die Frage, wie mit Formularelementen umgegangen werden soll.

          Das darf jeder Entwickler für sich selbst entscheiden. Das Tutorial bietet wie ausdrücklich angemerkt nur eine von unendlich vielen Möglichkeiten das zu tun.

          Ich würde da grundlegend nur anbieten, dass beim OK ein übergebener Callback aufgerufen werden kann, wo der Verwender das in seinem Fall benötigte Datenauslesen tun kann.

          OK. Ich zeige gleich ein Beispiel mit einer möglichen Vorgehensweise, um ein konkretes Ergebnis vorführen zu können.

          Mal sehen, wann ich Deine Anregungen umsetzen kann. Jedenfalls noch einmal ein herzliches Danke dafür!

          Liebe Grüße

          Felix Riesterer

          1. Tach!

            Zudem muss p nicht "global" in myDialog rumliegen sondern kann als const p = ... innerhalb der For-Schleife im dortigen lokalen Scope existieren. Der Wert wird nie verändert, weswegen sie als const deklariert werden kann. Ebenso geht es der Variable element und elements, die jeweils nur innerhalb eines eines Blocks existiert. Alle anderen var können auch zu const umgeschrieben werden, weil sich ihr Inhalt nicht ändert.

            Dieses const kenne ich noch nicht so lange wie var.

            const/let gibt es ja auch noch nicht so lange. Aber laut CanIUse kann das sogar der IE11 in Grundzügen.

            Beide sind für mich nur dazu da, eine Variable nicht als Eigenschaft von window, also als globale Variable zu definieren, sondern als lokale.

            Es gibt noch den Unterschied im Scope-Verhalten, const/let gilt nur innerhalb der nächstgelegenen {}-Klammern, var innerhalb der gesamten Funktion.

            Mir ist es völlig egal, ob man den Inhalt von const später mit einem anderen Datentyp befüllen kann, oder nicht - wegen mir bräuchte es das überhaupt nicht.

            Vermutlich ist const für die Laufzeitumgebung interessant, dass es da besser optimieren kann als bei veränderbaren Variablen.

            Die von querySelectorAll() zurückgegebenen NodeList ist non-live und braucht keine slice()-Kopie, um damit gefahrlos arbeiten zu können.

            Deswegen tue ich es auch nicht.

            Und obwohl sie kein Array ist, forEach() hat sie jedenfalls.

            In wirklich allen Browsern? Zuletzt meine ich mich zu erinnern, wollte es im IE nicht so recht. Ist das jetzt nicht mehr so?

            Naja, keiner der IEs hat diese Methode an der NodeList.

            Das prop ist auch wieder nur lokal verwendet und kann als for (let prop of data) notiert werden, so dass man es ebenfalsl nicht "global" in myDialog braucht.

            Ich empfand das immer als "unsauber", weil eine so definierte Variable nicht am Anfang meines Programms steht. Aber der Scope innerhalb einer Schleife geht ja nicht nach außen, daher würde prop tatsächlich so nur innerhalb der Schleife existieren. Also besser.

            Ich empfinde es eher unübersichtlich, alles am Anfang aufzuführen, selbst wenn es nur lokal verwendet wird. Das ist ja seit let/const schon besser geworden.

            Um nicht nur beiden Vorlieben gerecht zu werden, sondern auch der Lesbarkeit zu dienen, könnte man den Code weiter in logische und kürzere Einheiten (lies Funktionen) aufbrechen. Damit werden die Teile einfacher zu verstehen, weil sie kürzere in sich abgeschlossene Einheiten bilden. Und auch der Hauptcode ist dann nicht mehr so lang.

            Wenn man es schafft, die Funktionen mit beschreibenden Namen zu versehen, hat man als Leser gleich noch einen Hinweis auf das, was die Funktion tun soll, ohne erst den Code verstehen zu müssen und auch ohne dass da ein Kommentar zur Erläuterung steht.

            Zudem meckert meine IDE an, man solle bei diesen Stellen den typsicheren Vergleich nehmen.

            Du meinst ein Boolean true würde sonst auch akzeptiert? Kann das als Bezeichner einer Eigenschaft überhaupt sein?

            Kann nicht, aber der Linter in meiner IDE ist nicht clever genug, um das zu erkennen. Der kennt nur die Crockford-Regel, alles was ein Vergleich sein soll, habe === zu nehmen, selbst wenn es überhaupt nicht nötig ist.

            Es gibt da eine Comparison Table für Javascript. Alle Strings, die nicht mit Zahlen beginnen oder Leerstrings sind, sind nur zu Strings mit gleichem Inhalt gleich. In der Tabelle sind "true" und "false" (die mit den Anführungszeichen) Vertreter dieser Sorte. Es ist also komplett egal, was da an Wert reinkommt, denn nichts davon ergibt true außer dem Wert "info" in diesem Fall. Deswegen ist das Gemecker vom Linter hier sinnlos. Man kann aber trotzdem === statt == notieren, wenn man mag.

            if (data[prop].type && data[prop].type == "info")
            

            Aber das ist ziemlich egal. Ob der nun Vergleich fehlschlägt, weil irgendein x-beliebiger Wert auch nach Typumwandlung nicht gleich dem gegebenen String ist oder die Typen der Werte nicht stimmen, kommt aufs gleiche raus.

            So betrachtet schon. Ich habe mir immer angewöhnt, auf das prinzipielle Vorhandensein zu prüfen. Daher die erste Bedingung.

            Diesen Teil hatte ich gar nicht beachtet, nur den Vergleich nach dem &&. Aber jetzt wo du es sagst, fällt mir auf, dass ich doch was daran auszusetzen habe. Prinzipiell ist so ein Testen auf Vorhandensein richtig und wichtig, aber man muss es richtig tun. Ein einfacher Zugriff auf eine nicht vorhandene Eigenschaft erzeugt noch keinen Fehler, sondern ergibt stillschweigend undefined zurück. Aber das Verketten und Durchgreifen misslingt mit Fehlermeldung, wenn zwischendrin eine Eigenschaft auf undefined läuft, weil undefined ein primitiver Wert ist, der keine Eigenschaften hat.

            let obj = {};
            let x = obj.nicht_da; // problemlos, x enthält undefined
            let x = obj.nicht_da.irgendwas; // Laufzeitfehler, undefined hat keine Eigenschaften
            

            Man muss also nur "vorn" alles testen, aber nicht die letzte Eigenschaft selbst.

            if (obj && obj.nicht_da && obj.nicht_da.irgendwas == 0)
            
            if (obj && obj.nicht_da && obj.nicht_da.irgendwas && obj.nicht_da.irgendwas == 0)
            

            Letzteres würde auch bereits false liefern, wenn der Wert in irgendwas zu false kompatibel ist. Also wenn er in dem Beispiel 0 wäre, würde die erste Zeile true ergeben, die zweite nicht.

            Um zu deinem Code zurückzukommen

            if (data[prop].type && data[prop].type == "info")
            

            Die erste Prüfung ist so nicht sinnvoll. Man müsste data[prop] prüfen. Wenn das auf undefined/null läuft, erzeugt der Zugriff auf .type eine Fehlermeldung und Script-Abbruch. Um undefined oder null zu ergeben, reicht es, wenn der Anwender die Eigenschaft mit dem Wert null oder undefined statt einem Objekt erstellt. Besser also wie folgt, aber es geht hier noch besser.

            if (data[prop] && data[prop].type == "info")
            

            Man muss das nun nicht bei jeder Eigenschaft einzeln notieren, sondern kann diese Prüfung einmal am Schleifenanfang vornehmen.

            for (const prop in data) {
                if (!data[prop])
                    continue;
            
                if (data[prop].type == "info") {
                    // ...
            

            Außerdem muss ich mich in einem Punkt revidieren. for...of geht nur mit iterierbaren Dingen wie Arrays. Objekte gehören ohne Spezialbehandlung nicht dazu. Das muss also weiterhin eine for...in-Schleife bleiben, damit das über die Objekteigenschaften laufen kann. Falls man die prototypische Vererbung verhindern möchte, muss man das über hasOwnProperty() prüfen und entsprechend verzweigen. Das kann man hier aber wohl weglassen.

            Wenn man das Ganze noch ordentlich weitertreibt, dann würde man die Datenobjekte nicht von Hand als Literal notieren, sondern mittels einer Klasse bauen lassen, die dafür Sorge trägt, dass alle notwendigen Eigenschaften auch vorhanden sind - und als default-Typ eben info verwendet.

            Jein, wenn es "ordentlich" sei sollte, müsste man dieses XY-Problem durch Verwendung von <template> vermeiden.

            Als Bonus kann man das HTML des Templates mit allen Finessen des HTML-Editors erstellen.

            Der Phantasie sind eben keine Grenzen gesetzt. Man kann mit seiner Programmlogik alles Denkbare umsetzen.

            Da hast du mich wohl missverstanden. Ich meinte, dass dein Ansatz über das Konfigurationsobjekt nicht nur bei der Gestaltung des Dialoginhalts einschränkt auf das was der dieses Objekt auswertende Code bietet, auch im Editor beim Erstellen des Objekts hat man keinerlei Hilfe durch Autovervollständigung und ähnliche Hilfsmittel. Man erstellt ein Objekt mit für die IDE unbekannte Struktur. Wenn man die Eigenschaften nicht richtig tippt, gibt es zur Laufzeit kein Ergebnis. Wenn man stattdessen den Dialoginhalt direkt als HTML notieren könnte, wäre die IDE in der Lage, Syntaxprüfungen und Vervollständigung anzubieten.

            dedlfix.

            1. Lieber dedlfix,

              const/let gibt es ja auch noch nicht so lange. Aber laut CanIUse kann das sogar der IE11 in Grundzügen.

              sind Grundzüge ausreichend, dass ein Tutorial-Leser, der das in seinem IE ausprobiert, damit auch nicht scheitert?

              Vermutlich ist const für die Laufzeitumgebung interessant, dass es da besser optimieren kann als bei veränderbaren Variablen.

              Wenn ein JS-Neuling den Code mit seinem IE ausprobiert und an const/let scheitert, wem ist denn dann damit geholfen, dass ich kein var benutzt habe?

              Naja, keiner der IEs hat diese Methode an der NodeList.

              Also bleibe ich besser noch bei meiner Array.prototype.slice.call-Weise, damit's auch beim noob im IE funzt.

              Wenn man es schafft, die Funktionen mit beschreibenden Namen zu versehen, hat man als Leser gleich noch einen Hinweis auf das, was die Funktion tun soll, ohne erst den Code verstehen zu müssen und auch ohne dass da ein Kommentar zur Erläuterung steht.

              Ist das in meinen Beispielen nicht gegeben?

              So betrachtet schon. Ich habe mir immer angewöhnt, auf das prinzipielle Vorhandensein zu prüfen. Daher die erste Bedingung.

              Diesen Teil hatte ich gar nicht beachtet, nur den Vergleich nach dem &&. Aber jetzt wo du es sagst, fällt mir auf, dass ich doch was daran auszusetzen habe.

              Also gut. Da data[prop] zwingend existiert (kann ein leerer Wert unter diesem Eigenschaftsnamen sein), kann ich gleich data[prop].type auf den Wert testen, den ich haben will (also info).

              Außerdem muss ich mich in einem Punkt revidieren. for...of geht nur mit iterierbaren Dingen wie Arrays. Objekte gehören ohne Spezialbehandlung nicht dazu. Das muss also weiterhin eine for...in-Schleife bleiben, damit das über die Objekteigenschaften laufen kann.

              Danke für diese erweiterte Klarstellung. Mich freut, dass ich meinen Code an dieser Stelle nun doch nicht anzupassen brauche.

              Falls man die prototypische Vererbung verhindern möchte, muss man das über hasOwnProperty() prüfen und entsprechend verzweigen. Das kann man hier aber wohl weglassen.

              Sehe ich auch so.

              Jein, wenn es "ordentlich" sei sollte, müsste man dieses XY-Problem durch Verwendung von <template> vermeiden.

              Aha... OK. Das überblicke ich jetzt gerade nicht, da ich mich noch nicht mit Templates befasst habe.

              Da hast du mich wohl missverstanden. Ich meinte, dass dein Ansatz über das Konfigurationsobjekt nicht nur bei der Gestaltung des Dialoginhalts einschränkt auf das was der dieses Objekt auswertende Code bietet, auch im Editor beim Erstellen des Objekts hat man keinerlei Hilfe durch Autovervollständigung und ähnliche Hilfsmittel.

              Meine Zielgruppe erwartet wohl solche Features in diesem Zusammenhang nicht. Und wenn, dann braucht sie mein Tutorial wahrscheinlich nicht mehr. Mein Ansatz geht auch nicht in die Richtung, dass die Dialoge wirklich alle Möglichkeiten ausreizen, sondern dass sie programmatisch erstellt werden können, um übliche Formulardaten erfassen zu können und so über confirm und prompt weit hinaus gehen. Wer noch mehr will, geht freilich anders vor!

              Man erstellt ein Objekt mit für die IDE unbekannte Struktur. Wenn man die Eigenschaften nicht richtig tippt, gibt es zur Laufzeit kein Ergebnis. Wenn man stattdessen den Dialoginhalt direkt als HTML notieren könnte, wäre die IDE in der Lage, Syntaxprüfungen und Vervollständigung anzubieten.

              Das will der Profi. Der Profi sucht aber nicht mehr unser Tutorial hier zum Lernen auf. Oder doch?

              Liebe Grüße

              Felix Riesterer

              1. Tach!

                const/let gibt es ja auch noch nicht so lange. Aber laut CanIUse kann das sogar der IE11 in Grundzügen.

                sind Grundzüge ausreichend, dass ein Tutorial-Leser, der das in seinem IE ausprobiert, damit auch nicht scheitert?

                Ist es wirklich so, das man davon ausgehen muss, dass nur der IE zur Verfügung steht und kein anderer Browser?

                Wenn man es schafft, die Funktionen mit beschreibenden Namen zu versehen, hat man als Leser gleich noch einen Hinweis auf das, was die Funktion tun soll, ohne erst den Code verstehen zu müssen und auch ohne dass da ein Kommentar zur Erläuterung steht.

                Ist das in meinen Beispielen nicht gegeben?

                Kommentare sind vorhanden. Aber das lindert nur zum Teil, dass die Funktion sehr lang ist. Man muss immer noch verstehen, was da wie zusammenspielt. Bei kleineren Funktionen, die über ihre Parameter und den Rückgabewert eine definierte Schnittstelle zur Außenwelt haben, ansonsten aber eigenständig arbeiten, kann man besser deren Details ausblenden, wenn diese für das Verstehen des großen Ganzen nicht weiter benötigt werden. Das ist quasi wie mit Fachbegriffen. Man lernt diese und was dahintersteht, und kann dann effizient zum eigentlichen Thema kommunizieren.

                Jein, wenn es "ordentlich" sei sollte, müsste man dieses XY-Problem durch Verwendung von <template> vermeiden.

                Aha... OK. Das überblicke ich jetzt gerade nicht, da ich mich noch nicht mit Templates befasst habe.

                Die Template-Variante würde die Codemenge sehr reduzieren und gleichzeitig mehr Freiheiten geben. Der Dialog definiert nur das Grundgerüst und die grundlegenden Funktionen die zum allgemeinen Handling (Öffnen und Schließen) notwendig sind. Dazu gehört auch, den Inhalt des angegebenen Templates in den Body des Dialogs zu kopieren. Das Template muss nicht einmal in einem <template> sitzen, auch ein verstecktes Div oder ähnlich für ältere Browser geht. Wobei für die ebenfalls das ihnen unbekannte <template> verwendet werden könnte, nur muss das ausgeblendet werden.

                Wegfallen kann bei diesem Ansatz, ein Konfigurationsobjekt zu erstellen, dessen Syntax man erst noch lernen muss, und selbiges parsen und zu DOM-Elementen umarbeiten zu müssen. Stattdessen erstellt man HTML und es kommt nur ein wenig Code zum Einsatz, der dieses lediglich in den Dialog kopiert.

                Da hast du mich wohl missverstanden. Ich meinte, dass dein Ansatz über das Konfigurationsobjekt nicht nur bei der Gestaltung des Dialoginhalts einschränkt auf das was der dieses Objekt auswertende Code bietet, auch im Editor beim Erstellen des Objekts hat man keinerlei Hilfe durch Autovervollständigung und ähnliche Hilfsmittel.

                Meine Zielgruppe erwartet wohl solche Features in diesem Zusammenhang nicht.

                Nun, selbst wenn sie keine ausgefeilte IDE zur Verfügung haben, so werden viele von ihnen zumindest einen Editor haben, der grundlegende Syntaxhervorhebung kann. Auch da hilft es, bekanntes HTML erstellen zu können statt ein unbekanntes Objekt.

                Aber was erwartet die Zielgruppe wirklich?

                Und wenn, dann braucht sie mein Tutorial wahrscheinlich nicht mehr.

                Nun ja, möchtest du zeigen, wie man ein Konfigurationsobjekt liest und wie man DOM-Elemente erstellt? Oder wie man die eingebauten vorgefertigten Dialoge durch eine eigene Variante mit ein paar mehr, jedoch relativ fest vorgegebenen Gestaltungselementen ersetzt? Oder möchtest du zeigen, wie man nicht nur die eingebauten vorgefertigten Dialoge wegbekommt, sondern gleich noch mit recht wenig Code-Aufwand Dialoge mit vollständig individuellen Inhalt bekommen kann?

                Mein Ansatz geht auch nicht in die Richtung, dass die Dialoge wirklich alle Möglichkeiten ausreizen, sondern dass sie programmatisch erstellt werden können, um übliche Formulardaten erfassen zu können und so über confirm und prompt weit hinaus gehen. Wer noch mehr will, geht freilich anders vor!

                Aber du treibst einen unnötig hohen Aufwand, den man bei gleichzeitig mehr Freiheit deutlich reduzieren kann.

                Man erstellt ein Objekt mit für die IDE unbekannte Struktur. Wenn man die Eigenschaften nicht richtig tippt, gibt es zur Laufzeit kein Ergebnis. Wenn man stattdessen den Dialoginhalt direkt als HTML notieren könnte, wäre die IDE in der Lage, Syntaxprüfungen und Vervollständigung anzubieten.

                Das will der Profi. Der Profi sucht aber nicht mehr unser Tutorial hier zum Lernen auf. Oder doch?

                Auch der Anfänger mag es einfach.

                dedlfix.

              2. Tach!

                Ich hab mich mal rangesetzt, und eine eigene Version entworfen. (Beim Testen Browser-Konsole öffnen nicht vergessen!) Damit verbinde ich keinerlei Ansprüche. Wenn du das oder Ideen daraus übernehmen möchtest, finde ich das gut, wenn nicht, ist das auch in Ordnung.

                Vorab, ich hab deine Version als Vorlage genommen abzüglich dem Formular-Handling. Dabei ist mir aufgefallen, dass sie nicht läuft, so wie sie derzeit im Wiki steht. In den Javascript-Verwendungen wird ein textElement selektiert, das nicht unter diesem Selektor im HTML-Teil steht. Sicher nur ein Aufmerksamkeitstest 😉

                Anmerkungen zu meiner Version:

                Es gibt ja bereits Polyfills für Dialoge, beispielsweise jenen. Trotzdem hab ich eine eigene Variante erstellt, die aber unvollständig ist. Mein Anliegen war, dass das Polyfill nur die standardisierten Teile abdeckt und keine eigene Funktionalität hinzufügt. Somit ist das Polyfill auch gegen jenes austauschbar oder kann im Falle von Chrome ganz weggelassen werden.

                Im Beispiel-Code gibt es dialog.html für meine Variante und dialog-polyfill.html für das GoogleChrome-Polyfill. Der Unterschied ist eine Script-Einbindung im Head, die Funktion registerDialog und unter window.myDialog die Zeile

                dialogPolyfill.registerDialog(dialog);
                //vs. registerDialog(dialog);
                

                Mein Polyfill ist unvollständig, weil showModal nicht implementiert ist sowie das Backdrop-Handling. Auch die ganzen Feinheiten für veraltete Browser sind nicht enthalten.

                Wie kann man nun mit dem Polyfill bezüglich Wiki umgehen? Im Prinzip könnte man es weglassen und lediglich auf die vollständige Variante verweisen. So umfangreich, wie das dort ist, könnten wir das nicht nachbauen, ohne den Artikel zu sprengen. Als Kompromiss könnte man für Schulungszwecke das eigene Polyfill zeigen, für den Produktivbetrieb aber zu einem vollständigen raten.

                In meinem Code wird das Polyfill durch die Funktion registerDialog implementiert. Zusätzlich braucht es noch das globale Escape-Handling für das Schließen des Dialogs mit Esc-Taste. Im oben verlinkten Plunkr ist das unter "Individuelle Funktionalität" angesiedelt, müsste aber zum Polyfill gerechnet werden. Andererseits reagiert zwar das Beispiel in der MDN im Chrome auf Escape (wenn der Focus im Beispielkasten ist), das GoogleChrome-Polyfill ignoriert es jedoch. Meine Ergänzung spielt jedoch problemlos mit dem GoogleChrome-Polyfill zusammen. Außerdem wäre vielleicht noch erwähnenswert, dass mein Polyfill auch <form method="dialog"> berücksichtigt.

                Soweit zum Polyfill. Für unsere Belange brauchen wir jedoch noch weitergehende Funktionalität. Die wird bei mir durch window.myDialog hinzugefügt. Generell habe ich auf die bereits vorgeschlagenen Features gesetzt: Promises und Templates. (Vielleicht wird damit jetzt auch klarer, wie ich einige meiner Vorschläge gemeint hatte.)

                Es gibt bei mir keine drei Dialoge, sondern nur einen, in den durch ein beliebiges Template beliebiger Inhalt eingefügt werden kann. Die drei Varianten alert, prompt und confirm sind nur Anwendungsbeispiele dieses Prinzips.

                Nicht umsetzen konnte ich, dass das Escape-Schließen und das Klicken auf den Schließen-Button (d.h. alles mit close als Klasse) ins Reject läuft. Bei ordentlichem Schließen wird das Promise generell resolved. Man muss dann anhand des Rückgabewertes entscheiden, wie der Anwender den Dialog beendet hat. Das ist das value vom geklickten Button, inklusive dass der Browser selbständig bei Enter den ersten Button als geklickt simuliert.

                Auch kam ich nicht völlig ohne herkömmliche Callbacks aus. Mit Promises kann man dem Verwender nur anbieten, individuell handzuhaben, was "danach" passiert. Ich brauchte jedoch auch eine Möglichkeit der individuellen Initialisierung sowie zum Bereitstellen des Ergebnisses, dazu gleich mehr. Durch Promises fällt auch weg, mit den Handler für "nach dem Schließen" irgendwas anzustellen, beispielsweise sich ihn merken zu müssen. Allerdings habe auch ich an anderer Stelle das Problem zu lösen gehabt, Eventhandler zu registrieren und das nicht mehrfach bei mehrfacher Verwendung von Dialogen. (Die entsprechende Stelle in window.myDialog ist kommentiert.)

                Für das Individualisieren brauchte ich die Möglichkeit, Werte übergeben zu können. Dazu gibt es den options-Parameter. Dessen Eigenschaften sind

                • dialogSelector, der auf den Dialog verweist mit Default-Wert #mydialog
                • contentSelector, der auf das zu verwendende Template verweist
                • returnValue, als optionale Vorbelegung
                • initialize, als optionales Callback für die Initialisierung des Template-Inhalts
                • resultHandler, als optionales Callback für die Ermittlung des Ergebnisses. Ansonsten wird ein FormData-Objekt erzeugt.

                Die Möglichkeit, einen Result-Handler anzugeben, brauchte ich, weil das Promise erst nach meinen Aufräumarbeiten (dialog.removeChild …) zum then() übergeht. Ein konkretes Beispiel für den Result-Handler habe ich nicht beigefügt, FormData sollte für die meisten Fälle reichen.

                Durch Templates ist man zwar frei, was die Gestaltung des Dialog-Inhalts angeht. Aber manchmal möchte man auch zum Beispiel lediglich einen anderen Text anzeigen. Wie das gehen kann, habe ich im Beispiel zum Alert-Dialog eingebaut. Das Alert-Beispiel hat übrigens keinen then()-Handler, weil mein angedachter Anwendungsfall hier vom Typ "Fire and Forget" ist, ohne dass man danach noch etwas tun möchte.

                Zu beachten ist generell auch, dass alles was "nach dem Schließen des Dialog" stattfinden soll, im then()-Handler stattfinden oder von diesem aufgerufen werden muss. Das ist jedoch das übliche Wesen von asynchron ablaufenden Prozessen. Der Profi jongliert dann mit den Möglichkeiten von Promises, um hier noch mehr für seinen konkreten Bedarf herauszuholen.

                Ich hatte ja auch erwähnt, dass es besser ist, kleinteilige Funktionseinheiten zu bilden, statt Monsterfunktionen. Das habe ich im Polyfill meines Erachtens recht gut umgesetzt, so dass der Teil auch ohne Kommentare selbsterklärend sein sollte - bilde ich mir zumindest ein. (Siehe show - recht übersichtlich, obwohl da einiges zu tun ist.) Bei window.myDialog habe ich das nicht so umgesetzt und musste Kommentare setzen, für die bessere Nachvollziehbarkeit der Einzelteile. Das könnte man noch refaktorisieren.

                Was man auch noch verbessern könnte, mehr Objektorientiertheit reinbringen, so dass im Polyfill nicht immer der Dialog an die dortigen Funktionen übergeben werden muss. Hmm, eigentlich könnte man die Übergabe auch weglassen, weil dialog ja sowieso im Closure zur Verfügung steht.

                Die Internet Explorers habe ich nicht weiter beachtet. Der kann eh keine Promises, jedenfalls nicht ohne Polyfill. Ich bin der Meinung, wenn jemand unsere Dialog-Versionen im produktiven Umfeld verwenden möchte, kann er sich auch die Mühe machen, das für seinen konkreten Bedarf anzupassen. Wir wollen ja erklären und nicht einfach nur kopierfertigen Code anbieten.

                dedlfix.