Erstellung einer Web-App von Grund auf – Teil 6 von 8: Hinzufügen von AJAX Interaktivität

Avatar of Chris Coyier
Chris Coyier am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

👋 Hallo! Wir möchten Sie darauf hinweisen, dass der Quellcode, der diese Serie begleitet, nicht mehr zum Download verfügbar ist. Wir glauben, dass diese Serie immer noch wertvolle Informationen enthält. Da wir jedoch über 10 Jahre weiter sind, denken wir auch, dass es sich lohnt, ein modernes PHP-Framework (wie Laravel) oder sogar ein JavaScript-Framework (wie React oder Vue) für die Erstellung einer progressiven Web-App in Betracht zu ziehen.

Unser Entwickler hat bereits enorme Arbeit geleistet, um diese Idee in eine reale Anwendung zu verwandeln. Nun machen wir ihm noch ein wenig mehr Arbeit! Der wichtigste Teil dieser App ist die Erstellung und Verwaltung Ihrer Liste. Wir haben von Anfang an entschieden, dass dies eine AJAX-basierte App sein wird. Wir haben uns nicht für AJAX entschieden, weil es ein populäres Schlagwort ist, sondern weil wir wissen, dass es der beste Weg ist, eine reaktionsschnelle, einfach zu bedienende und natürlich wirkende Anwendung im Web zu erstellen.

Artikelserie

  1. Planung der App: Grundidee und Design
  2. Planung der App: Datenbankarchitektur und Entwicklungsansatz
  3. Design der App: Workflow-Karte und Photoshop-Design
  4. Design der App: HTML und CSS
  5. Entwicklung der App: Benutzerinteraktion
  6. Entwicklung der App: AJAX-Interaktivität hinzufügen
  7. Entwicklung der App: Listeninteraktion
  8. Sicherheit & Die Zukunft

Die große Sache: Speichern der Liste

AJAX ermöglicht es uns, Anfragen an den Server zu senden und Antworten zu erhalten, ohne die Seite neu laden zu müssen. In unserer App wird diese Funktionalität hauptsächlich zum Speichern verwendet. Lassen Sie uns jeden Fall durchdenken, in dem etwas gespeichert werden muss.

  • Wenn ein neues Listenelement hinzugefügt wird, sollte es in der Liste gespeichert werden.
  • Wenn ein Listenelement gelöscht wird, sollte es aus der Liste gelöscht werden.
  • Wenn die Farbe eines Elements geändert wird, sollte die neue Farbe gespeichert werden.
  • Wenn ein Listenelement als erledigt markiert wird, sollte dieser Status gespeichert werden.
  • Wenn die Liste neu geordnet wird, sollte die neue Reihenfolge gespeichert werden.
  • Wenn der Text eines Listenelements geändert wird, sollte der neue Text gespeichert werden.

Das ist eine Menge von Speichervorgängen. Unser Entwickler hat viel zu tun, denn jedes dieser kleinen Ereignisse erfordert eine PHP-Datei, die bereit ist, diese Anfrage entgegenzunehmen und zu bearbeiten. Glücklicherweise hat er bereits einige coole objektorientierte Dinge im Einsatz und kann diese sicherlich erweitern, um damit umzugehen.

Interface JavaScript

Neben all diesen AJAX-Speichervorgängen gibt es all die Dinge, die dafür sorgen, dass die Benutzeroberfläche visuell das tut, was sie verspricht. Dieser kleine Drag-Tab deutet an, dass Listenelemente verschoben werden können. Wir sagen, dass wir nach diesem Vorgang die Liste speichern werden. Aber wie funktioniert das eigentlich? Keine Sorge, wir werden dazu kommen. Vorerst denken wir über all die Interface-JavaScript-Aufgaben nach, die wir brauchen.

  • Durch Klicken und Ziehen des Drag-Tabs können Listenelemente verschoben und neu angeordnet werden.
  • Durch Klicken auf den Farb-Tab wird die Farbe der Listenelemente zwischen einigen vordefinierten Optionen umgeschaltet.
  • Durch Klicken auf das Häkchen wird das Listenelement durchgestrichen und ausgeblendet.
  • Durch Klicken auf das X gleitet eine Bestätigung heraus. Erneutes Klicken und das Listenelement verschwindet.
  • Doppelklicken auf das Listenelement, der Text wird zu einem Texteingabefeld zur Bearbeitung.
  • Tippen Sie in das große Feld unten und klicken Sie auf Hinzufügen, wird ein neues Listenelement am Ende der Liste angehängt.

