JavaScript/Tutorials/Fader-Framework/objektorientierter Ansatz

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche
Empfehlung: Folgende Artikel können zum Verständnis wichtig sein:

Es wurde im vorherigen Beispiel mit globalen Variablen gearbeitet, was spätestens dann zu Problemen führen muss, wenn mehr als eine Slideshow auf einer Seite ablaufen soll. Denn: die eine Slideshow würde nach obigem Modell der anderen Slideshow die globalen Variablen umschreiben.

Eine Lösung für dieses Dilemma wäre entweder

  • für jede Slideshow ein eigenes Script mit eigenen Variablennamen, oder
  • für jede Slideshow ein eigenes Objekt anlegen, das seine "Variablen" intern verwaltet.

Daher wird jetzt aus obigem Beispiel eine objektorientierte Lösung gebaut.

Es gibt mehrere Wege in JavaScript, um ein Objekt zu erstellen. In seinem Artikel Organisation von JavaScripten geht Mathias Schäfer auf die verschiedenen Möglichkeiten und ihre wesentlichen Unterschiede ein. Um nun obiges Beispielscript objektorientiert umzusetzen, eignet sich der Einsatz eines eigene-objekte Konstruktors am besten.

Objekte mit einem Konstruktor erzeugen[Bearbeiten]

In Javascript ist jede Funktion ein Konstruktor. Mit dem Schlüsselwort new erzeugt man Exemplare von dem Typ des Konstruktors. Mit dem Aufrufen der Konstruktorfunktion wird das neue Objekt initialisiert. Im Wesentlichen sieht ein Konstruktor so aus:

Beispiel
function Konstruktor (wert) {
    this.produkt = wert;
}

var meinObjekt = new Konstruktor("meinWert");

alert(meinObjekt.produkt); // erzeugt die Meldung "meinWert"

Eine Konstruktor-Funktion gibt als "Ergebnis" eine neue Instanz eines Objektes zurück, wenn man sie mit dem Schlüsselwort new aufruft (meinObjekt = new Konstruktor()). In der Konstruktorfunktion ist das Schlüsselwort this die Referenz auf das aktuelle Objekt, in dem Beispiel also später auf meinObjekt. Der Konstruktorfunktion können dort wie bei einer normalen Funktion Parameter übergeben werden. Hier wird dieser Parameter der Objekteigenschaft produkt zugewiesen. Das aktuelle Objekt meinObjekt hat nun die Eigenschaft produkt mit dem Wert meinWert.

Fader-Konstruktor[Bearbeiten]

Wie sieht nun der Konstruktor für einen Fader nach obigem Beispiel aus? Betrachten Sie folgenden Code, indem die zuvor besprochenen Fader-Funktionen integriert wurden:

Beispiel ansehen …
<!doctype html>
<html><head>
    <meta charset="utf-8">
    <title>Ein objektorientierter Fader</title>
    <script>
        function Fader(id) {
            this.id = id;

            this.images = document.getElementById(id).getElementsByTagName("img");
            this.counter = 0;

            this.fade = function (step) {
                var fader = this;

                step = step || 0;

                this.images[this.counter].style.opacity = step/100;
                this.images[this.counter].style.filter = "alpha(opacity=" + step + ")"; // IE

                step = step + 2;

                if (step <= 100) {
                    window.setTimeout(function () { fader.fade(step); }, 1);

                } else {
                    window.setTimeout(function () { fader.next(); }, 2000);
                }
            };

            this.next = function () {
                this.counter++;

                if (this.counter < this.images.length) {

                    this.fade();
                }
            };
        }
    </script>

    <style>
        ...
    </style>
</head><body>
    <h1>Ein objektorientierter Fader</h1>
    <p id="meinFader">

        <img src="images/berge1.jpg" alt="">
        <img src="images/berge2.jpg" alt="" class="next">
        <img src="images/berge3.jpg" alt="" class="next">
        <img src="images/berge4.jpg" alt="" class="next">
        <img src="images/berge5.jpg" alt="" class="next">
        <img src="images/berge6.jpg" alt="" class="next">

    </p>
    <p>
        <a href="#" onclick="window.meinFader = new Fader('meinFader');
            meinFader.next();
            this.onclick = function () { return false; };
            return false;">Slideshow starten</a>
    </p>
</body></html>

Die bisher global angelegten Funktionen fade und next werden nun als Methoden des Fader-Objektes definiert. Sie werden zunächst als anonyme Funktionen mit function (...) { ... } definiert und mittels der Zuweisung this.<Funktionsname> = function (...) { ... }; als Methode des Objektes gespeichert. Auch die bisher globale Variable counter wurde als this.counter zu einer Eigenschaft des Fader-Objektes. Außerdem wurde dem Fader-Objekt noch eine Eigenschaft id spendiert. Über diesen Namen findet das Script das passende HTML-Element im Dokument, in dem sich die Bilder der Slideshow befinden. Außerdem korrespondiert der Name im obigen Beispiel mit dem globalen Objektnamen, unter dem das frisch erstellte Fader-Objekt als Eigenschaft von window gespeichert wird.

