Colorscheme

Webdesign Guide – Ajax für das Volk – Scriptaculous und Prototype im Einsatz


Wir nähern uns dem Ende vom Webdesign Guide und bislang haben wir folgendes erreicht:

  • eine lauffähige Anwendung die auf Klassenbibliotheken von PEAR zurückgreift
  • als Template-Engine läuft unter der Haube Smarty
  • mittels HtAccess haben wir uns suchmaschinen-optimierte Links gebastelt
  • eine Datenbank liegt bereit um unsere Eingaben zu speichern
  • User können sich Registrieren, Einloggen und Webnotizen (die überaus “tolle” Funktionalität unserer Webanwendung) erstellen

Nun wollen wir mittels Ajax unsere Webanwendung ein wenig aufmotzen, der Nutzen wird der gleiche bleiben, aber es wird mehr Spass machen Notizen anzulegen und zu löschen ;)

Um Ajax einfach und schnell verwenden zu können, verwenden wir Scripaculous: http://script.aculo.us/downloads

Wir erzeugen im Root-Verzeichnis unseres Webservers einen Ordner namens “ajaxstuff” und dort fügen wir folgende Dateien (welche alle im entpackten Scriptaculous-Order zu finden sind) ein:

  • dragdrop.js
  • effects.js
  • prototype.js
  • scriptaculous.js

(es werden zwar nicht alle Dateien benötigt, aber wer später weiterbasteln sollte, hat so schonmal die wichtigsten Dateien eingebunden)

Um Prototype und die Funktionalität von Scriptaculous in unserem Projekt bekanntzumachen, müssen wir noch zwei Zeilen in unserer header.tpl hinzufügen, so sollte das Ergebnis dann aussehen:

<html>
<head>
<title>Meine Webanwendung</title>
<link rel="stylesheet" href="style.css" type="text/css" media="screen" />
<script src="ajaxstuff/prototype.js" type="text/javascript"></script>
<script src="ajaxstuff/scriptaculous.js" type="text/javascript"></script>
</head>
<body>

Als erstes wollen wir eingetragene Webnotizen auf unserer Übersichtsseite schnell löschen können, natürlich ohne Reload sondern mittels Ajax. Dazu sind folgende Schritte nötig:

  1. Anpassen der Output.php – jede Webnotiz soll ein kleines “x”-Zeichen tragen, ein Klick auf dieses Zeichen soll die Webnotiz löschen
  2. Ebenfalls in der output.php werden wir dem “x”-zeichen eine Javascript-onclick Methode verpassen und sobald das “x” geklickt wird, wird eine PHP-Datei mittels Ajax aufgerufen
  3. die PHP-Datei löscht dann die jeweilige Webnotiz
  4. ein Scriptaculous-Effekt wird dafür zuständig sein, dem User ein Feedback darüber zu geben das die Notiz gelöscht wurde

Löschen der Notiz mit Ajax
In der Output.php werden die folgenden zwei Zeilen aktualisiert / eingefügt:
$webnotizen .= '<div class="note" id="note_'.$row[id].'"><div class="notetitle">';
$webnotizen .= '<a class="delete" href="#" onclick="new Effect.Fade(note_'.$row[id].')">x</a>';

das ganze sollte dann so aussehen:

<?php
require_once ($_SERVER['DOCUMENT_ROOT'].'/DB/DB.php');
require_once 'includes/config.php';
require_once 'smarty/libs/Smarty.class.php';

$dbh = DB::connect(DB_SYSTEM_1);
$res =& $dbh->query("SELECT post.id_post AS id, post.title AS title, post.content AS content FROM post, post_user WHERE post_user.id_user = '".$_SESSION[_authsession][data][id_user]."' AND post_user.id_post = post.id_post");
$dbh->disconnect();

$webnotizen = "";
while ($row =& $res->fetchRow(DB_FETCHMODE_ASSOC)) {
	$webnotizen .= '<div class="note" id="note_'.$row[id].'"><div class="notetitle">';
	$webnotizen .= '<a class="delete" href="#" onclick="new Effect.Fade(note_'.$row[id].')">x</a>';
	$webnotizen .= $row[title];
	$webnotizen .= '</div><div class="notetext">';
	$webnotizen .= $row[content];
	$webnotizen .= '</div></div>';
}