Und auch hier werden wir uns darum kümmern. Nur noch ein wenig mehr Vorbereitung!

Zuerst die Dinge: Aufrufen der JavaScript-Dateien

Es gibt wirklich nur eine Seite auf unserer Website, die Hauptlistenseite, die überhaupt JavaScript benötigt. Daher werden wir die Skriptdateien direkt in die index.php-Datei einfügen. Oft sieht man JavaScript-Dateien, die im Header von Websites verlinkt sind. Dies ist keine Zeit für eine ausführliche Diskussion darüber, aber es genügt zu sagen, dass dies nicht erforderlich ist, da es allgemein als Leistungsverbesserung angesehen wird, sie am Ende der Seiten aufzulisten. Das werden wir hier tun. In unserer index.php-Datei werden wir nach der Ausgabe der Liste das benötigte JavaScript aufrufen.

<script type='text/javascript' src='//ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3.2'></script>
<script type="text/javascript" src="js/jquery-ui-1.7.2.custom.min.js"></script>
<script type="text/javascript" src="js/jquery.jeditable.mini.js"></script>
<script type="text/javascript" src="js/lists.js"></script>
<script type="text/javascript">
    initialize();
</script>
  1. Laden Sie jQuery (von Google für bessere Geschwindigkeit)
  2. Laden Sie eine angepasste jQuery UI-Bibliothek (für die Drag-Funktionalität)
  3. Laden Sie das jEditable Plugin (für Klick-zu-Bearbeiten)
  4. Laden Sie unser benutzerdefiniertes Skript
  5. Rufen Sie die Initialisierungsfunktion auf (unseren eigenen Starter, ähnlich wie eine DOM-ready-Anweisung)

Bereinigen des Markups mit JavaScript

In unserer benutzerdefinierten lists.js-Datei erstellen wir eine Reihe von verschiedenen Funktionen. Die allererste ist die, die direkt in der index.php-Datei aufgerufen wird, nachdem die Listen ausgegeben wurden.

function initialize() {

};

Das Erste, was wir dort tun werden, ist, das Markup ein wenig aufzuräumen, indem wir gemeinsame Elemente mit JavaScript einfügen, anstatt sie direkt im Markup zu haben. Erinnern Sie sich, wie das Markup für die Liste aussah, als wir es skizziert haben?

<ul id="list">
   <li class="colorRed">
        <span>Walk the dog</span>
        <div class="draggertab tab"></div>
        <div class="colortab tab"></div>
        <div class="deletetab tab"></div>
        <div class="donetab tab"></div>
    </li>

    <!-- more list items -->

</ul>

Vieles davon ist redundant für alle Listenelemente. Was wir wollen, ist eher so etwas:

<ul id="list">
   <li class="colorRed">
        Walk the dog
    </li>

    <!-- more list items -->

</ul>

Alle Divs und der Span wurden entfernt. Der Klassenname auf dem Listenelement ist in Ordnung, da PHP dies für uns ausgibt, wenn es die Datenbank liest und die Liste ausgibt.

Wie hängen wir all dieses zusätzliche HTML an? Einfach mit jQuery. Zielen Sie auf die Listenelemente und wickeln Sie jedes der Innenteile mit der Funktion wrapInner() ein und fügen Sie die zusätzlichen Divs mit der Funktion append() hinzu.

