phpmyadmin 4.6.4 ist buggy – nicht für Sicherungen verwenden !

Ich hatte heute die Aufgabe, die Daten eines WordPress-Blogs von der MySQL-Datenbank eines Hosting Providers zu sichern und den Blog dann auf eine neue Datenbank bei einem anderen Provider umzuziehen. Dabei hat ein übler Bug in der phpMyAdmin-Version 4.6.4 etliche meiner Nerven verschlissen:

Auf Exports von Tabellen mit Kommentaren oder Spalten mit bestimmten HTML-Inhalten kann man sich bei der aktuellen Version (Stand 03.10.2016) leider nicht verlassen; der nachfolgende Import scheitert unter Umständen – vermutlich wegen einer fehlenden oder fehlerhaften Maskierung von Anführungszeichen.

Es dauerte aber eine ganze Weile, bis ich anfing, der Exportfunktion von phpMyAdmin zu misstrauen – vor allem, weil mir bei dem Provider keine Datenbank-Logs zur Verfügung standen. Ich konnte den Fehler aber an einer reduzierten “posts”-Testtabelle der WordPress-Installation nachvollziehen; der Export und nachfolgende Import schlägt mit der Version 4.6.4 reproduzierbar fehl – in meinem Fall sogar mit einem internen Server-Error.

Gott sei dank war die alte Datenbank noch nicht gelöscht! Ich konnte den Export/Import-Vorgang mit der auf die Schnelle installierten phpMyAdmin-Version 4.4.15.8 anschließend anstandslos und fehlerfrei durchführen.

Solche Bugs können üble Folgen haben, wenn man nach einer “Sicherung” naiverweise den alten Datenbank-Zustand löscht, ohne vorher an einer anderen Datenbank den Reimport getestet zu haben. Auch für phpMyAdmin gilt bei Sicherungen die alte Regel:
Immer erst die ganze Kette zur Restaurierung testen, bevor man irgendetwas löscht!

Dennoch: So ein Bug ist wirklich übel! Ich bin ein wenig sauer …. im Nachhinein fand ich dann unter github folgende Bug-Einträge:
https://github.com/phpmyadmin/phpmyadmin/issues/12086
https://github.com/phpmyadmin/phpmyadmin/issues/12453
Der erste davon wurde geschlossen, obwohl er offenbar immer noch oder wieder vorhanden ist.

Apache Rewrite für zwei Domänen, die gemeinsame Datei-Ressourcen nutzen

Gestern wurden wir mit zwei Websites konfrontiert, die wir um einen Blog ergänzen sollten. Die Webserver-Installation, die wir vorfanden, war interessant und hat uns ein wenig beschäftigt:

Zwei Domain-Namen (alpha.de und beta.de) waren beim Hosting-Provider mit ein und demselben Webserver-Verzeichnis verbunden. Dieses wiederum wies zwei Unterverzeichnisse auf, in denen sich vor allem HTML-Seiten zu den verschiedenen Domänen befanden. Die Idee hinter diesem Setup war wohl, von den Webseiten beider Domänen aus gemeinsame Datei-Ressourcen im Hauptverzeichnis zu nutzen.

Setup zur Nutzung gemeinsamer Datei-Ressourcen durch zwei Web-Domänen auf demselben Webserver

Nennen wir das Hauptverzeichnis mal dir_domains und die Subverzeichnisse dir_alpha und dir_beta.

Die Webseiten von alpha.de und beta.de nutzen gleiche Datei-Ressourcen (CSS-Dateien, Javascripts, PHP-Programme, …). Entsprechende Verzeichnisse fanden sich unter dir_domains:

dir_domains (Webserververzeichnis für Domains alpha/beta)
|__css
|__js
|__php
|__index.php
| ….
|
|__dir_alpha
|        |__index.html
|        |…(HTML-Dateien für die Domäne beta.de)
|        | ….
|__dir_beta
         |__index.html
         |… (HTML-Dateien für die Domäne beta.de)
         |…

Die Datei index.php übernahm die Zugangssteuerung für die Startseiten: Je nach Domain-Anforderung (alpha.de oder beta.de) des Users wurde dieser auf die jeweilige Index-Seite unter dir_alpha oder dir_beta umgelenkt. So weit, so gut – oder eher so schlecht ….

Nach Inaugenscheinnahme der beiden Domänen im Internet war mir klar, warum das so aufgesetzt worden war:

Die Webseiten unter alpha.de bzw. beta.de haben einen ganz ähnlichen Aufbau und nutzen gleiche PHP-Programme und Scripts. Da bei der Anforderung von Ressourcen wie CSS-/JS-/PHP-Dateien Domaingrenzen in der Verzeichnisstruktur des Web-Servers normalerweise nicht überschritten werden dürfen, waren die Web-Designer gezwungen gewesen, beiden Domänen dasselbe Hauptverzeichnis des gehosteten Webserver-Accounts zuzuweisen. Zur Kompensation musste eine Art Umlenkung auf die jeweiligen Verzeichnisstrukturen integriert werden. (Anmerkung am Rande: Die Domängrenzen, im Sinne von Verzeichnisgrenzen, gelten übrigens nicht für Include-Dateien, die in PHP-Programmen nachgeladen werden. Solche Include-Dateien kann man ruhig oberhalb des Domainverzeichnisses unterbringen!).

Was war am Setup schlecht?

Während die Nutzung gemeinsamer Ressourcen durchaus zu befürworten ist, war die “Umlenkung” durch das PHP-Skript schlecht gelöst. Sie funktionierte eigentlich nur für den Aufruf einer der beiden Domän-Namen selbst (Umlenkung auf die jeweilige index.html-Seiten). Die Navigation innerhalb der Webseiten einer Domäne war über relative Pfade gelöst; sobald ein User sich einmal innerhalb einer Domäne bewegte, funktionierte deshalb alles aus Nutzersicht alles bestens.

Aber: Der Aufruf einer spezifischen Seite einer Domäne über eine direkte Eingabe in die Adresszeile des Browsers – z.B. http://alpha.de/infos/impressum.html – funktionierte mit dem vorhandenen PHP-Script nicht. Das Script index.php kümmerte sich nur um die Index-Seiten der Domänen. Es wurde als Index-Datei ja nur dann aktiv, wenn eine der Domän-Adressen alpha.de oder beta.de ohne weitere Zusätze im Browser
aufgerufen wurde.

Lösungsansatz über Apache Rewrite

Bei dem gehosteten Webserver handelte es sich um einen Apache-Server. Eine saubere Lösung für den gewünschten Setup-Ansatz mit geteilten Datei-Ressourcen besteht dann natürlich darin, das Apache Rewrite-Modul (mod_rewrite) zu nutzen. Die Servereinstellungen beim Provider waren so, dass die Rewrite Engine über lokale “.htaccess”-Dateien aktiviert und gesteuert werden konnte. Wir haben dann folgenden einfachen Vorschlag umgesetzt:

Inhalt der .htaccess-Datei für das Verzeichnis dir_domains:

Options +FollowSymLinks 
RewriteEngine On 
RewriteBase /

RewriteRule ^alpha/(.*)$ - [L]
RewriteRule ^beta/(.*)$ - [L]

RewriteRule ^css/(.*)$ - [L]
RewriteRule ^images/(.*)$ - [L]
RewriteRule ^php/(.*)$ - [L]
RewriteRule ^script/(.*)$ - [L]

RewriteCond %{SERVER_NAME} alpha.de [OR]
RewriteCond %{SERVER_NAME} www.alpha.de
RewriteRule ^$ alpha/index.html [L]

RewriteCond %{SERVER_NAME} alpha.de [OR]
RewriteCond %{SERVER_NAME} www.alpha.de
RewriteRule ^(.*)$ alpha/$1 [NC,L]

RewriteCond %{SERVER_NAME} beta.de [OR]
RewriteCond %{SERVER_NAME} www.beta.de
RewriteRule ^$ beta/index.html [L]

RewriteCond %{SERVER_NAME} beta.de [OR]
RewriteCond %{SERVER_NAME} www.beta.de
RewriteRule ^(.*)$ beta/$1 [NC,L]

 
Die ersten 2 Rewrite-Regeln sorgen dafür, dass keine Umlenkung mehr vorgenommen wird, wenn man sich bereits im Verzeichnisbereich der Seite befindet. Dieser Vorspann ist wichtiger, als man meinen möchte: die nachfolgenden Regeln würden für sich allein zu einer unbegrenzten Iteration von Rewrites führen!

Die nächsten 4 Regeln sorgen dann dafür, dass Verweise auf die gemeinsam genutzten Ressourcen-Verzeichnisse und zugehörige GET-Anforderungen an den Server unangetastet bleiben. Diese Verzeichnisse werden ja aus dem HTML-Code der Webseiten unter dem alpha- bzw. dem beta-Sub-Verzeichnis referenziert; z.B. über relative Pfade.