$smarty->assign('webnotizen', $webnotizen);

?>

Zur Erläuterung:
Wir belegen unseren Link mit einer “onclick” Funktion, wird der Link geklickt soll keine neue Seite aufgerufen werden (darum auch die “#”), sondern stattdessen ein Instanz der Klasse “Effect” von Scriptaculous erzeugt werden, in unserem speziellen Fall “Fade”. Wer Lust hat mit den Effekten herumzuspielen, kann “Fade” auch einfach mal durch folgendes ersetzen:

  • SwitchOff
  • Puff
  • BlindUp
  • Shrink

und hier finden sich alle Effekte: http://wiki.script.aculo.us/scriptaculous/show/CombinationEffectsDemo

Ich pflücke den Code nochmal etwas auseinander:
while ($row =& $res->fetchRow(DB_FETCHMODE_ASSOC)) {
$webnotizen .= '<div class="note" id="note_'.$row[id].'"><div class="notetitle">';
$webnotizen .= '<a class="delete" href="#" onclick="new Effect.Fade(note_'.$row[id].')">x</a>';
$webnotizen .= $row[title];
$webnotizen .= '</div><div class="notetext">';
$webnotizen .= $row[content];
$webnotizen .= '</div></div>';
}

Wir durchlaufen in diesem Block unsere Ergebnis-Werte die wir durch unseren Datenbank-Query erhalten haben. Unser Ergebnis sind alle Notizen die wir für unseren Benutzer angelegt haben. Mittels “$row” können wir für jeden Schleifendurchlauf auf die jeweilige Ergebnis-Zeile zugreifen und in jeder Zeile haben wir Zugriff auf die Werte “id”, “title”, und “content”. Wenn ich also innerhalb der Schleife folgendes aufrufe:
$row[id]
dann erhalte ich die ID der gerade ausgewählten Notiz, genauso bekomme ich dann mit $row[content] denn Inhalt oder mittels “$row[title]” den Titel der Notiz.

Eine wichtige Veränderung die ich durchgeführt habe findet in folgender Zeile statt:
$webnotizen .= '<div class="note" id="note_'.$row[id].'"><div class="notetitle">';

Nun bekommt jeder Notiz-Div-Kalender eine eindeutige ID zugewiesen, also “note_1″, “note_2″, “note_15″ usw. jenachdem welche ID in der Datenbank selektiert wurde für die Notiz. Dies ist wichtig damit wir in der darauffolgenden Zeile der Scriptaculous Klasse die ID übergeben können, für welche der Effekt “Fade” ausgeführt werden soll. An Scriptaculous wird bei einem Klick auf einen Link dann nur noch die ID übergeben und Scriptacuolous selbst übernimmt dann das ausfaden, indem es sich das Div-Element im DOM sucht, für welches die ID zutrifft.

Mehr zum DOM (Dodument Object Model):

Nun können wir bei einem Klick auf das “x”-Zeichen unsere Notizen löschen…nein, ganz so einfach ist es nicht, mittels Scriptaculous werden nun zwar bei einem Klick die Div-Container ausgeblendet, gelöscht wird aber noch nichts, denn dafür braucht es noch ein PHP-Script welches aufgerufen werden muss und in dem dann mittels eines Datenbank-Queries der jeweilige Datensatz entfernt wird, doch dazu später mehr, erstmal soll nun der “Lösch”-Link etwas aufpoliert werden, dazu muss unser Stylesheet durch folgende Zeile erweitert werden:

.delete {color: red; text-decoration: none; margin-right: 15px; font-size: 1.5em;}

So, optisch ist die Funktionalität nun da (wenn auch unter Usability-Gesichtspunkten missverständlich), jetzt wollen wir das bei einem Klick auf das “x” die jeweilige Notiz auch aus der Datenbank gelöscht wird. Dazu legen wir als erstes einen Ordner namens “ajaxscripts” im Ordner “filesystem” an. Darin erstellen wir die Datei “delete_note.php”, diese Datei wird durch einen Ajax-Request aufgerufen und darin löschen wir die jeweilige Notiz.

Löschen von Notizen mit Ajax
Der Code für die Datei “delete_note.php” ist folgender:

<?php
	require_once ($_SERVER['DOCUMENT_ROOT'].'/DB/DB.php');
	require_once ($_SERVER['DOCUMENT_ROOT'].'/includes/config.php');

	$note_id = $_GET['note_id'];

	$dbh = DB::connect(DB_SYSTEM_1);
	$res =& $dbh->query("DELETE FROM post WHERE id_post = '".$note_id."'");
	$dbh->disconnect();

?>

Codeerklärung “delete_note.php”
Die delete_note.php ist dafür da, von der Ajax-Engine angesprochen zu werden, eine ID zu erhalten und mittels dieser ID einen Wert aus der Datenbank zu löschen. Der Aufruf des PHP-Scripts erfolgt asynchron, doch dafür müssen wir nochmal in die output.php und dort den “Lösch”-Link anpassen, denn zusätzlich zum Scriptaculous Effekt soll nun ja auch noch etwas passieren. Dafür brauchen wir einen Ajax-Request, den wir mit dem Scriptaculous Effekt verschmelzen:

$webnotizen .= '<a class="delete" href="#" onclick="new Effect.Fade(\'note_'.$row[id].'\'); new Ajax.Request(\'/filesystem/ajaxscripts/delete_note.php\',{method: \'post\', parameters:\'note_id='.$row[id].'\'});">x</a>';

Das “onclick”-Ereignis ist nun ganz schön vollgepackt, neben dem “Ausfaden” haben wir nun auch noch den Ajax Request da drin. Der Ajax.Request unterteilt sich in folgende zwei Bereiche:

new Ajax.Request(url, options);

Die URL gibt den Ort an, wo das PHP Script zu finden ist und in options können wir allerhand zusätzlichen Kram packen, wir begnügen uns an dieser Stelle mit der Methode “post” und den Parametern die wir dem Script mitschicken. Benötigt wird die ID einer Notiz, und diese soll das Script auch bekommen.

Notizen platzieren mit “Draggable”
Um noch ein wenig die eigene Kreativität anzuregen und zu zeigen was Ajax sonst noch kann, wollen wir nun noch die Möglichkeit bieten das man seine Notizzettel frei auf dem Bildschirm platzieren kann. Als erstes machen wir unsere Notizen “frei platzierbar” und in einem weiteren Schritt sollen die Koordinaten auch in der Datenbank mittels Ajax abgespeichert werden um bei einem Reload der Webanwendung (oder nach einem Logout/Login) die Notizen an der gleichen Stelle wiederzufinden, wo man sie abgelegt hat.

Wir nutzen die Klasse “Draggable” zum freien platzieren unserer Notizen. Die Datei output.php erweitern wir um folgenden Code:

$note_drag .= '
<script type="text/javascript">
	var messages = document.getElementsByClassName('note');
	for (var i = 0; i < messages.length; i++) {
		new Draggable(messages[i].id, {ghosting:true, revert:false})
	}
</script>
';

$smarty->assign('note_drag', $note_drag);

Dieser Code muss nach der Ausgabe unserer Webnotizen erfolgen, sprich nach der “While”-Schleife in der wir der Variablen “$webnotizen” die Werte zugewiesen haben. Der Javascript-Code macht folgendes:

  • Er holt sich alle Elemente die die Klasse “note” tragen (alle unsere Notizen tragen diese Klasse)
  • Dann wird eine Schleife erstellt die solange läuft bis alle Notizen erfasst wurden
  • Jede Notiz wird “draggable” gemacht, “ghosting” bedeutet das wenn man ein Element bewegt, dass das Element vorerst an der Ursprungsposition bleibt und man nur eine Kopie bewegt und “revert: false” sagt aus das Elemente, welche bewegt wurden, nicht wieder zu ihrer Startposition zurückspringen

Wir speichern das Javascript in der Smarty-Variablen “note_drag” und müssen, um den Inhalt der Variablen auch auszugeben, in der Datei index.tpl folgendes nach “{$webnotizen}” ausgeben:

{$note_drag}

Jetzt lassen sich unsere Notizen schon lustig auf dem Bildschirm platzieren, als nächstes werden die Koordinaten abgespeichert, dazu müssen wir folgende Änderung in der Datenbanktabelle “post” vornehmen:

ALTER TABLE `post` ADD `xpos` INT NOT NULL , ADD `ypos` INT NOT NULL ;

Dadurch legen wir zwei weitere Spalten an, so können wir für jede Notiz individuell die X- und die X-Position abspeichern. Standardmäßig sollten alle schon nun angelegten Notizen mit der X- und Y-Pos “0″ versorgt sein. Um diese Koordinaten auch verwenden zu können, müssen wir einen weiteren Eingriff in die output.php wagen.

Die Zeile mit dem Datenbankquery muss ersetzt werden durch:
$res =& $dbh->query("SELECT post.id_post AS id, post.title AS title, post.content AS content, post.xpos AS xpos, post.ypos AS ypos FROM post, post_user WHERE post_user.id_user = '".$_SESSION[_authsession][data][id_user]."' AND post_user.id_post = post.id_post");

Nun selektieren wir noch zusätzlich die xpos und ypos-Spalte, welche wir gerade eben angelegt haben. Mit diesen Werten können wir jedem Element seine eigene Position geben, dazu ändern wir die erste Zeile in der While-Schleife zu:

$webnotizen .= '<div class="note" id="note_'.$row[id].'" style="position: absolute; left: '.$row[xpos].'; top: '.$row[ypos].';"><div class="notetitle">';

Die Elemente bekommen eine absolute Position damit wir ihnen ihre x- und y-Koordinate geben können. Nachdem man eine Notiz gedragged und dropped hat, soll die neue Koordinate ermittelt und mittels Ajax an ein Script geschickt werden, dieses Script speichert dann für die jeweilige Notiz den x- und y-wert in der Datenbank ab. Der folgende Code könnte für Scriptaculous Neulinge recht komplex wirken, ich musste mich auch erst durch einige Foren kämpfen bevor ich einen Lösungsweg fand und zu dem lauffähigen Ergebnis kam. Probiert es einfach mal aus und erfreut euch an dem Effekt ;)