function initialize() {

  // WRAP LIST TEXT IN A SPAN, AND APPLY FUNCTIONALITY TABS
  $("#list li")
    .wrapInner("<span>")
    .append("<div class='draggertab tab'></div><div class='colortab tab'></div></div><div class='deletetab tab'></div><div class='donetab tab'></div>");

};

Binden von Ereignissen an die neuen Funktions-Tabs, der intelligente Weg
Das Binden eines Ereignisses an ein HTML-Element ist in JavaScript ziemlich einfach. Es ist so:

$("li").click(function() {
   // do something
});

Daran ist nichts falsch, aber wir befinden uns in einer etwas einzigartigen Situation mit unserer Listen-App. Wenn Sie Ereignisse auf diese Weise binden, 1) wird für jedes einzelne Listenelement auf der Seite ein eindeutiger Ereignis-Handler erstellt, der jeweils Browserspeicher beansprucht, und 2) geschieht dies nur einmal für den aktuellen Zustand des DOM. Machen Sie sich darüber nicht zu viele Gedanken, der Punkt ist, dass das Binden von Ereignissen auf diese Weise für uns nicht ideal ist, da wir dynamisch neue Listenelemente einfügen werden.

Wenn ein Benutzer ein neues Listenelement einfügt, wird es sofort in das DOM eingefügt. Dieses neue Listenelement wird nicht wie die anderen gebunden, was bedeutet, dass all diese schicken kleinen Tabs nicht richtig funktionieren. Heul. Was können wir tun, um das zu lösen? Nun, wir können eine neue Funktion erstellen, die aufgerufen wird, wenn die Seite geladen wird und wenn neue Listenelemente angehängt werden, die all diese Ereignisbindungsarbeit leistet. Das wird definitiv funktionieren, aber… jQuery ist schlauer als das. jQuery bietet eine Funktion namens live(), die dieses Problem vollständig beseitigt.

$("li").live("click", function() {
   // do something
});

Das Binden von Ereignissen mit der live()-Funktion ist für uns fantastisch, weil 1) nur ein Ereignis-Handler erstellt wird, was viel effizienter ist, und 2) automatisch neue Elemente, die an die Seite angehängt werden, von demselben Handler gebunden werden. Killer.

Für unsere kleinen Funktions-Tabs werden wir sie also so verwenden:

$(".donetab").live("click", function() {
   // do stuff
});

$(".colortab").live("click", function(){
   // do stuff
});

$(".deletetab").live("click", function(){
   // do stuff
});

Der Drag-Tab hat kein Click-Ereignis, er wird tatsächlich die Drag-Funktionalität von jQuery UI verwenden, um seine Sache zu machen. Schauen wir uns das mal an.

Machen der Liste per Drag & Drop sortierbar

Mega Dank an jQuery UI für die Bereitstellung einer so nützlichen Sammlung von Funktionen. Das Draggable-Modul ist genau richtig, um eine Liste wie unsere sortierbar zu machen. Wir zielen auf das übergeordnete <ul>, sagen ihm, welchen "Handle" wir verwenden möchten (welchen Teil des Listenelements man anklicken und ziehen kann, um sie zu verschieben). Wir verwenden auch einen Parameter forcePlaceholderSize für visuelles Feedback, wenn die Listenelemente herumgezogen werden (ein weißer Blockplatz erscheint, um anzuzeigen, wo das Listenelement landen würde, wenn es losgelassen wird).

$("#list").sortable({
    handle   : ".draggertab",
    update   : function(event, ui){
       
        // Developer, this function fires after a list sort, commence list saving!

    },
    forcePlaceholderSize: true
});

Elemente als "erledigt" markieren