Danach kommen die eigentlichen Umlenkungsregeln. Wir haben hier die Grundregel befolgt, dass eine oder mehrere Rewrite Conditions sich nur und ausschließlich auf die nächste Rewrite Rule beziehen – also auf genau eine Rewrite Rule! Das wird in der hektik oft übersehen; es gibt keine native Klammerung mehrerer Rewrite Rules zu Rewrite Conditions. Allerdings kann man mit Negationen der Bedingungen und Skip-Zusätzen hinter den Rewrite-Regeln tricksen. Um das Verständnis nicht zu erschweren, haben wir hier auf solche Hacks verzichtet.

Wird keine Dateiname angegeben – ist also der Pfad hinter dem Domain-Anteil leer – wird auf die jeweilige Index-Seite umgelenkt. Sind konkrete Webseiten einer Domäne angefordert, wird auf die gewünschte Datei im jeweiligen Unterverzeichnis verwiesen.

Das war es schon; die Datei index.php kann und sollte danach gelöscht werden. Unser Kunde kann nun 2 Domänen im gleichen Webserververzeichnis nutzen und zwischen beiden Implementierungen gemeinsame Ressourcen teilen. Eigentlich eine nette kleine Geschichte, die ich vielleicht auch mal für eigene Sites nutzen werde – Apache sei Dank!

jQuery: Aufruf von Objekt-Methoden im Rahmen der Event-Behandlung und der .each()-Funktion für Selektionen

Will man Funktionalitäten, die man in Javascript/jQuery-Projekten erfolgreich entwickelt hat, wiederverwenden, muss man modular arbeiten und seine Funktionen in übergeordneten Einheiten kapseln. Wie einige meiner Leser wissen, versuche ich diese Anforderungen u.a. über die Etablierung abstrakter Control-Objects [CtrlOs] und definierte “Methoden” solcher Objekte umzusetzen.

Einerseits können Konstruktorfunktionen für abstrakte CtrlOs über ihren “prototype” zusammenhängende funktionale Verfahren im Sinne einer reinen Funktionalklasse bündeln.
Anderseits repräsentieren CtrlOs in meinen Web-Projekten bei Bedarf aber auch zusammenhängende Web-Bereiche sowie deren konkrete HTML-Objekte. Sie bündeln dann alle erforderlichen Methoden zum Umgang mit Ereignissen auf diesen Objekten. CtrlOs abstrahieren dabei soweit als möglich von der konkreten HTML-Ausprägung und führen zu einer allgemeineren Repräsentation bestimmter HTML-Schlüssel-Objekte im JS-Code – wenn nötig über die Definition weiterer geeigneter Unter-Objekte des CtrlOs. Wiederverwendbarkeit reduziert sich dann meist auf ein geeignetes Matching der IDs von Schlüsselelementen sowie ein Matching von Selektionskriterien für einen neuen Anwendungskontext (sprich für eine andere Webseite).

Beispiele:
Für die Steuerung einer Bildgalerie auf unterschiedlichen Webseiten ist z.B. lediglich der übergeordnete Container für die Bilder zu bestimmen und es sind geeignete Kriterien zur jQuery-Selektion der beinhalteten Bilder über Parameter zu definieren. Und schon können die meisten vordefinierten Methoden des CtrlOs wieder zum Einsatz kommen. Ein anderes Beispiel würde etwa die Kontrolle der Ajax-Interaktionen eines normierten Form-Elements mit einem PHP-Server zur Durchführung und Statusüberwachung eines Dateiuploads bieten. Auch hier lassen sich alle Funktionalitäten in Methoden eines relativ abstrakten und wiederverwendbaren CtrlOs bündeln.

Unter “Methoden” verstehe ich dabei allgemeine Funktionsobjekte, die als Elemente der fundamentalen “prototype”-Eigenschaft der Konstruktor-Funktionen für das CtrlO und ggf. der Konstruktorfunktionen für Sub-Objekte des CtrlO definiert werden. (In Javascript stellen Funktionen Objekte dar.)

Ziel ist es also, die gesamte Event-Behandlung und auch andere Aufgaben vollständig über “Methoden” eines für die jeweilige Aufgabe “fachlich” zuständigen CtrlOs und eventueller passend definierter Unterobjekte abzuwickeln. Das CtrlO entspricht somit einem (Singleton-) Container, der die Funktionalität zur Abwicklung aller auftretenden Ereignisse und Aufgaben in geordneter und wiederverwendbarer Weise kapselt. (Über den “prototype”-Mechanismus lässt sich dann natürlich auch eine “Vererbung” in komplexeren Projekten realisieren.)