Freie Platzierung, wie auf dem Desktop
Folgende Schritte sind zum Ziel nötig:

  • Anlegen einer Datei “save_position.php”
  • Anpassen des Konstruktors “Draggable”

Als erstes legen wir die Datei save_position.php im Ordner “/filesystem/ajaxscripts/” an. Diese Datei erhält von uns eine ID, eine Xposition und eine YPosition und updated für die jeweilige ID die Position in der Datenbank.

saveposition.php

<?php
	require_once ($_SERVER['DOCUMENT_ROOT'].'/DB/DB.php');
	require_once ($_SERVER['DOCUMENT_ROOT'].'/includes/config.php');

	$note_id = $_POST['note_id'];
	$xpos = $_POST['xpos'];
	$ypos = $_POST['ypos'];

	$note_id = str_replace("note_", "", $note_id);

	$dbh = DB::connect(DB_SYSTEM_1);
	$res =& $dbh->query("UPDATE post SET xpos = '".$xpos."', ypos = '".$ypos."' WHERE id_post = '".$note_id."'");
	$dbh->disconnect();
?>

Hier geschieht nichts spektakuläres, wir erhalten xpos, ypos und den ID-Namen des Elements, welches verändert werden soll. Da wir aber nur den Namen bekommen, also zum Beispiel “note_21″, “note_52″ oder “note_12232″ und wir uns erst die ID aus diesem Konstrukt basteln müssen, löschen wir den Anfangspart mit str_replace. Also “note_” fliegt raus und wir erhalten unsere ID.