Wenn der Benutzer auf den kleinen Häkchen-Tab klickt, haben wir bereits entschieden, zwei Dinge zu tun. Die Liste durchstreichen und dann die gesamte Listenelement ausblenden. Aber es gibt auch die Überlegung, was zu tun ist, wenn das Element bereits als erledigt markiert ist und dieser Tab geklickt wird. Nun, wir werden es wieder aufheben und es wieder einblenden. Wenn also der Klick erfolgt, stellen wir sicher, dass wir zuerst prüfen, in welchem Zustand wir uns befinden.

$(".donetab").live("click", function() {

    if(!$(this).siblings('span').children('img.crossout').length) {
        $(this)
            .parent()
                .find("span")
                .append("<img src='/images/crossout.png' class='crossout' />")
                .find(".crossout")
                .animate({
                    width: "100%"
                })
                .end()
            .animate({
                opacity: "0.5"
            },
            "slow",
            "swing",
            function() {
                           
                // DEVELOPER, the user has marked this item as done, commence saving!

            })
    }
    else
    {
        $(this)
            .siblings('span')
                .find('img.crossout')
                    .remove()
                    .end()
                .animate({
                    opacity : 1
                },
                "slow",
                "swing",
                function() {
                           
                // DEVELOPER, the user has UNmarked this item as done, commence saving!

            })
            
    }
});

Farbwechsel

Wir sollten uns um den "farbigen" Teil von "Farbige Listen" kümmern, richtig? CSS wird die tatsächliche Farbe anwenden, so dass wir mit JavaScript nur die Klassennamen beim Klicken auf diese Listenelemente durchschalten.

$(".colortab").live("click", function(){

    $(this).parent().nextColor();

    $.ajax({
       
        // DEVELOPER, the user has toggled the color on this list item, commence saving!

    });
});

Diese nextColor()-Funktion ist keine eingebaute Funktion, sie wird von uns selbst geschrieben. Sie ist hier zur Klarheit des Codes abstrahiert. Die Art und Weise, wie wir sie hier verwendet haben (als Teil der "Kette"), bedeutet, dass wir ein kleines jQuery-Plugin daraus machen müssen. Kein Problem.

jQuery.fn.nextColor = function() {

    var curColor = $(this).attr("class");

    if (curColor == "colorBlue") {
        $(this).removeClass("colorBlue").addClass("colorYellow").attr("color","2");
    } else if (curColor == "colorYellow") {
        $(this).removeClass("colorYellow").addClass("colorRed").attr("color","3");
    } else if (curColor == "colorRed") {
        $(this).removeClass("colorRed").addClass("colorGreen").attr("color","4");
    } else {
        $(this).removeClass("colorGreen").addClass("colorBlue").attr("color","1");
    };

};

Grundsätzlich prüft dies, welche Farbe das Listenelement bereits hat, und wechselt zur nächsten Farbe. Beachten Sie, wie wir auch ein Attribut auf den Listenelementen ändern. Farbe ist kein gültiges Attribut in XHMTL (in HTML5 ist es in Ordnung), aber egal. Es ist nicht im Markup, also spielt es keine wirkliche Rolle. Warum benutzen wir das? Nun, weil wir damit auf dem besten Weg sind, das wirklich intelligent zu machen. Wenn unser Entwickler die Farbinformationen dieses Listenelements in die Datenbank speichern will, braucht er etwas zum Speichern. Zurück in Teil 2 dieser Serie sehen wir, dass unser Entwickler dies bereits vorausgesehen und ein Feld für die Farbe namens listItemColor erstellt hat, das er als INT (Integer) festgelegt hat. Er dachte, das wäre der intelligenteste Weg, da es leicht, einfach und abstrakt ist. Wir können später entscheiden, was der Schlüssel ist, z. B. 1 = Blau, 2 = Rot usw. Die Daten selbst müssen nicht wissen, dass sie rot sind. Wenn wir also einen Integer haben, der die Farbe repräsentiert, direkt im DOM, macht das das Aufnehmen und Weitergeben zum Speichern in der Datenbank wirklich einfach.