Nun ist jQuery ursprünglich nicht auf eine solche Logik ausgerichtet; es sind daher ein paar Klimmzüge erforderlich, um HTML-Objekte einer Webseite mit Methoden abstrakter JS-Objekte zu koppeln. Vor allem der this-Operator und sein unter jQuery oftmals wechselnder Kontext führen dabei immer wieder zu Missverständnissen und Kapriolen.

So lenken jQuery’s Event-Behandlungs-Funktionen, die für jQuery-Objekte (Selektionen) ausgeführt werden, den this-Kontext in den aufgerufenen Callback-Funktionen auf das jeweilige HTML-Element um. Sind die Callbacks nun Methoden eines CtrlO-Objekts, so passt dies meist nicht zu der Erwartung, die den Entwickler beim Methoden-Design geleitet haben dürfte – nämlich, dass this in der Methode auf das CtrlO-Objekt selbst verweist.

Ich werfe daher zunächst einen Blick auf den $.proxy()-Mechanismus, der einem unter jQuery zumindest im Fall des Event-Handlings hilft, die erforderlichen Aufgaben an CtrlO-Methoden zu delegieren und den Kontext des this-Operators gezielt auf das CtrlO selbst zu setzen.

Danach wende ich mich einer Frage zu, mit der man es auch immer wieder zu tun bekommt:

Wenn man innerhalb einer CtrlO-Methode über eine Selektion von HTML-Objekten mittels der jQuery-Objekt-Funktion $(..).each( …) iteriert – wie delegiert man dann die anstehenden Aufgaben zur Behandlung der selektierten HTML-Objekte an eine (andere) geeignete “Methode” des abstrakten CtrlOs?
Und wie nutzt man dabei den this-Operator, so dass wir einerseits eine Referenz auf das CtrlO selbst und andererseits aber auch eine Referenz auf das aktuell zu behandelnde HTML-Objekt erhalten?

Wir werden sehen, dass sich der Aufruf der gewünschten CtrlO-Methode nicht ganz so bequem wie im Falle der Event-Behandlung über den $.proxy()-Mechanismus bewerkstelligen lässt; wir werden aber auch sehen, dass ein solcher Aufruf über einen kleinen Trick durchaus auf einfache Weise und ohne Informationsverlust möglich ist.

CtrlOs und Einsatz des $.proxy()-Verfahrens zur Delegation der Event-Behandlung an Objekt-Methoden bei gleichzeitiger Kontrolle des this-Operators

Das Abfangen von Events und die anschließende Delegation der funktionalen Bearbeitung an Methoden eines CtrlOs kann man relativ bequem über den $.proxy()-Mechanismus erledigen. Man registriert dazu im Rahmen der Definition der Event-Behandlung mittels geeigneter jQuery-Objekt-Funktionen (wie etwa “click()” ) die Funktion $.proxy() als primären Callback der Event-Behandlungs-Funktion – also z.B. click( $.proxy(), …). Der Funktion $.proxy() selbst wird dann einerseits eine Kontext-Referenz und eine Methode des CtrlO zur Event-Behandlung als Callback übergeben. Diese Verschachtelung von Callbacks hört sich kompliziert an – sie ist faktisch aber sehr bequem. Der Ansatz führt typischerweise zu Code-Abschnitten der unten angegebenen Art.

Voraussetzungen: GOC (“Global Object Controller”) definiert im Beispiel ein globales Objekt; um ein Zumüllen des globalen Kontextes zu vermeiden, werden die benötigten CtrlOs im GOC erzeugt. Wir nehmen für unser Beispiel vereinfachend an, dass die IDs benötigter HTML-Elemente über irgendwelche Parameter ermittelbar sind.


GOC = new Constr_GOC(); 

function Constr_GOC() {
    ....	
    this["CtrlO_Form"] = new Constr_CtrlO_Form( ...parameterS ...); 
    ....
    this["CtrlO_Form"].register_form_events(); 
    ...
}