Und nun kommt der Brocken:
Ersetzt in der output.php den Teil, wo die Variable “$note_drag” den Javascript Part zugewiesen bekommt durch folgenden:

$note_drag .= '
<script type="text/javascript">
	var messages = document.getElementsByClassName('note');
	for (var i = 0; i < messages.length; i++) {
		new Draggable(messages[i].id, {ghosting:false, revert:false, endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){
            Draggable._dragging[element] = false
          }
        });
        new Ajax.Request('/filesystem/ajaxscripts/save_position.php',{method: 'post', parameters:'note_id='+element.id+'&xpos='+document.getElementById(element.id).style.left+'&ypos='+document.getElementById(element.id).style.top});
      }});
	}
</script>
';

Uff, was ist denn hier geschehen? Vorher hatte der Konstruktor “Draggable” lediglich die Optionen “ghosting:false” und “revert:false” nun ist aber noch eine weitere hinzugekommen, “endeffect“.

Wir schauen uns den Inhalt von “endeffect” im Detail an:

endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){
            Draggable._dragging[element] = false
          }
        });
        new Ajax.Request('/filesystem/ajaxscripts/save_position.php',{method: 'post', parameters:'note_id='+element.id+'&xpos='+document.getElementById(element.id).style.left+'&ypos='+document.getElementById(element.id).style.top});
      }