Warum ist das nur zu 50% schlau? Nun, weil wir diese Klugheit wahrscheinlich auf die Klassennamen selbst ausdehnen sollten. Wir verwenden zum Beispiel colorYellow, wenn color-1 sinnvoller wäre, wenn wir uns später entscheiden, Gelb aus dem Sortiment zu streichen und zu ersetzen. Oder vielleicht sogar Benutzern erlauben, ihre eigenen Farben festzulegen.

Löschen von Listenelementen

Unser kleiner "X"-Tab ist dafür zuständig, Benutzern das Löschen von Listenelementen zu ermöglichen. Wir möchten jedoch zusätzliche Sicherheit gegen versehentliche Falscheingaben haben. Daher benötigen wir zwei Klicks, um etwas tatsächlich zu löschen. Einige Anwendungen greifen auf ein übles "SIND SIE SICHER?"-Modal-Pop-up-Dialogfeld zurück, wir werden etwas gerissener sein. Wenn Sie auf das X klicken, erscheint rechts eine kleine Benachrichtigung, die nach der Sicherheit fragt. Wenn Sie erneut klicken, kann das Löschen beginnen.

$(".deletetab").live("click", function(){

    var thiscache = $(this);
            
    if (thiscache.data("readyToDelete") == "go for it") {
        $.ajax({
          
              // DEVELOPER, the user wants to delete this list item, commence deleting!

              success: function(r){
                    thiscache
                            .parent()
                                .hide("explode", 400, function(){$(this).remove()});

                    // Make sure to reorder list items after a delete!

              }

        });
    }
    else
    {
        thiscache.animate({
            width: "44px",
            right: "-64px"
        }, 200)
        .data("readyToDelete", "go for it");
    }
});

Da wir zuvor schlau waren und unsere kleine Tab-Grafik Teil einer einzigen Sprite-Grafik ist, müssen wir nur die Breite dieses Tabs erweitern, um die Nachricht anzuzeigen. Nach dem ersten Klick fügen wir dem Listenelement ein wenig Daten hinzu (jQuerys data()-Funktion) mit der Angabe "go for it". Bei einem zweiten Klick ist dieser Test WAHR und wir wissen, dass wir mit dem Löschen dieses Listenelements fortfahren können.

Da wir jQuery UI verwenden, haben wir mit der "explode"-Option zum Ausblenden von Elementen ein wenig zusätzlichen Spaß hinzugefügt.

Klick-zu-Bearbeiten von Listenelementen

Um unsere Listenelemente per Klick bearbeitbar zu machen, werden wir auf den Schultern anderer stehen und ein jQuery-Plugin verwenden, jEditable. Mit diesem Plugin müssen wir nur ein Element anvisieren und die Funktion editable() mit einigen Parametern darauf anwenden. Eine große Einschränkung: Wir können die live()-Funktion nicht mit diesem Plugin verwenden, da es kein Standard-jQuery-Ereignis ist.

Früher, bevor wir live hatten, haben wir das getan, was wir kurz besprochen haben. Wir riefen eine Funktion auf, die alle unsere Bindungen vornahm. So konnten wir sie sowohl beim DOM-Ready als auch nach beliebigen AJAX-Einfügungen aufrufen. Auf diese Technik werden wir uns jetzt verlassen.

function bindAllTabs(editableTarget) {
   
    $(editableTarget).editable("/path/for/DEVELOPER/to/save.php", {
        id        : 'listItemID',
        indicator : 'Saving...',
        tooltip   : 'Double-click to edit...',
        event     : 'dblclick',
        submit    : 'Save',
        submitdata: {action : "update"}
    });
    
}

Mit diesen Parametern geben wir dem Entwickler, was er für einen Callback-Dateipfad benötigt. Wir deklarieren auch, dass Listenelemente zum Bearbeiten einen Doppelklick erfordern, wie der Tooltip lauten soll und wie der Text auf dem Speicherbutton lauten soll. Sehr schön anpassbar!