Neu hinzugekommen ist in der Methode fade die Variable fader, die auf das Fader-Objekt selbst verweist. Diese wird benötigt, damit in der anonymen Funktion, die window.setTimeout als erster Parameter übermittelt wird, der Zugriff auf das entsprechende Fader-Objekt möglich ist. Die Variable wird in der anonymen Funktion anstelle des Schlüsselwortes this verwendet, denn this verweist innerhalb dieser Funktion nicht auf das Fader-Objekt, sondern auf window. Wie vorher bereits besprochen ist dieser anonymen Funktion die Variable fader bekannt, da sie sie durch die Verschachtelung in der Methode fade »einschließt« und konserviert. Beim Aufruf der anonymen Funktion "weiß" diese, welches Fader-Objekt gemeint war. (In diesem Falle gibt es zwar nur ein mögliches Fader-Objekt, aber die fader wird auch bei mehreren Fadern auf das korrekte Objekt verweisen.)

Beachten Sie: Der Fader kann erst nach dem vollständigen Laden der Seite initialisiert werden, da der Zugriff auf das HTML-Dokument mit der ID "meinFader" vorher nicht möglich ist.

Deshalb wurde die Initialisierung in das onclick-Event des Links verpackt, wo der Fader erstellt und dann auch gleich gestartet wird. Um ein erneutes Klicken auf den Link wirkungslos zu machen, wird dieser Event-Handler beim ersten Aufruf mit einer neuen Funktion überschrieben, die ihn sozusagen "deaktiviert".

Die entsprechenden Zugriffe oder Abfragen in den Funktionen wurden selbstverständlich auch angepasst! So steht beispielsweise in der Funktion next nicht mehr fade(), sondern this.fade().

Das Schlüsselwort this ist sehr stark kontextabhängig! Es bezieht sich grundsätzlich auf dasjenige Objekt, bei dem die gegenwärtige Funktion als Methode definiert worden ist. Wenn eine Funktion an keinem bestimmten Objekt gespeichert wurde oder nicht explizit als dessen Methode aufgerufen wird, dann verweist this auf das Objekt window. Deshalb muss man sehr genau überlegen, in welchem Kontext eine Funktion definiert wurde bzw. in welchem Kontext sie aufgerufen wurde.

In diesem Artikel betrifft dies immer Fälle, in denen ein Timeout benutzt wurde, um eine Methode des Fader-Objektes aufzurufen. Damit this auf das entsprechende Fader-Objekt zeigt, muss wie im Beispiel window.setTimeout(function () { fader.next(); }, 2000); notiert werden. Die anonyme Funktion schließt wie gesagt die Variable fader ein und hat darüber Zugriff auf das Fader-Objekt. Nur durch die Aufrufweise fader.next(); ist gewährleistet, dass this auf das korrekte Fader-Objekt zeigt. Es würde nicht ausreichen, bloß window.setTimeout(fader.next, 2000); zu schreiben, auch wenn dies naheliegt. In diesem Fall würde die Methode aus ihrem Kontext, dem Fader-Objekt, gerissen und sie würde nicht als dessen Methode ausgeführt. Das bedeutet, dass this darin auf window zeigen würde.

Objektorientierter Vorteil: Mühelos mehrere Slideshows auf einer Seite[Bearbeiten]

Da ein Fader nun bequem über den Konstruktor herstellbar ist, außerdem alle seine Komponenten als Eigenschaften oder Methoden unter sich vereint, ist es sehr einfach möglich, mehrere Fader auf der selben Seite ablaufen zu lassen.

Um einen Bezug zwischen dem HTML-Element und seinem Fader herzustellen, wird die id-Eigenschaft des jeweiligen Faders benutzt. Ein HTML-Element mit der ID "SlideShow_x" bekäme dann ein Fader-Objekt zugewiesen, das ebenso die ID "SlideShow_x" trüge, und das über window.SlideShow_x erreichbar wäre. Man könnte es also mittels window.SlideShow_x.next() zu einem Bilderwechsel bringen. Das folgende Beispiel macht dies praktisch deutlich.

Beispiel ansehen …
<!doctype html>
<html><head>
    <meta charset="utf-8">
    <title>Zwei Fader auf derselben Seite</title>
    <script>
        function Fader(id) {
            ...
        }
    </script>
    <style>
        .next { position: absolute; top: 0; left: 0; opacity: 0; filter:alpha(opacity=0); }
        #erste_slideshow, #zweite_slideshow { position: relative; }
    </style>
