Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – II

Im ersten Beitrag dieser Serie - siehe
"Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – I" -
hatten wir uns angesehen, warum man bei der asynchronen Zustandsabfrage eines lang laufenden PHP-Jobs auf einem Server mittels Ajax eher auf zwischenzeitlich gespeicherte Zustandsdaten in einer Datenbank zuzugreifen sollte, anstatt korrespondierende Zustandsdaten in einem Sessionspeicher abzufragen.

Wir nennen in Anlehnung an den ersten Beitrag den einmalig auf dem Webserver zu startenden und danach asynchron zu beobachtenden PHP-Hauptjob "RUN", während wir die vielen per Ajax auf dem Server periodisch gestarteten PHP-basierten Zustands-Prüf-Jobs als "CHECKER" bezeichnen.

In diesem Beitrag befassen wir uns mit einer ersten Tücke der Client-Seite - also mit dem Javascrip/jQuery-Programm, das sowohl den Hauptjob (einmalig) als auch die asynchronen, parallel zum Hauptjob laufenden Abfragen (periodisch) auf dem Server startet und die Ergebnisse auswertet. Das betrachtete Problem hat wie auch die in späteren Beiträgen angerissenen Fälle letztlich mit der Variabilität des "this"-Operators unter Javascript/jQuery zu tun.

Der Einsatz von Kontroll-Objekten unter Javascript und ihre Kopplung an Formulare oder andere DOM-Elemente der Oberfläche mittels jQuery

Eine vernünftige Programmierung unter JS wird sich an MVC-Grundprinzipien orientieren. Anstelle permanent direkt und über globale Javascript-Funktionen mit den DOM-Objekten des HTML-Interfaces zu interagieren, wird man sich eine korrespondierende Struktur von reinen JS-Objekten aufbauen, die die Behandlung der grafischen DOM-Objekte in entsprechenden Methoden kapseln. In unserem Beispiel liegt es u.a. nahe,

  • ein Objekt zu verwalten, das den Start und ggf. auch den Output des PHP-Hauptjobs kontrolliert, dessen Zustand wir während seiner Laufzeit abfragen wollen.
  • ein Objekt zu konstruieren, das einerseits die stetige, periodische Ajax-Interaktion des Web-Interfaces mit dem Server managed und andererseits die benötigten DOM-Objekte der Oberfläche zur Anzeige der asynchron ermittelten Zustandsdaten des Server-Jobs steuert.

Im ersten Fall spielt in der Regel ein Button eines Formulars die Hauptrolle beim Start des Serverjobs. Zeitgleich kann auf dem Client dann ein zusätzliches Browser-Fenster gestartet werden, das den produzierten regulären Output des RUN-Jobs während seiner Laufzeit aufnimmt. Weitere Aktionen auf der Web-Oberfläche mögen zudem anstehen. Es liegt also nahe, all diese Aktionen in Methoden mehrerer Kontrollobjekte für die erforderlichen Aufgaben (und zugehörige,abgegrenzte HTML-Objekte) zu bündeln und zu kapseln.

Auch im zweiten Fall wird es vermutlich erforderlich sein, den per Ajax zu startenden "CHECKER"-Programmen Parameter (z.B. die Nummer der zuletzt empfangenen Statusmeldung) zu übergeben. Man kann das intern im Kontrollobjekt oder aber in besonderen Fällen auch mal über versteckte oder sichtbare Input-Felder eines weiteren Formulars erledigen.
[Die Daten des (evtl. versteckten) Formulars sind dann zuvor in einem Ajax-tauglichen Datenobjekt zu serialisieren. Auch hierbei würde jQuery natürlich über seine formularbezogene "serialize"-Funktion helfen.]