Erinnern Sie sich an die Spans, die wir zuvor um den Text der Listenelemente eingefügt haben. Jetzt nutzen wir das. Wenn wir diese editable()-Funktion binden, werden wir sie an diese Spans binden. Direkt nach DOM-Ready werden wir also aufrufen:

bindAllTabs("#list li span");

Diese Funktion werden wir auch beim Anhängen neuer Listenelemente wieder benötigen...

Anhängen neuer Listenelemente

Kern der Funktionalität unserer Listen-App ist es, den Leuten das Hinzufügen neuer Listenelemente zu ermöglichen. Wir haben bereits das Markup dafür bereit, also schauen wir uns das JavaScript an, das es antreibt.

$('#add-new').submit(function(){

    var $whitelist = '<b><i><strong><em><a>',
        forList = $("#current-list").val(),
        newListItemText = strip_tags(cleanHREF($("#new-list-item-text").val()), $whitelist),
        URLtext = escape(newListItemText),
        newListItemRel = $('#list li').size()+1;
    
    if(newListItemText.length > 0) {
        $.ajax({
           
                // DEVELOPER, save new list item!

            success: function(theResponse){
              $("#list").append("<li color='1' class='colorBlue' rel='"+newListItemRel+"' id='" + theResponse + "'><span id=""+theResponse+"listitem" title='Click to edit...'>" + newListItemText + "</span><div class='draggertab tab'></div><div class='colortab tab'></div><div class='deletetab tab'></div><div class='donetab tab'></div></li>");
              bindAllTabs("#list li[rel='"+newListItemRel+"'] span");
              $("#new-list-item-text").val("");
            },
            error: function(){
                // uh oh, didn't work. Error message?
            }
        });
    } else {
        $("#new-list-item-text").val("");
    }
    return false; // prevent default form submission
});

HINWEIS: Bleiben Sie dran für den letzten Teil der Serie, in dem wir uns mit zusätzlicher Sicherheit befassen, die hier erforderlich ist. Jede Benutzereingabe muss bereinigt werden. Dies ist keine Ausnahme.

Eine letzte Sache, die wir dort gemacht haben, war, das Eingabefeld nach der Übermittlung zu leeren. Das erleichtert das sequentielle Hinzufügen vieler Listenelemente und wirkt natürlich. Beachten Sie auch, dass wir nach dem Anhängen des neuen Listenelements erneut die Funktion bindAllTabs aufgerufen haben. Deshalb haben wir diese Funktion ursprünglich abstrahiert, damit wir sie aufrufen können, wenn neue Listenelemente im AJAX-Stil angehängt werden. Das stellt sicher, dass die Klick-zu-Bearbeiten-Funktionalität auch bei neu angehängten Listenelementen vor einem Seitenrefresh funktioniert. Der Rest der Tabs ist dank der live()-Bindung automatisch in Ordnung.

Weiter geht's

Als Nächstes geben wir es an den Entwickler zurück, um die Lücken zu füllen, wie diese Listeninteraktionen auf PHP / Datenbankseite funktionieren. Dann werden wir über Sicherheit sprechen und alle losen Enden zusammenfassen.

Autoren der Serie

Jason Lengstorf ist ein Softwareentwickler mit Sitz in Missoula, MT. Er ist der Autor von PHP for Absolute Beginners und bloggt regelmäßig über Programmierung. Wenn er nicht an seine Tastatur gefesselt ist, steht er wahrscheinlich in der Schlange für Kaffee, braut sein eigenes Bier oder träumt davon, ein Mythbuster zu sein.
Chris Coyier ist ein Designer, der derzeit in Chicago, IL, lebt. Er ist Mitherausgeber von Digging Into WordPress, sowie Blogger und Sprecher zu allen Designthemen. Abseits des Computers schreit er wahrscheinlich Football-Trainer im Fernsehen an oder spielt Banjo.