pl: DOMParser und Progressive Enhancement

problematische Seite

Die Idee beschreibt, wie eine auf native CGI beruhende Anwendung infolge PE mehr Komfort bekommt. Und zwar so, daß der serverseitige Code überhaupt nicht geändert werden muss. Das Ergebnis wird, wie im MVC so üblich, serverseitig ins View-Template gerendert und, genauso wie das bei einem normalen Submit erfolgt wird diese HTML-Datei als Response gesendet.

In der AJAX-Callbackfunktion angekommen, werden die das Ergebnis betreffenden Werte der Datei entnommen, das erledigt der DOMParser. Letztendlich wird das Ergebnis in die der Anwendung vorliegende Seite eingebaut ohne diese Seite in ihrer Gesamtheit neu zu laden.

Nichts Neues, nur ein bischen anders.

  1. problematische Seite

    Hallo pl,

    native CGI ist für das Thema wohl irrelevant, würde ich zu behaupten wagen :)

    DOMParser ist hinreichend unterstützt, aber ich frage mich, warum Du nicht einfach responseType auf "document" setzt und das gelieferte Document-Objekt nutzt?

    Ein Service-Endpoint, der die Umrechnung durchführt und sich einen gemeinsamen Funktionskern mit dem Browser-Endpoint teilt, ist aber auch nicht sooo viel mehr Aufwand. Wenn Du jetzt damit argumentieren solltest, dass das unter Native CGI zu viel Arbeit macht, hast Du ein Argument gegen Native CGI geliefert 😉

    Deine HTML Scraping Lösung ist gut gemeint, aber nicht zu Ende gedacht. Als Verbesserung würde ich Dir, in Anlehnung an ASP.NET, eine class "updatePanel" empfehlen.

    <form action="/jd.html">
        <fieldset class="updatePanel" id="scaligerRechner" style="padding:1em"><legend><strong><label for="date">Datum links</label>, <label for="jd">Scaliger-Tag rechts</label></strong></legend>
            <input name="date" id="date" value="22.2.2019" title="Beliebiges Datum" data-submitDefault="date2jd">
    
            <input type="submit" name="date2jd" id="date2jd" value="Datum zu JD >">
            <input type="submit" name="jd2date" id="jd2date" value="&lt; JD zu Datum">
    
            <input name="jd" id="jd" value="2458537" title="Fortlaufender Tag" data-submitDefault="jd2date">
    
            <span>Wochentag: <em id="wochentag">Freitag</em></span>
        </fieldset>
    </form>
    

    Das fieldset ist das Update-Panel. Das Form könnte es auch sein. Ablauf im Submit-Event:

    1. Prüfe, ob das Form ein updatePanel ist oder updatePanel-Elemente enthält. Wenn nein: Normaler Submit

    2. Browser-Submit verhindern und Submit-Button bestimmen. Deine Lösung ist ein Anfang, aber unzureichend. Du musst prüfen:

      1. War der Fokus auf einem Button oder input type="button"? Dann ist das der Submit-Button

      2. Bei Fokus auf einem anderen input- oder textarea-Element brauchst Du Hilfe. Ich würde ein data-Attribut vorschlagen: data-submitDefault. (Ein aria-Attribut wie aria-owns oder aria-controls passt semantisch nicht, und ein Missbrauch von for wäre auch nicht gut). Das Attribut kann auf fokussierbaren Elementen, ihren Elternelementen oder dem Form liegen. Du guckst vom Fokus-Element bis hoch zum Form, ob Du dieses Attribut findest. Darin steht die ID des Submitbuttons, der zu verwenden ist.

      3. Primitivere Lösungen dieses Problems könnte mit Namenskonventionen für IDs arbeiten, an Hand derer aus der input-ID die button-ID ableitbar ist, aber das ist meiner Meinung nach zu fragil. Man könnte es auch mit Klassen lösen - ein Button und die inputs, aus denen heraus der Button bei ENTER-Submit zu nutzen ist, haben einfach alle die gleiche Klasse. Welche das ist, wäre wieder per Namenskonvention zu regeln, um nicht über die falsche Klasse einen falschen Match zu bekommen. Wie gesagt: das sind primitivere Lösungen. Meine data-Attribut Idee gefällt mir besser.

    3. Formdata per Ajax submitten.

    4. AJAX-Antwort parsefähig machen (DOMParser oder responseType="document".

    5. Zu jedem updatePanel auf der aktuellen Seite via ID das korrespondierende updatePanel in der Antwort suchen und innerHTML austauschen.

    Das ist dann generisch und nicht auf ein konkretes Form ausgelegt. Wie man die UpdatePanels schneidet, muss man je nach Anwendungszweck analysieren. Und man könnte auch im Ajax-Request per Header oder Zusatzparameter mitteilen, dass dies ein Update-Aufruf ist, so dass ein informierter Server daraufhin seine Antwort optimieren kann (nicht muss).

    Rolf

    --
    sumpsi - posui - clusi
    1. problematische Seite

      DOMParser ist hinreichend unterstützt, aber ich frage mich, warum Du nicht einfach responseType auf "document" setzt und das gelieferte Document-Objekt nutzt?

      Ne, so funktioniert das nicht.

      1. Browser-Submit verhindern und Submit-Button bestimmen. Deine Lösung ist ein Anfang, aber unzureichend. Du musst prüfen:

      Mein CODE macht das alles. Und zwar voll funktional: Du kannst Enter drückem oder klicken, beides funktioniert.

      Das ist dann generisch und nicht auf ein konkretes Form ausgelegt.

      Genau dem entspricht ja meine Lösung. Weder das <form> noch der serverseitige Code ist hinsichtlich PE zu ändern.

      MFG

      1. problematische Seite

        Hallo pl,

        Weder das <form> noch der serverseitige Code ist hinsichtlich PE zu ändern.
        Mein CODE macht das alles.

        Genau das ist mein Kritikpunkt: Es steckt im CODE. Und zwar spezifischem Code für dieses Form. Der für jede Änderung am Form synchron zu ändern ist. Das ist schlecht. Ziel von Software-Engineering ist Entkoppelung. Deine Lösung schafft eine starke Kopplung zwischen JS und HTML. Darum sollte alles fachspezifische im Markup stecken. class="updatePanel" und data-defaultSubmit="..." sind in der Javascript-freien Lösung ohne Bedeutung. Mit JavaScript führen sie zum PE.

        Und zwar voll funktional: Du kannst Enter drücken

        Genau das hat bei mir vorhin nicht funktioniert. Hm. Hast Du dies hier frisch eingebaut? Ich meine nämlich, dass da eben noch kein if stand. Hätt ich mal 'nen Screenshot gemacht.

            if( elm.name == 'jd' || elm.name == 'jd2date'){
                param += ";jd2date=1";
            }
            else if( elm.name == 'date' || elm.name == 'date2jd'){
                param += ";date2jd=1";
            }
        

        Aber dieser Code verschärft den Anlass für meine Kritik an der Lösung noch. Das JS ist spezifisch an das Form anzupassen. Dabei ist es eigentlich Boilerplate-Code, den man nicht für jedes Form neu schreiben (und testen!) möchte. Wer ist denn hier der Framework-Spezi? Genau sowas gehört in den Client-Teil eines Frameworks.

        Rolf

        --
        sumpsi - posui - clusi
        1. problematische Seite

          Genau das ist mein Kritikpunkt: Es steckt im CODE. Und zwar spezifischem Code für dieses Form. Der für jede Änderung am Form synchron zu ändern ist. Das ist schlecht. Ziel von Software-Engineering ist Entkoppelung.

          Genau das ist meine Idee: Die Entkopplung. Die damit beginnt, das Submit-Event abzufangen.

           document.forms[0].addEventListener("submit",subm);
          

          ohne in das <form> einzugreifen. Was zur Konsequenz hat, daß man in der Funktion feststellen muss welcher Button geklickt wurde. Und wie man das macht, war eine Idee von Dir übrigens (siehe Archiv).

          Natürlich muss man, wenn der serverseitige CODE unverändert funktionieren soll, dieselben Parameter im Request haben.

          MFG

          1. problematische Seite

            Hallo pl,

            wenn ein Form Attribute oder Klassen enthält, die nur im PE Fall relevant sind, stört das nicht weiter.

            Dass das Client-Script für jedes Form neu zu schreiben und zu pflegen ist, stört aber gewaltig. Du verwechselst, glaube ich, entkoppelt und unobtrusive.

            Rolf

            --
            sumpsi - posui - clusi
            1. problematische Seite

              Dann zeige doch mal bitte wie Du das Entkoppeln realisierst.

              MFG

              1. problematische Seite

                Hallo pl,

                daran habe ich bis gerade gebastelt. Sag's nicht meinem Chef 😉

                An meinen Onlinespace komm ich von hier nicht ran, darum muss ich es posten:

                PHP Testseite - kein vorbildliches PHP, kein vorbildliches HTML, nur als kleiner, Ajax-agnostischer Testrahmen. Zeigt 3 Eingabefelder und einer Checkbox. Je nach gepostetem Wert von op werden die Eingaben zu 1000 addiert oder von 1000 subtrahiert. Die Checkbox ist funktionslos, dient nur zum Spielen für Default-Button im Eltern-Element. Die Seite funktioniert ohne ajaxforms.cs ganz normal als Affenformular.

                <?php
                class RequestData {
                   public $Value1;
                   public $Value2;
                   public $Value3;
                   public $IsOk;
                   public $Result;
                   public function getValue(int $n) {
                      switch ($n) {
                		case 1: return $this->Value1;
                		case 2: return $this->Value2;
                		case 3: return $this->Value3;
                		default: return 0;
                	  }
                   }
                }
                if ($_SERVER['REQUEST_METHOD'] == 'GET')
                   $data = handleGet();
                else
                   $data = handlePost();
                   
                function handleGet()
                {
                	$data = new RequestData();
                	$data->Value1 = 0;
                	$data->Value2 = 0;
                	$data->Value3 = 0;
                	$data->IsOk = false;
                	$data->Result = '';
                	return $data;
                }
                function handlePost() {
                	$data = new RequestData();
                	$data->Value1 = isset($_POST['value1']) ? intval($_POST['value1']) : 0;
                	$data->Value2 = isset($_POST['value2']) ? intval($_POST['value2']) : 0;
                	$data->Value3 = isset($_POST['value3']) ? intval($_POST['value3']) : 0;
                	$data->IsOk = false;
                	
                    if (isset($_POST['op']) && $_POST['op'] == "add")
                		$data->Result = 1000 + $data->Value1 + $data->Value2 + $data->Value3;
                	else
                		$data->Result = 1000 - ($data->Value1 + $data->Value2 + $data->Value3);
                	return $data;
                }
                ?>
                <!DOCTYPE html>
                <html>
                <head>
                <title>Ajax Test</title>
                <style>
                label {
                   display: block;
                }
                label span {
                	display: inline-block;
                	width: 5em;
                }
                label output {
                   display: inline-block; whitespace:pre:
                }
                </style>
                <script src="ajaxforms.js"></script>
                </head>
                <body>
                <h1>Dein Taschenrechner <?=time()?></h1>
                <form method="POST">
                <fieldset><legend>Los geht's</legend>
                <?php for ($i=1; $i<4; $i++): ?>
                  <label>
                    <span>Wert <?=$i?>:</span>
                	<input autocomplete="off" type="tExt" name="value<?=$i?>" id="input<?=$i?>" data-submitBtn="<?=$i==2?"btnSub":"btnAdd"?>" value="<?=$data->getValue($i)?>">
                  </label>
                <?php endfor; ?>
                  <label data-submitBtn="btnSub">
                    <span>Alles ok?</span>
                	<input type="checkbox" name="ok" id="cbOk" value="<?=$data->IsOk?>">
                  </label>
                <label class="updatePanel" id="ergebnis"><span>Ergebnis:</span><output><?=$data->Result?></output></label>
                <button type="submit" id="btnAdd" name="op" value="add">Addieren</button>
                <button type="submit" id="btnSub" name="op" value="sub">Subtrahieren</button>
                </fieldset>
                </form>
                </body>
                </html>
                

                Rolf

                --
                sumpsi - posui - clusi
              2. problematische Seite

                So, und jetzt das JavaScript. Sind 90 Zeilen geworden, eine Menge davon für die Logik der Defaultbuttons. Diese Demo-Version kann nur ein Form auf der Seite. Ohne jegliche JS Libs. Natürlich nur zusammengeklopfter Code. Wenn ich den als "dein Werk" auf deiner Homepage wiederfinde, gibt's übrigens Ärger. Ich werde das bei Interesse ins Self Wiki stellen.

                Beim Start wird das Form gesucht und geprüft, ob darin updatePanel Elemente sind, die auch eine ID haben. Wenn nicht, passiert nix. Geschachtelte Updatepanels sind nicht sinnvoll und dürften zu Kuddelmuddel führen, das wird nicht geprüft sondern per GIGO abgehandelt. Ansonsten wird das Form in ein FormData serialisiert und verschickt.

                receiveAjaxForm überlagert den Inhalt der Updatepanels mit dem neuen Inhalt.

                serializeForm macht die Arbeit. Gemäß Clean Code habe ich etliches in Funktionen gegliedert, dadurch sind Kommentare eigentlich unnötig.

                Wenn Du debuggen willst: kein Breakpoint vor der Fokusbestimmung. Sonst ist der Fokus weg 😂

                Script und HTML sind zu 99% voneinander unabhängig. Einziger gemeinsamer Nenner ist der Klassenname 'updatePanel' und der Name des data-submitBtn Attribut. DAS ist Entkoppelung.

                // Demonstrationscode für die SelfHTML Community. 
                // Autor: Rolf Borchmann (SelfHTML User Rolf B). Verwendung auf eigenen Seiten mit Nennung des Autors.
                document.addEventListener("DOMContentLoaded", function() {
                	"use strict";
                	// ---- seitenspezifische Initialisierung. Würde den Rest normalerweise als Modul importieren
                	registerAjaxForm(document.querySelector("form"));
                		
                	// --------------- Eine Lösung als Modul muss exakt diese Funktion exportieren. 
                	function registerAjaxForm(form) {
                		let panels = form.querySelectorAll(".updatePanel[id]");
                		if (panels.length < 1) return;
                	
                		form.addEventListener("submit", e => sendAjaxForm(e, form, panels));
                	}
                	
                	// -------------------- Code ab hier bleibt private für das Modul
                	
                	function sendAjaxForm(e, form, panels) {
                		let data = serializeForm(form);
                
                		let req = new XMLHttpRequest();
                		req.open("POST", form.action);
                		req.responseType = "document";
                		req.onload = e => receiveAjaxForm(e, panels);
                		req.send(data);
                		e.preventDefault();
                	}
                	
                	function receiveAjaxForm(loadedEvent, panels) {
                		let xhr = loadedEvent.target;
                		if (xhr.status != 200) {
                			alert("Request failed, Status="+xhr.status);
                			return;
                		}
                		for (let i=0; i<panels.length; i++) {
                			let updatePanel = panels[i];
                			let newPanel = xhr.response.getElementById(updatePanel.id);
                			if (newPanel != null)
                				updatePanel.innerHTML = newPanel.innerHTML;
                		}
                		return;
                	}
                	
                	function serializeForm(form) {
                		let data = new FormData(form);
                		let submitter = locateSubmitButton(form);
                		if (submitter) {
                			data.append(submitter.name, submitter.value);
                		}
                		return data;
                	}
                	
                	function locateSubmitButton(form) {
                		let submitter = form.querySelector(":focus");
                		if (isSubmitButton(submitter)) {
                			return hasValidName(submitter) ? submitter : undefined;
                		}
                		if (isInputElement(submitter)) {
                			let s = locateDefaultSubmitter(submitter);
                			if (!s) return undefined;
                			s = document.getElementById(s);
                			return (s && hasValidName(s)) ? s : undefined;
                		}
                		return undefined;
                	}
                	
                	function isSubmitButton(elem) {
                		if (!(elem instanceof HTMLButtonElement || 
                				elem instanceof HTMLInputElement))
                			return false;
                		return elem.type == "submit"; 
                	}
                	
                	function isInputElement(elem) {
                		if (elem instanceof HTMLTextAreaElement) return true;
                		if (!(elem instanceof HTMLInputElement)) return false;
                		return (elem.type == "text" || elem.type == "radio" || elem.type == "checkbox");
                	}
                	
                	function locateDefaultSubmitter(elem) {
                		if (elem.dataset.submitbtn) return elem.dataset.submitbtn;
                		if (elem instanceof HTMLFormElement) return null;
                		return locateDefaultSubmitter(elem.parentElement);
                	}
                	
                	function hasValidName(elem) {
                		return typeof elem.name == "string" && elem.name.trim() != "";
                	}
                });
                

                Rolf

                --
                sumpsi - posui - clusi
                1. problematische Seite

                  Ich danke Dir ganz herzlich, aber nach Entkopplung sieht das nicht gerade aus 😉

                  Je nach gepostetem Wert von op werden die Eingaben zu 1000 addiert oder von 1000 subtrahiert.

                  Dann ist op ein sogenannter Schlüsselparameter. Natürlich geht es bei Webanwendungen immer darum, die Programmierlogik über Requestparameter zu schleifen.

                  Um auf mein Beispiel zurückzukommen: Da sind es 2 Schlüsselparameter, date2jd und jd2date. Du tust immer gut daran, diese Schlüsselparameter von den restlichen Parametern sauber zu trennen. Die restlichen Parameter sind date=1.1.1&jd=1234 und der Schlüsselparameter legt fest was damit gemacht werden soll (anhand der Namen erklärt sich das von selbst in meinem Beispiel).

                  Natürlich muss der Schlüsselparameter dem Request mitgegeben werden. Beim native Submit macht das der Browser, wenn für PE das Submitevent abgefangen wird, muss man sich selbst darum kümmern.

                  Serverseitig siehst bei mir übrigens so aus, Kontrolle über die Schlüsselparameter:

                  sub control{
                      my $self = shift;
                      my $date = $self->trim($self->param('date'));
                      my $injd   = $self->trim($self->param('jd')) || "0";
                      
                      $self->{STASH}{date} = $self->ents($date);    
                      $self->{STASH}{jd} = $self->ents($injd) || 0;  
                      $self->{STASH}{wochentag} = '';
                  
                      if($self->param('date2jd')){
                          $self->{STASH}{jd} = '';
                          my $date = $self->trim($self->param('date')) or return $self->errorP(title => "Eingabefehler", descr => "Kein Datum eingegeben");
                          $self->{STASH}{jd} = $self->{SCA}->jd( date => $date);
                          $self->{STASH}{wochentag} = $self->{SCA}->wochentag( date => $date);
                          return $self->errorP(descr => "$@", title => "Eingabefehler") unless defined $self->{STASH}{jd};
                      }
                      elsif($self->param('jd2date')){
                          $self->{STASH}{date} = '';
                          return $self->errorP(title => "Eingabefehler", descr => "Kein JD eingegeben") if length($injd) == 0;
                          
                          my $day = $self->{SCA}->day( jd => $injd ) or return $self->errorP(title => "Eingabefehler", descr => "$@");
                          my $month = $self->{SCA}->month( jd => $injd );
                          my $year = $self->{SCA}->year( jd => $injd );
                          $self->{STASH}{wochentag} = $self->{SCA}->wochentag( jd => $injd);
                          $self->{STASH}{date} = "$day.$month.$year";
                      }
                      else{ $self->errorP( title => 'Fehler im Request', descr => 'Unbekannter Parameter'   ) }
                  }
                  

                  Also einschließlich der Fehlerbehandlung. Hierzu setzt die Funktion errorP() den Responsestatus auf 400 damit die XHR Callbackfunktion das mitbekommt. Die Fehlertexte liefert das darunterliegende Kalendermodul.

                  MFG

                  1. problematische Seite

                    Hallo pl,

                    sorry für den kurzen Moment, wo ich alles gelöscht hatte. Das war nicht korrekt von mir. Aber ich war sauer über dein beiläufiges Wegwischen von 4 Stunden Arbeit.

                    Welche Einwände hast Du bezüglich Entkoppelung von ajaxForms-Modul und Testseite? Dass Client- und Serverteil der Testseite voneinander wissen und aufeinander aufbauen, liegt in der Natur der Sache und darum ging's auch nicht.

                    Rolf

                    --
                    sumpsi - posui - clusi
                    1. problematische Seite

                      offenbar hast Du nichts verstanden.

                      Doch schon. Du wolltest mir zeigen wie Du entkoppelst. Und das habe ich gesehen: Du prüfst, genauso wie ich das mache, welcher Button den Focus hat. Von daher ist Dein Script genauso wie meins an das <form> gekoppelt.

                      MFG

                      1. problematische Seite

                        Hallo pl,

                        nein, du verstehst es wirklich nicht. Oder Du willst trollen.

                        Dein Script ist detailliert an die Eingabefelder gekoppelt. Es muss genau wissen, wie das Form aussieht. Es muss logische Beziehungen zwischen den Feldern und Buttons kennen. Es muss sogar die Postback-Adresse kennen. Anderes Form, anderes Script. Enge funktionale Kopplung.

                        Mein Script funktioniert mit jedem Form, das Bereiche enthält die als updatePanel markiert sind. Das <form> Element ist der Aufhänger. Muss ja sein, ohne Form kann man nichts Posten. Der Inhalt des Forms ist mir komplett egal. Ganz lose funktionale Kopplung.

                        Ein Bug: Ich müsste für die Postback-Adresse das Action-Attribut des Form verwenden, nicht die Location der Seite. Das fixe ich noch. Ein Todo: Unterstützen von GET Formularen.

                        Die technische Kopplung ist bei uns beiden gleich: unobtrusiv auf das Submit-Event registriert.

                        Rolf

                        --
                        sumpsi - posui - clusi
                        1. problematische Seite

                          Dein Script ist detailliert an die Eingabefelder gekoppelt. Es muss genau wissen, wie das Form aussieht. Es muss logische Beziehungen zwischen den Feldern und Buttons kennen.

                          Nein, muss es nicht. Das Anhängen des Schlüsselparameters kann ich genauso anonymisieren wie Du das gezeigt hast: Über Name und Value des Buttons der den Focus hatte. Genaus kann ich auch mit den Namen der Eingabefelder verfahren, nur ist das serverseitig nicht implementiert. Ergo mache ich das clientseitig.

                          Es muss sogar die Postback-Adresse kennen.

                          Muss es auch nicht. Wie ich schon schrieb: Das ganze <form> austauschen ist auch kein Thema.

                          Mein Script funktioniert mit jedem Form, das Bereiche enthält die als updatePanel markiert sind.

                          Ja sicher doch. Und selbstverständlich muss das Script wissen wohin die Response gerendert werden soll. Ob dieser Bereich explizit mit updatePanel markiert ist oder nicht ist funktional ohne Bedeutung. Aber die Idee einer Kennzeichnung ist gut!

                          Ich werde das mal umsetzen, und in den Ausgabebereich auch die Fehlerbehandlung mit einbeziehen. Die bei Dir übrigens fehlt.

                          Ok, done. Du bekommst ein weiteres Problem: Beim Austausch Deines updatePanels geht der Focus flöten. Das ist nicht gerade schön.

                          MFG

                          1. problematische Seite

                            nur ist das serverseitig nicht implementiert. Ergo mache ich das clientseitig.

                            Das Submitten per Enter aus dem jeweiligen Eingabefeld heraus ist serverseitig gar nicht implementierbar. Die Zuordnung der Felder lässt sich nur mit JS realisieren und genau das ist Progressive Enhancement:

                                // Welcher Button wurde geklickt
                                // In welchem Eingabefeld ist der Cursor
                                var btn = ev.currentTarget.querySelector("button:focus");
                                var inp = ev.currentTarget.querySelector("input:focus");
                                var elm = btn || inp; // Klick oder Enter
                                var param = $(document.forms[0]).serialize();
                                if( elm.name == 'jd' || elm.name == 'jd2date'){
                                    param += ";jd2date=1";
                                }
                                else if( elm.name == 'date' || elm.name == 'date2jd'){
                                    param += ";date2jd=1";
                                }
                            

                            Und natürlich muss der Action-Parameter angehängt werden. Ob das über FormData.append() gemacht wird oder wie obenstehend ist unerheblich, dafür ist der Transportlayer ja transparent.

                            Die Umkehrung von form.serialize() wäre interessant. Denn es ist ja 1:1 dasselbe Form, also was die Response schickt und was im Browser ist. So kann die Zuweisung der Werte automatisiert werden, von doc(response) zu document(DOM). Das wäre auch ein Schritt zur Enkopplung. Mit FormData jedoch ist eine Umkehrung nicht möglich.

                            MFG

                            1. problematische Seite

                              Hallo pl,

                              Das Submitten per Enter aus dem jeweiligen Eingabefeld heraus ist serverseitig gar nicht implementierbar.

                              Im Allgemeinen nicht, im vorliegenden Fall schon: 2 <form>s im <fieldset>. Dann hat jedes Eingabefeld seinen eigenen default <button>.

                              Die Umkehrung von JQuery .serialize() nützt dir nichts weil du ja vom Server keinen POST Body zurück schickst. Deshalb ist responseType="document" das nächst liegende.

                              Ob das über FormData.append() gemacht wird oder wie obenstehend ist unerheblich, dafür ist der Transportlayer ja transparent.

                              Nein, das ist sehr erheblich. Durch die Stringverkettung durchbrichst du die Transparenz und setzt eine konkrete Implementierung voraus. Transparenz - im Sinne von "It Just Works" und mein Code muss nicht wissen wie - erreichst du nur mit FormData.append. Blöd ist dabei nur, dass weder .serialize() noch FormData den encType beachten und ein urlencoded Ergebnis liefern. Aber das vorauszusetzen ist trotzdem ein Transparenzbruch, bzw. eine unnötige Koppelung.

                              Rolf

                              --
                              sumpsi - posui - clusi
                              1. problematische Seite

                                him,

                                Die Umkehrung von JQuery .serialize() nützt dir nichts weil du ja vom Server keinen POST Body zurück schickst.

                                Das ganze Dokument wird im Messagebody zurückgesendet. Und selbstverständlich kann ich da das darin eingebaute Formular serialisieren, eine Funktion dazu ist fertig und funktioniert einwandfrei.

                                Nein, das ist sehr erheblich. Durch die Stringverkettung durchbrichst du die Transparenz und setzt eine konkrete Implementierung voraus.

                                Stimmt. Kann ich aber problemlos ändern.

                                Transparenz - im Sinne von "It Just Works" und mein Code muss nicht wissen wie - erreichst du nur mit FormData.append.

                                Nö. Mit meinen Funktionen kann ich das auch.

                                Blöd ist dabei nur, dass weder .serialize() noch FormData den encType beachten und ein urlencoded Ergebnis liefern.

                                Der Enctype spielt überhaupt keine Rolle! MFG

                                1. problematische Seite

                                  hi,

                                  Nein, das ist sehr erheblich. Durch die Stringverkettung durchbrichst du die Transparenz und setzt eine konkrete Implementierung voraus.

                                  Stimmt. Kann ich aber problemlos ändern.

                                  Done 😉

                                  Blöd ist dabei nur, dass weder .serialize() noch FormData den encType beachten und ein urlencoded Ergebnis liefern.

                                  Der Enctype spielt überhaupt keine Rolle!

                                  Wenn ein FormData Objekt im POST ist, sendet der Browser spontan den Content-Type multipart/form-data samt Boundarystring. D.h., daß der Programmierer hierzu nicht eingreifen muss.

                                  MfG

                                  PS: Meinen Formularserializer findest Du in /request.js. Der Funktion sampleform() kann nun auch des Document übergeben werden, z.B. von einer AJAX-Response. Default ist window.document.

    2. problematische Seite

      DOMParser ist hinreichend unterstützt, aber ich frage mich, warum Du nicht einfach responseType auf "document" setzt und das gelieferte Document-Objekt nutzt?

      Stimmt. Funktioniert aber nur, wenn man responsetype richtig schreipt 😉

      Das fieldset ist das Update-Panel. Das Form könnte es auch sein.

      Interessanter fänd' ich eine Umkehrfunktion zu form.serialize(). Da das Ergebnis ja eh ins <form> geschrieben wird. Eine Alternative wäre, das <form> komplett auszutauschen.

      Deine ASP.NET Lösung überzeugt nicht weil sie der Idee des PE nicht entspricht: zu Prüfen ob JS verfügbar ist was mehr Komfort ermöglicht. MFG

      1. problematische Seite

        Hallo pl,

        ja, komplett das HTML austauschen, das meinte ich. Genau das tut ja mein Vorschlag: HTML Fragmente aus der Response ins DOM klatschen statt Elementeigenschaften zu kopieren. Das Kopieren der Elementeigenschaften ist natürlich feingranularer und erfordert vor allem kein neues Layouting der Seite. Man könnte das lösen, indem man die zu aktualisierenden Elemente mit Attributen wie data-update="value" oder data-update="class,textContent" markiert, aber es gibt ja auch Fälle, wo die Antwort auf einen POST mehr verändert als nur Feldinhalte. Eine verbessertes Form2Ajax-Script könnte beides unterstützen.

        Und wieso sollte meine Idee kein PE sein? Genau wie bei Dir läuft ohne JS standardmäßiges HTML Submit mit Page Reload. "Prüfen ob JS verfügbar ist" kann eh keiner - wenn es nicht da ist, ist das HTML auf sich gestellt und muss damit leben und funktionieren.

        Mit dem kleinen Nachteil, dass eine Eingabe in der Tagesnummer mit ENTER nicht funktioniert. Um das degressiv in den Griff zu bekommen, müsste man zwei Forms machen; in dem einen steckt nur Datum-Input und date2jd, im anderen steckt jd2date und Tagesnummer-Input. Das wär eine domainspezifische Lösung, die nicht generalisierbar ist, und die vermutlich andere Probleme auslöst (aber immerhin sollte gemäß der Inhaltskategorien <form> in <fieldset> erlaubt sein).

        Rolf

        --
        sumpsi - posui - clusi
    3. problematische Seite

      hi @Rolf B

      Deine HTML Scraping Lösung ist gut gemeint, aber nicht zu Ende gedacht. Als Verbesserung würde ich Dir, in Anlehnung an ASP.NET, eine class "updatePanel" empfehlen.

      Diese Idee hatte ich übrigens auch schon und: Hatte die vor ein paar Jahren mal hier beschrieben. Da ging ein riesen Geschrei dann los, man dürfe ja das class-Attribut nicht für Funktionalitäten der Programmlogik missbrauchen.

      MFG

      1. problematische Seite

        Hallo pl,

        Link bitte.

        Bis demnächst
        Matthias

        --
        Pantoffeltierchen haben keine Hobbys.
      2. problematische Seite

        Hallo pl,

        wer auch immer da geschrieen hat. Es ist ja nicht auszuschließen, dass sich die Meinung zum richtigen Gebrauch von class verändert hat. Damals gab's vielleicht auch noch keine semantischen HTML Elemente, je nach dem, wie lang das her ist.

        Das class-Attribut beschreibt, so wie ich das verstanden habe, Business-Aspekte der Webseite, um zu markieren, welches Element welche Bedeutung hat, wenn das semantische Markup nicht ausreicht. Deswegen würde ich das nicht als falsch ansehen, aber dazu könnten sich mal die HTML-Päpste hier äußern. Die HTML Living Spec sagt: "authors are encouraged to use values that describe the nature of the content, rather than values that describe the desired presentation of the content.", und da finde ich eine class updatePanel durchaus nicht daneben.

        Die Art, wie ein Ajaxifizierer seine Updatebereiche findet, ist aber ein Implementierungsdetail. Wenn es keine class sein soll, gibt es Alternativen. Irgendwas muss man ja tun, um die Bereiche zu markieren, die als Updatepanel dienen sollen. Denkbar wäre die Verwendung eines custom-Element <update-panel>, oder ein Attribut data-updatepanel="panel-id".

        UpdatePanels in ASP.NET sind schon relativ alt (2005), und ich habe mir nie angeschaut wie das HTML aussieht, das sie rendern. Aber - nur für dich - hab ich mein Visual Studio mal angefeuert und eine Testpage mit Updatepanel gemacht. ASP.NET macht es ohne Klassen, statt dessen mit einem (generierten) Script-Block, der das Updatepanel explizit an Hand seiner ID einem PageRequestManager bekannt macht. Das ist eine Mischung aus generischem und spezifischem Code. Entscheidend ist auch bei ASP.NET: Außer der Registrierung ist der übrige Code komplett unabhängig vom Business. Und aus Entwicklersicht ist es rein deklarativ, das Script wird von der Runtime generiert. Ohne ASP.NET hast Du keine Runtime und kannst darum kein Script generieren, darum muss man die Registrierung dann entweder von Hand programmieren oder deklarativ - wie von mir vorgeschlagen - steuern.

        Rolf

        --
        sumpsi - posui - clusi
        1. problematische Seite

          Moin,

          der Protest war ja berechtigt, denn es gibt Abhängigkeiten. Also von wegen Universalattribute, Logik und Design sollten schon voneinander unabhängig sein. Anstelle des class Attribute lässt sich jedoch für logische Belange genausogut das name Attribute verwenden, sofern es zugelassen ist.

          Logische (programmiertechische) Belange sind z.B. das Gruppieren von Eingabefeldern und damit verbunden eine bessere Strukturierung der Eingabedaten. Und natürlich auch die Auszeichnung von Zielgebieten wo Ajaxresponses treffsicher einschlagen sollen.

          Je filigraner das erfolgt desto benutzerfreundlicher das ist. Nachdem ein ganzes <fieldset> oder <form> ausgetauscht wurde, muss der Cursor neu positioniert werden, schön ist das nicht.

          MFG

          1. problematische Seite

            Hallo pl,

            Nachdem ein ganzes <fieldset> oder <form> ausgetauscht wurde, muss der Cursor neu positioniert werden, schön ist das nicht.

            Das stimmt, schön ist was anderes. Aber eine Rekonstruktion des Fokus lässt sich in ajaxForms.js integrieren, so dass man das Problem einmal löst und danach automatisiert behandelt. Voraussetzung dafür (in meiner unveröffentlichten Implementierung):

            • das fokussierte Element hat ein id Attribut, dann kann ich mit getElementById repositionieren
            • das fokussierte Element hat ein name Attribut. Dann ist noch zu ermitteln, das wievielte Vorkommen des Name das ist (radio-buttons!) und ich kann nach Rückkehr vom Server auf das i-te Vorkommen des Name repositionieren. Damit bleibt der Cursor im gleichen Feld stehen. Er springt lediglich an den Anfang des Feldes zurück. Mit window.getSelection und inputelement.setSelectionRange könnte man auch das rekonstruieren. Vermutlich muss man auch noch was bezüglich der Scrollposition des Fensters unternehmen; wenn ein großes Form ersetzt wird, könnte es im Browser zucken.

            Unter diesem Gesichtspunkt ist es eleganter, viele kleine Updatepanels vorzusehen. Dann zuckt weniger. Nachteil ist, dass die Struktur des Forms dann nicht variieren darf (z.B. je nach Auswahl in Drop-Down 1 kommen Eingabefelder hinzu). Solche Bereiche müssen komplett in einem Updatepanel stehen.

            Ohne id oder name könnte man noch weiteren Aufwand treiben, um einen Locator für das Element zu konstruieren, das ist aber eine riskante Sache weil die POST-Rückgabe die Formstruktur verändern kann. Darum habe ich das erstmal weggelassen. Id oder name sollten fokussierbare Form-Elemente in 99% der Fälle aufweisen.

            Was bei einem grobschlächtigen Replace auch verloren gehen kann sind Tuningmaßnahmen am DOM, die von JavaScript gemacht wurden (z.B. eine ergänzte class). Die Frage ist nur, wieweit solche Änderungen nach einem Server-Durchlauf noch sinnvoll sind. Bei einer Ajaxifizierung einer Seite muss man clientseitig vermutlich Hirnschmalz investieren, wenn man per JS ins DOM eingreift. Der Ablauf bei einem vollen Postback ist anders als bei einem per Ajax gekapselten partiellen Postback. Vielleicht muss man den Ajaxifizierer zu einer EventSource ausbauen, damit sich Client-code auf das Ende des Postback registrieren kann (analog zum DOMContentLoaded Event). Diesen Aspekt hast Du bei deiner Lösung genauso vergessen wie ich :)

            Was auch noch fehlt, ist eine Opt-In Möglichkeit für den Server, seine Arbeit bei einem partiellen Postback zu optimieren. ASP.NET macht das mit einem speziellen Header, der vom Postback des UpdatePanel gesetzt wird, und auf den das ASP.NET Framework mit einem optimierten Rendering der Response reagiert. Ist ja Quatsch, eine riesige Seite neu auszuliefern wenn man nur 500 Zeichen davon haben will; der Server kann dann auf das Rendering des Restes verzichten. Für den Einstieg kann man es seinlassen, für eine Optimierung kann man es einbauen.

            Auto-Ajax hat eine Menge Potenzial, aber macht auch potenziell viel Arbeit

            Rolf

            --
            sumpsi - posui - clusi
            1. problematische Seite

              ASP.NET macht das mit einem speziellen Header,

              Bisher mache ich das ja so. Nur ist die hier beschiebene Idee ja die, auf diese Diskriminierung eben zu verzichten 😉