//  Constructor of a CtrlO for a certain form-Tag  
// -----------------------------------------------
function Constr_CtrlO_Form(... parameters ...) {
    ....
    // gather or define concrete IDs of relevant HTML-objects 
    // by some appropriate evaluation methods
  
    this.id_form     = "#" + " ... (evaluation of parameters) ..."; 	
    this.id_rad_stat = "#" + ...; 	
    this.id_rad_heur = "#" + ...;
    this.id_but_chk  = "#" + ...;
    .....
    ....
}

// Proxy solution to register event handlers for HTML-elements   
// -----------------------------------------------------------
// The behaviour of the HTML-elements shall be controlled by CtrlO-methods 

Constr_CtrlO_Form.prototype.register_form_events = function() {
	
	console.log("From Form - registering event handlers for some key elements of the form");
		
	// Click on any of the radio buttons
	// ----------------------------------
       	$(this.id_rad_stat).click(
       	    jQuery.proxy(this, 'radio_click')
      	);
        $(this.id_rad_heur).click(
            jQuery.proxy(this, 'radio_click')
        );
        $(this.id_rad_both).click(
            jQuery.proxy(this, 'radio_click')
        );
			
	// Button_Click 
	// ----------------
	$(this.id_but_chk).click(
	    jQuery.proxy(this, 'click_event')
	); 
		
	// Submit Event 
	// ------------
	$(this.id_form).submit(
	    jQuery.proxy(this, 'submit_ajax_chkfiles')
	); 
			
};	

// 
Methods of the CtrlO 
// ------------------------------- 
Constr_CtrlO_Form.prototype.radio_click = function(e) {
    .....
    e.preventDefault(); 
    // get the JS-pointer to the HTML-Object for which the event e was triggered   	
    var pointerToHtmlObject = e.target;  	
    ....
    // var attribute = $(pointerToHtmlObject).attr(.....); 
    ....	
}
Constr_CtrlO_Form.prototype.click_event = function(e) {
    ....
}
Constr_CtrlO_Form.prototype.submit_ajax_chkfiles = function(e) {
    ...
}

 
Zuerst werden die IDs relevanter HTML-Schlüssel-Objekte anhand verfahrensspezifischer Kriterien identifiziert. Dann werden Event-Listener und -Handler für diese HTML-Objekte über geeignete jQuery-Funktionen registriert. Die Callbacks ‘radio_click’, ‘click_event’, ‘submit_ajax_chkfiles’ entsprechen im Beispiel Methoden, die über den prototype der Konstruktorfunktion für das instanziierte CtrlO bereitgestellt werden. Der this-Parameter, der an die $.proxy()-Funktion übergeben wurde, weist dabei auf das Objekt “CtrlO_Form” – dieses wiederum ist ein Element des GOC.

Der Aufruf von Callbacks als Event-Handler über jQuery-Objekt-Funktionen wie “.click()” führt dazu, dass der this-Operator innerhalb der im Eventfall ausgelösten Funktion auf das HTML-Objekt (!) verweist. Dies gilt auch, wenn der Callback eine Objektmethode sein sollte. jQuery setzt intern den Kontext für den this-Operator in expliziter Weise! Das führt regelmäßig zur Problemen, da ein Entwickler bei der Ausformulierung von Methoden als Teil des “prototype” eines Konstruktors in der Regel erwartet, dass this auf das (später) erzeugte Objekt zeigen wird.

Das Zwischenschalten der $.proxy()-Funktion sorgt im obigen Beispiel deshalb dafür, dass die jeweils genannte Methode zur Eventbehandlung aufgerufen wird und dass der this-Operator innerhalb der aufgerufenen Methode (wie erwartet) auf das CtrlO und nicht auf das HTML-Objekt zeigt, für das der Event ausgelöst wurde.

Wie aber erhält man nun in der CtrlO-Methode einen Zeiger auf das HTML-Object? Nun dies ist über das Event-Objekt von JS möglich, dass der Methode über den proxy()-Mechanismus automatisch übergeben wird:

Constr_CtrlO_Form.prototype.radio_click = function(e) {...}

e.target liefert dann den gewünschten Zeiger auf das HTML-Objekt. Dieser Zeiger lässt sich dann wiederum für jQuery-Selektionen und zugehörige Funktionen einsetzen.

Der $.proxy()-Mechanismus erlaubt so die vollständige Übergabe der Event-Behandlung an Methoden abstrakter Objekte. Das Verfahren lässt sich im Gegensatz zum Beispiel natürlich auch auf den Aufruf der Methoden anderer Objekte als des aktuell aktiven Objekts verallgemeinern.