</head><body>
    <h1>Zwei Fader auf derselben Seite</h1>

    <p id="erste_slideshow">
        <img src="images/fliegen1.jpg" alt="">
        <img src="images/fliegen2.jpg" alt="" class="next">
        <img src="images/fliegen3.jpg" alt="" class="next">
        <img src="images/fliegen4.jpg" alt="" class="next">
        <img src="images/fliegen5.jpg" alt="" class="next">
        <img src="images/fliegen6.jpg" alt="" class="next">
    </p>
    <p id="zweite_slideshow">
        <img src="images/berge1.jpg" alt="">
        <img src="images/berge2.jpg" alt="" class="next">
        <img src="images/berge3.jpg" alt="" class="next">
        <img src="images/berge4.jpg" alt="" class="next">
        <img src="images/berge5.jpg" alt="" class="next">
        <img src="images/berge6.jpg" alt="" class="next">
    </p>
    <p>
        Erster Fader:
        <a href="#" onclick="window.erste_slideshow = new Fader('erste_slideshow');
            erste_slideshow.next();
            this.onclick = function () { return false; };
            return false;">Slideshow starten</a>
    </p>

    <p>
        Zweiter Fader:
        <a href="#" onclick="window.zweite_slideshow = new Fader('zweite_slideshow');
            zweite_slideshow.next();
            this.onclick = function () { return false; };
            return false;">Slideshow starten</a>
    </p>
</body></html>

Im Dokument werden zwei Textabsätze jeweils mit einer ID versehen. Diese ID wird weiter benutzt, um jedem der beiden Absätze einen namentlich passenden Fader zu erzeugen, der außerdem auch das namentlich passende HTML-Element findet. Dabei kommt der unveränderte Konstruktor aus dem vorherigen Beispiel zum Einsatz.

Beachten Sie: Bei der gegenwärtig Lösung wurde wie auch im Beispiel zuvor die ID des HTML-Elementes als "Name" für den Fadern benutzt

Das muss nicht unbedingt so sein. Ein Fader kann auch mit einem anderen Namen erzeugt werden: window.meinFader = new Fader("voellig_andere_id");

Der Internet Explorer hat die unangenehme Eigenschaft, Elemente mit einer ID von sich aus als direkte Unterobjekte von window einzurichten. Im obigen Beispiel bedeutet das, dass window.erste_slideshow schon vor dem Erzeugen und Speichern des Fader-Objektes existiert. Und zwar ist darin das p-Elementobjekt mit der gleichnamigen ID gespeichert. Daher kann eine Prüfung auf das Vorhandensein von window["id-des-HTML-Elementes"] positiv ausfallen, obwohl noch kein Fader mit diesem Namen eingerichtet wurde. In diesem Falle "findet" der Internet Explorer nämlich das HTML-Element mit dieser ID.

Bisheriger Stand des Projektes[Bearbeiten]

Die bisherige Vorgehensweise erforderte immer die vollständige Notation aller Bilder einer Slideshow als <img>-Elemente im entsprechenden HTML-Element. Es ist nun die Frage, ob dieser Umstand auch immer wünschenswert ist. Immer dann, wenn JavaScript nicht verfügbar ist (aus welchen Gründen auch immer), könnte das eventuell zu unerwünschten Dokumentstrukturen führen. Dazu ein paar Gedanken.

Je nach Einsatzzweck kann ein Fader tatsächlich eine Bildergalerie "aufhübschen", indem er die Bilder innerhalb einer Slideshow nacheinander mit einem schicken Überblendungseffekt präsentiert. Wenn JavaScript nicht verfügbar ist, dann sind trotzdem alle Bilder vom Besucher (oder einer Suchmaschine) auffindbar. Das bedeutet, dass das Vorhandensein der <img>-Elemente im Dokument sinnvoll und gewünscht ist.

Anders sieht es aus, wenn eine Slideshow lediglich zu Designzwecken als illustratives Gestaltungselement eingesetzt werden soll. In diesem Falle würden die Bildelemente stören, da sie keinen inhaltlichen Sinn in diesem Dokument hätten. Hier wäre ein Vorhandensein aller <img>-Elemente im Dokument gerade nicht sinnvoll.

Ein echter Schwachpunkt ist, dass nach dem letzten Bild der Slideshow nicht automatisch von vorne begonnen werden kann, da immer nur das darüberliegende Bild sichtbar gemacht wird, sodass beim Wechsel vom obersten Bild zurück auf das erste Bild kein Übergang mit dem bisherigen Aufbau der fade-Methode möglich ist.

Ein weiterer Schwachpunkt ist, dass eine Slideshow nicht automatisch nach dem Laden der Seite ablaufen kann. Die Initialisierung der Fader geschieht während des Ladevorgangs, wenn das Dokument noch längst nicht vollständig geladen ist. In den bisherigen Beispielen war der JavaScript-Code im head des Dokuments notiert, sodass bei der Initialisierung noch kein Zugriff auf den body im Dokument möglich ist. Zu diesem Zeitpunkt existiert das HTML-Element mit der angegebenen ID natürlich auch noch nicht. Für die sofortige Animation ist ein Zugriff auf das entsprechende Element jedoch unverzichtbar, sodass ein automatisches Starten eines Faders mit dem bisherigen Ansatz nicht möglich ist.

Der bisherige Ansatz muss also modifiziert werden!