Es geschieht folgendes: Wenn endeffect eintritt (also wenn der drag beendet ist, der User das Element also losgelassen hat) dann starte die Funktion und übergebe der Funktion den aktuellen ID-Namen des Elements (also z.b.: note_12, note_34, note_1232, usw). Die folgenden 7 Zeilen sind entnommen aus “draganddrop.js”, dies sind die Standardaktionen von Scriptaculous für die Drag-Funktionalität.

Nun wird es interessant: Wenn der Drag erfolgreich abgeschlossen wurde, führen wir mittels Ajax.Request eine Anfrage an eine PHP-Datei aus. Wir rufen also die Datei “save_position.php” und übergeben dieser die aktuelle ID des Elements welches gedragged und gedropped wurde (schlimmes D-Englisch) und die aktuelle X- und Y-Position für das Element mittels

document.getElementById(element.id).style.left
document.getElementById(element.id).style.top

Nun sollten sich die Notizen frei auf dem Bildschirm platzieren lassen und auch nach einem Logout/Login an der gewünschten Position verweilen. Probiert es einfach mal aus, es ist wirklich beeindruckend was mit Prototype und Scriptaculous möglich ist.

Zum Schluß
So, hiermit geht der Webdesign Guide zuende und ich hoffe ihr konntet einen Nutzen daraus ziehen. Auch wenn es lange gedauert hat, mir hat es wirklich Spass gemacht diesen Guide zu schreiben und ich denke es werden weitere folgen. Ich werde den Guide auch als PDF veröffentlichen und mit der PDF auch den gesamten Code für das Projekt.

Weiterführende Quellen:


Verwandte Artikel
  1. Webdesign Guide – Eintragsformular und Webnotizen
  2. Scriptaculous – 5 Minuten Speed Tutorial
  3. Webdesign Guide – Programmieren einer eigenen Web-Anwendung mit PHP, MySQL, Smarty, Pear, ModRewrite
  4. Webdesign Guide – Modul: Kontaktformular und Dankesseite
  5. Das Buch “AJAX – Frische Ansätze für das Webdesign” jetzt kostenlos im Internet

am Januar 19, 2007 um 11:12 Uhr | in Webdesign | 13 Kommentare

2 Trackbacks/Pingbacks
  1. Pingback: Webdesign Guide - Programmieren einer eigenen Web-Anwendung mit PHP, MySQL, Smarty, Pear, ModRewrite » Webdesignblog on Januar 19, 2007
  2. Pingback: Script Artists | AJAX in Verwendung on Januar 19, 2007