In beiden Situationen stößt man also auf folgende Thematik:

  • Es gibt ein Formular "F", das per jQuery mit einem Submit-Event verknüpft wird, der direkt aus einer Javascript-Funktion oder über eine besonderes Element (Button) ausgelöst wird.
  • Man hat ferner ein Javascript-Kontroll-Objekt "K". Sein Prototyp-Objekt heiße "Ctrl_K". Eine spezielle, z.B. über
     
    "Ctrl_K.prototype.submit_event = function (e) { ...}"
     
    definierte "Methode" des Objektes soll im Zuge des Formular-Submit-Events eine (einmalige oder periodische) Programm-Aktion auf dem Server und gleichzeitig mehrere Aktionen auf der Weboberfläche auslösen. Dabei sollen weitere Eigenschaften und/oder Methoden von "K" benutzt und über den "this"-Operator (also mit Bezug zu "K") angesprochen werden.
  • Man will den Submit-Event des Formulars "F" an die für das "K"-Objekt definierte Methode "submit_event" als Callback binden. Dabei soll ein unter "submit_event" benutztes "this" wie gewöhnlich auf "K" verweisen.

Falle: Das "this" des Eventhandlers ist nicht das "this" des den Eventhandler definierenden Kontroll-Objekts

Hier tritt dann die erste Falle auf, in die man trotz besseren, theoretischen Wissens immer mal wieder reinfallen kann. Nehmen wir mal an, das Formular habe die ID "formx". Nehmen wir ferner an, wir haben irgendwo innerhalb einer Funktion von "K" das Statement

this.id_form = "formx";
$(this.id_form).submit( this.submit_event );

zur Bindung der Eventhandler-Funktion an das Formular abgesetzt. Die Frage ist, ob dann ein späteres innerhalb von "K" abgesetztes

$(this.id_form).submit();

oder ein Klick auf den evtl. vorhandenen Formularbutton funktionieren wird ? Die Antwort ist Nein.

Der Grund hierfür ist, dass innerhalb von Eventhandlern - also den zur Behandlung des Events aufgerufenen Funktionen - "this" auf den Auslöser des Ereignisses innerhalb der DOM-Hierarchie zeigt. Das gilt auch für Funktionen von (externen) JS-Objekten, die dem Event eines DOM-Objektes per Referenz zugeordnet wurden. "this" verweist in diesem Fall also auf das Formular (oder einen zugehörigen Button) und nicht auf das Kontroll-Objekt "K". "submit_event" wird zwar ausgelöst, aber "this" zeigt innerhalb dieser Funktion leider nicht - wie erwartet - auf "K". Dies führt natürlich zu Fehlern, sobald innerhalb von "submit_event" auf weitere Funktionen/Methoden oder Eigenschaften von "K" über den "this"-Operator zugegriffen werden soll.

Lösung: Einsatz der "$.proxy"-Funktion von JQuery

Es gibt mehrere Lösungsansätze, wie man mit dem Problem umgehen kann. Die eleganteste ist aber aus meiner Sicht die Verwendung von "$.proxy". Mit Hilfe dieser Funktion des jQuery-Objekts kann man den zu geltenden Kontext für das "this" in der Eventhandler-Funktion explizit setzen. Man definiert die Event-Handler-Funktion dabei innerhalb von "K" wie folgt:

$(this.id_form).submit(
    jQuery.proxy(this, 'submit_event')
);

Nach dem Auslösen des Events und dem Aufruf von "K.submit_event" weist "this" innerhalb der auszuführenden Funktions-Statements von "submit_event" dann tatsächlich wie gewöhnlich auf "K". Man kann also die Eventhandler, die man DOM-Objekten der HTML-Oberfläche zuordnen will, sehr wohl auch bei Benutzung von JQuery als Methoden ordentlich strukturierter Modell- und Kontroll-Objekte zugeordneter Javascript-Programme kapseln.

In unserem Beispiel kann die "$.proxy"-Funktion bereits beim Aufruf von "RUN" aber auch beim periodischen Aufruf der "CHECKER"-Programme genutzt werden, wenn man im letzteren Fall einen Umweg über ein (ggf. verstecktes oder offenes) Formular der Web-Oberfläche gehen muss oder will.

Im folgenden Beitrag dieser Serie
Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – III
vertiefe ich den eben andiskutierten Punkt und betrachte dann die Bedeutung von "this" im Rahmen einer definierten Callback-Funktion, des "K"-Objektes die im Fall einer erfolgreichen Ajax-Transaktion aufgerufenen wird. Auch hier ist oder bleibt "this" nicht unbedingt das, was man vielleicht erwartet.