jQuery’s .each()-Funktion und der this-Operator

Das obige Beispiel hat verdeutlicht, dass die Kontrolle über den this-Operator für die Delegation der Eventbehandlung an abstrakte Objekt-Methoden wichtig ist.

Nun delegiert auch die Funktion .each() eines jQuery-Objekts (sprich einer Selektion), die Durchführung von Aufgaben im Zusammenhang mit jedem der identifizierten HTML-Objekten an eine Funktion, die als Callback mit vordefinierten Parametern – nämlich (index, element) – festgelegt werden muss (s. https://api.jquery.com/each/). Wie lenkt man die Aufgabendurchführung aber statt dessen auf eine passende Methode eines CtrlOs um? Und wohin weist der this-Operator innerhalb der zu definierenden Callback-Funktion?

Betrachten wir zunächst, wie die .each()-Funktion im Zusammenhang mit Selektionen praktisch zum Einsatz kommt. Wir nehmen im nachfolgenden Beispiel an, dass bestimmte IMG-Tags einer Bilder-Galerie durch die CSS-Klasse “enlarge” gekennzeichnet sein sollen. Die Verwendung von .each() sieht dann im einfachsten Fall etwa so
aus:


// iterate over all (IMG-) tags with css class="enlarge"
// -----------------------------------------------------
$(".enlarge").each(
	function () {
		// Note: this in this function points to the HTML object, here an identified IMG tag 
		.....
		// Code to perform actions, e.g. on the HTML object
		// console.log("id des HTML-Elements : " + $(this).attr("id"); 
		..... 	
	}	
);

 
Wir führen eine Selektion durch und definieren innerhalb von each() eine Callback-Funktion, die dann bestimmte Dinge im Zusammenhang mit jedem der ermittelten HTML-Objekte des DOM-Baums der Webseite durchführt. Z.B. könnten wir dessen Attribute oder CSS-Eigenschaften verändern oder aber Event-Listener implementieren, etc..

Wichtig ist, sich zu vergegenwärtigen, dass der this-Operator innerhalb der Funktion “function() {…}”, die pro Element der Selektion ausgeführt werden soll, auf das aktuell betroffene HTML-Element verweist! Gemeint ist hier tatsächlich die JS-Referenz auf den DOM-Knoten – und kein JQuery-Objekt einer Selektion. Dieser Unterschied ist wichtig : Will man nämlich jQuery-Funktionen, die für Selektionen (also ein jQuery-Objekt) definiert sind, anwenden, so ist zuvor $(this) zu bilden !

“function() {…}” muss bestimmte formale Voraussetzungen bzgl. der Parameter erfüllen. Deshalb kann man “function() {…}” nicht so ohne weiteres durch Callback-Verweise auf beliebige Funktionsobjekte des JS-Codes ersetzen. Es ergibt sich daher die Frage:

Wie kann man über die innerhalb von each() definierten Callback-Funktion “function() {…}” Methoden eines CtrlOs aufrufen und die Referenz auf den DOM-Knoten dabei weiterverwenden?

Nehmen wir mal an, unser Code-Schnipsel von oben sei Teil einer Methode eines CtrlOs, welches wiederum als Element eines globalen Objects GOC definiert sei. Innerhalb des CtrlO-Konstruktors werde im Beispiel eine Initialisierungs-Methode “init_img_control()” aufgerufen; sie möge die Selektion von (IMG-) Tags anhand der CSS-Klase “enlarge” durchführen und pro (IMG-) Tag Aktionen zur späteren Bildkontrolle ausführen. Diese Aktionen seien in der Methode “set_img_nr()” des CtrlO definiert:


GOC = new Constr_GOC(); 

function Constr_GOC() {

    ....
    this.CtrlO_Manipulate_Pics = new this['Constr_CtrlO_Manipulate_Pics']( ...parameter, ...);
    ....
    ....
    // Konstruktor eines CtrlO - this refers to the GOC 
    // -----------------------
    this.Constr_CtrlO_Manipulate_Pics = function(.. parameter, ...) {
        ....
        var numOfImgs = 0; 
        ....
        // "this" refers here to the CtrlO which is created via the present constructor 		
        ...
        this.init_img_control();
        .... 
    };

    // CtrlO-Methode init_img_control()
    // -----------------------------------	
    this['Constr_CtrlO_Manipulate_Pics'].prototype.init_img_control = function() {
	
        // this refers to the CtrlO
        ....
        $(".enlarge").each(
            function () {
                // Note: this points here to an HTML object, 
                // an identified img tag with the CSS class "enlarge" 
                ....
                // Code to perform actions, e.g. on the HTML object

                // console.log("id des HTML-Elements : " + $(this).attr("id"); 

                // ... We WANT TO CALL set_img_nr() - BUT HOW TO DO IT PROPERLY ??? ....
                .....
            }	
        );
        .....
        .....
    };

    // CtrlO-Methode set_img_nr() - to be used for each IMG-tag
    // -----------------------------------------------------------	
    this['Constr_CtrlO_Manipulate_Pics'].prototype.set_img_nr = function(ref_to_img_tag) {
        ....
        var img_nr = 0; 
        // Count the number of identified IMG tags
        this.numOfImgs++; 
        img_nr = this.numOfImgs; 
        ... 
        $(ref_to_img_tag).attr("imgNum", img_nr.toString() );  
        ....
    };

}

 
Wir haben hier nebenbei für Interessierte noch eine weitere zusätzliche Kapselung im GOC vorgeführt:

Die Konstruktor-Funktion für das CtrlO kann selbst als Element des globalen GOC definiert werden. Das GOC-Objekt erzeugt dann das CtrlO als weiteres Unterobjekt durch expliziten Verweis auf das als Konstruktor zu verwendende Funktionsobjekt (this[“Name_des_Funktionsobjekts“]) nach dem new-Operator. Dass sowas tatsächlich geht, ist hier aber nur eine Randnotiz (siehe hierzu den Blog-Artikel Javascript: Einsatz des new-Operators mit Variablen und dynamischer Vorgabe der Konstruktorfunktion).

Im Beispiel wird per “$(..).each()”-Funktion über selektierten Objekte iteriert; wir möchten dabei z.B. gerne jedes der per $(“.enlarge”) ermittelten <IMG>-Tags um eine neues künstliches Attribut ergänzen; so könnten wir in diesem Attribut etwa eine Nummer hinterlegen, die wir später zur Identifikation des Bildes nutzen. Die Attribut-Ergänzung soll im Beispiel über die Methode “set_img_nr()” des CtrlOs GOC[“CtrlO_Manipulate_Pics”] geschehen.

Das Dumme ist nun, dass this innerhalb von “function() {…}” auf das aktuell identifizierte HTML-Element – also ein <IMG class=”enlarge” … >-Tag – verweist. Wir müssen das CtrlO daher anders referenzieren. Aus Gründen der besseren Wiederverwendbarkeit wollen wir zur Identifikation des CtrlOs aber keinen Bezug auf eine explizite Bezeichnung von Unterobjekten des GOC heranziehen; wir woll vielmehr auf ein geeignetes “this” zurückgreifen, das unmittelbar auf das CtrlO verweist. Andererseits soll aber die CtrlO-Methode “set_img_nr()” auch einen Verweis auf den aktuellen HTML-Knoten (also das IMG-Tag) als Parameter erhalten.

Ein wenig Nachdenken führt zu folgender Lösung für die Methode init_img_control() :


    this['Constr_CtrlO_Manipulate_Pics'].prototype.init_img_control = function() {
	
        // this still refers to the CtrlO
        ....
        ....
        // Use a helper variable to store the reference to the CtrlO

       var this_CtrlO = this;  // "this_CtrlO" points to the CtrlO (=GOC["CtrlO_Manipulate_Pics"])
                               // we could have called the helper variable also "that"
        ....
        $(".enlarge").each(
            function () {
                // Note: "this" now points to the HTML object, here an identified img tag 
                // Note: the variable "this_CtrlO" is found by implicit search in nested JS-functions 
                ....				
                .....
                // Code to perform actions, e.g. on the HTML object
                // console.log("id des HTML-Elements : " + $(this).attr("id");
                .....
                // we refer to the appropriate method of the CtrlO via the helper variable  
                // and provide the reference to the <IMG> tag (=this) as a parameter 

                this_CtrlO.set_img_nr(this); 
                ....
            }	
        );
        .....	
    };

 
Alles gut!
Der Profi erkennt sofort den klassischen Trick: Wir merken uns vor der Durchführung der jQuery-Selektion die Referenz auf das CtrlO in einer lokalen Hilfs-Variablen “this_CtrlO”. Da wir uns vor der Selektion ja noch im Kontext
einer gewöhnlichen Objekt-Methode befinden, verweist “this” dort ja noch auf das übergeordnete Objekt.

In der Callback-Funktion “function() {…}”, die innerhalb von .each() definiert wird, rufen wir dann die gewünschte Methode des CtrlO explizit unter Nutzung der Objekt-Referenz “this_CtrlO.Methodenname” auf. Die Variable wird im Lösungsansatz durch implizite Suche im Kontext der umgebenden, kapselnden Funktion init_img_control() gefunden.

Innerhalb der Callback-Funktion “function() {…}” verweist this nun jedoch auf das aktuell zu behandelnde HTML-Objekt der Selektion. Diese Referenz auf das HTML-Objekt können wir deshalb als Parameter (this) an die Objektmethode übergeben. Das zu bearbeitende HTML-Objekt kann danach auch in der CtrlO-Methode eindeutig identifiziert und bearbeitet werden.

Hinweis: Das zwischenzeitliche Speichern der this-Referenz in einer anderen Variablen entspricht in anderen Lehrbuchbeispielen oft dem Einsatz einer Hilfsvariablen “that”.

Unschöne impliziter Variablen-Suche

Wir haben uns mit dem obigen Lösungsansatz allerdings ein unschönes Thema für die Code-Pflege eingehandelt, das mit dem Hoisting von Variablen in Funktionen zu tun hat und in komplexeren Codes bei Unachtsamkeit zu Problemen führen kann: Die Variable “this_CtrlO” darf innerhalb von “function() {…}” an keiner Stelle über var-Statements redefiniert werden – auch nicht nach dem Statement “this_CtrlO.set_img_nr(this);”. Man erkennt leider nicht zwingend, dass es sich eigentlich um einen Parameter handelt, der nach unten durchgereicht wird. Das würde man gerne etwas expliziter sehen.

Man könnte diesem Thema einerseits dadurch ausweichen, dass man die Werte in Variablen speichert, die über das GOC explizit adressiert werden. Das würde unsere Kapselung aber auch wieder durchbrechen. Besser erscheint eine Übergabe der Referenz als Parameter in den each()-Bereich. Ein anderer Ansatz besteht deshalb in der expliziten Übergabe an eine sog. “immediate function” – das ist zwar auch nicht optimal, aber schon ein wenig besser. Man erkennt die Variable wenigstens als Parameter. Ganz nach unten durchreichen kann man die Referenz auf das CtrlO in diesem Falle halt leider nicht.


    this['Constr_CtrlO_Manipulate_Pics'].prototype.init_img_control = function() {

        // this still refers to the CtrlO
        ....
        ....
        // Use a helper variable to store the reference to the CtrlO

        var this_CtrlO = this;  // "this_CtrlO" points to the CtrlO (=GOC["CtrlO_Manipulate_Pics"])
                                // we could have called the helper variable also "that"
        ....
        ( function (pointer_CtrlO) {	
              $(".enlarge").each(
                  function () {
                      // Note: "this" now points to the HTML object, here an identified img tag 
	
                      // get external reference to the CtrlO
                      var CtrlO = pointer_CtrlO; // best at the top of the function code 
                      ....				
                      .....
                      CtrlO.set_img_nr(this); 
                      ....
                  }	
              );
          }
        )(this_CtrlO);
        .....	
    };

 
Man beachte: Die “immediate function” wird im Kontext des vorliegenden Beispiels in der Initialisierungssfunktion “init_img_control” nur genau einmal ausgeführt.

Fazit

Über die Callback-Funktion, die innerhalb der “.each()”-Funktion für Selektionen zu definieren ist, lassen sich auch Methoden eines abstrakten Control Objects [CtrlO] zur Behandlung der selektierten HTML-Objekte aufrufen. Das gilt selbst dann, wenn die Selektion $(..).each( ..) innerhalb einer anderen
Methode desselben CtrlOs vorgenommen wird. Dem Kontextwechsel des this-Operators in den verschiedenen Code-Bereichen der Funktionen beugt man durch rechtzeitige Zwischenspeicherung in einer geeigneten lokalen Hilfsvariable im Scope der kapselnden Methode vor. Die this-Referenz auf das selektierte, aktuell zu behandelnde HTML-Objekt kann man den CtrlO-Methoden als Parameter übergeben. Damit können Aufgaben, die im Rahmen von .each() durchzuführen sind, vollständig an geeignete Methoden von Control Objects delegiert werden.

Viel Spaß weiterhin mit jQuery, Javascript und der Kapselung von Funktionalität in Objekt-Methoden.