11 Kommentare
  1. erich, Januar 19, 2007:

    es gibt dazu ein eigenes prototype libary, das solche fenster effekte unterstützt. ich hab damit vor einigen tagen auch was gezaubert: http://erich.erich-holzbauer.at/2007/01/15/onsite-widget-v01/

  2. Christian Strang, Januar 19, 2007:

    Wirklich interessant, das spart sicherlich einiges an Arbeit.

  3. konSumi, Januar 21, 2007:

    Scriptaculous ist echt ne super Sache. Schnell eingebaut und bietet wirklich ne Menge verschiedener Funktionen. Besonder praktisch finde ich diese Notes. OK die Effekte sind eigentlich Spielerei, aber im Moment jedenfalls gerne gesehen, wie eigentlich alles was mit Web 2.0 zu tun hat ;)

  4. Alex, Januar 22, 2007:

    185kb JavaScript (ISDN: 23 Sek. ohne bzw. 11 Sek. mit Kanalbündelung – Komprimiert komme ich auf 73kb und 9 Sek. ohne Kanalbündelung) in die Seite knallen um ein paar Effekte zu machen – und noch ein paar zusätzliche Scripts laden lassen, falls wir sie später mal brauchen sollten? Ist das wirklich nötig – vor allem muss/sollte man sowas in einem Tutorial empfehlen? Jeden Respekt für das Tutorial ansich – aber bring doch den Leuten nicht bei, dass ein höheres Trafficaufkommen bei Benutzer und Seitenbetreiber (durch größere/mehr Scripte) gut sei.

    Ich arbeite ja lieber mit jQuery (jquery.com) – 19kb als Produktiv Version / 55kb als lesbare Version – kann recht viel für seine Größe und wem das nicht reicht, der klemmt ein Plugin dazu – da wo er es braucht.

    Die Produktivversion braucht bei ISDN ohne Kanalbündelung immerhin nur 2 Sekunden. Und immerhin sollte man nicht vergessen, dass “DSL bereits in vielen Anschlussgebieten verfügbar” ist – also in vielen auch nicht. jQuery mag ja nicht DAS Framework schlechthin sein – aber bezüglich Ladezeit ist es spitze.

    Just my 50 cent.
    Alex

  5. Christian Strang, Januar 22, 2007:

    Mh… hast recht, die Dateien die nicht verwendet werden sollten auch nicht mit reinkommen, hab sie wieder entfernt.

  6. SteffenR, April 20, 2007:

    Hallo – hab dein Tutorial hier gerade mal per “Copy And Paste” getestet – leider scheint es nicht so zu funktionieren – ich bekommen hier die Fehlermeldung “Fatal error: Cannot redeclare class db in D:\_web\xampp\php\pear\DB.php on line 433″ beim Aufruf der index.php bzw. der Loginseite..

    Wäre klasse, wenn du dich diesbezüglich mal melden könntest..
    MfG
    SteffenR

  7. Christian Strang, April 20, 2007:

    Copy & Paste ist immer problematisch (aufgrund der Code-Umwandlung von WordPress). Lad dir am besten mal die Projektdateien heir runter: http://webdesignblog.de/webdesign/webdesign-guide-pdf/

    und versuche ob es damit läuft.

  8. niels, Januar 1, 2008:

    Hallo,
    vielen Dank für das super Tutorial! Allerdings komme ich nicht weiter als bis zur register.php. Dort erhalte ich ebenfalls immer die Fehlermeldung:

    Cannot redeclare class db in /tools/pear/PEAR/DB.php on line 433

    Pear ist richtig installiert und läuft mit anderen Scripten auch ohne Probleme. Kann dies an verschieden Versionen liegen?

  9. niels, Januar 2, 2008:

    Hat sich erledigt. Der Fehler kommt durch die erneute Einbindung der DB.php in der register.php. Da diese aber schon durch die index.php geladen wurde, kann die Klasse nicht nochmal deklariert werden und es kommt zum Fehler. Einfach auskommentieren.

  10. Milos, Februar 18, 2008:

    Guter Beitrag,

    ich schau mal ob ich das so hinkrieg :-)

    Grüsse Milos

  11. Nico Schubert, März 17, 2009:

    Huhu,

    vielen Dank für das Tutorial. Was aber ein wenig unschön an den Tutorial ist, das im IE 7 der Code über das Rechte Menüe geht, das sieht echt doof aus und macht sich zum Kopieren absolut schlecht. Dazu bekomme ich ein waagerechten Scrollbalken, sowas ist echt unschön.

    Grüße Nico

Tut mir leid, die Kommentarfunktion für diesen Beitrag ist geschlossen.