Development

Documentation/de_DE/book/1.0/11-Ajax-Integration

You must first sign up to be able to contribute.

Version 13 (modified by Jan.Kunzmann, 10 years ago)
--

ARBEITSENTWURF ROHÜBERSETZUNG / TRANSLATION WORKING DRAFT
Originaldokument: http://svn.symfony-project.com/doc/branches/1.1/book/11-Ajax-Integration.txt, Revision 8468 vom 2008-04-10
Übersetzungsfortschritt: 100%
Übersetzung: JK
Korrekturfortschritt: 0%

Kapitel 11 - Ajax-Integration

Clientseitige Interaktion, komplexe visuelle Effekte und die asynchrone Kommunikation sind typisch für das Web 2.0. Sie benötigen JavaScript, aber sie selbst zu schreiben bedeutet oft mühsames und zeitraubendes Debuggen. Zum Glück automatisiert Symfony viele der gebräuchlichen Einsatzmöglichkeiten für Javascript in den Templates mit einer Reihe von Helfern. Viele clientseitige Verhaltensweisen können sogar ohne eine einzige Zeile JavaScript kodiert werden. Die Entwickler müssen sich nur noch Gedanken machen, welchen Effekt sie erreichen wollen, und Symfony kümmert sich um den komplexen Syntax und Kompatibilitätsfragen.

Dieses Kapitel beschreibt die Werkzeuge, die Symfony bereitstellt, um clientseitiges Scripting zu ermöglichen:

  • Einfache JavaScript-Helfer geben standardkonforme <script>-Tags in Symfony-Templates aus, um Elemente im Document Object Model (DOM) zu verändern oder ein Script über einen Link auszuführen.
  • Mit Prototype wurde eine JavaScript-Bibliothek in Symfony integriert, die die Entwicklung von clientseitigen Scripten beschleunigt, indem sie neue Funktionen und Methoden zum JavaScript-Kern hinzufügt.
  • Ajax-Helfer ermöglichen es dem User, Teile der Seite zu aktualsieren, indem er einen Link anklickt, ein Formular abschickt oder ein Formularelement benutzt.
  • Die reichhaltigen Optionen dieser Helfer bieten noch mehr Flexibilität und Leistung, vor allem durch den Einsatz von Callback-Funktionen.
  • Script.aculo.us ist eine weitere in Symfony integrierte JavaScript-Bibliothek, die dynamische visuelle Effekte bereitstellt, um die Benutzerschnittstelle zu erweitern und das Benutzererlebnis zu verstärken.
  • JavaScript Object Notation (JSON) ist ein Standard, der für die Kommunikation zwischen Server- und Clientscript benutzt wird.
  • Komplexe clientseitige Interaktionen, die alle zuvor aufgeführten Elemente kombinieren, sind in Symfony-Anwendungen möglich. Auto-Vervollständigung, Drag'n'drop, sortierbare Listen und editierbarer Text - all dies kann mit einer einzigen Zeile PHP implementiert werden -- dem Aufruf eines Symfony-Helfers.

Einfache JavaScript-Helfer

Lange Zeit führte JavaScript wegen der Inkompatibilitäten zwischen den Browsern ein Schattendasein in professionellen Webanwendungen. Mittlerweile sind die meisten dieser Kompatibilitätsfragen gelöst. Es gibt einige stabile Bibliotheken, mit denen Sie komplexe Interaktionen in JavaScript programmieren können, ohne unzählige Zeilen Code zu schreiben und stundenlang zu debuggen. Die wohl populärste Entwicklung, Ajax genannt, werden wir im Abschnitt "Ajax-Helfer" später in diesem Kapitel behandeln.

Es mag paradox klingen, dass Sie ziemlich wenig JavaScript-Code in diesem Kapitel sehen werden. Dies liegt daran, dass Symfony einen ziemlich einfachen Ansatz für clientseitiges Scripting verfolgt: es verpackt und abstrahiert JavaScript-Verhaltensweisen in Helfer, wodurch Ihre Templates letztlich garkeinen JavaScript-Code mehr enthalten. Einem Seitenelement ein neues Verhalten hinzuzufügen bedeutet für den Entwickler eine einzige Zeile PHP, doch dieser Helferaufruf gibt JavaScript-Code aus, und wenn Sie sich die erzeugten HTML-Antwort ansehen, dann werden Sie die gekapselte Komplexität erkennen. Der Helfer kümmert sich um browserübergreifende Angelegenheiten, complex limit cases, Erweiterbarkeit usw., weshalb sie eine beträchtliche Menge JavaScript enthalten können. Daher wird Ihnen in diesem Kapitel gezeigt, wie man ohne JavaScript die Effekte erreichen kann, die Sie bisher mit JavaScript erzeugt haben.

Alle hier beschriebenen Helfer stehen in den Templates zur Verfügung, vorausgesetzt, Sie haben die Verwendung der Javascript Helfergruppe angegeben.

[php]
<?php use_helper('Javascript') ?>

Wie Sie bald lernen werden, geben einige dieser Helfer HTML-Code aus und andere JavaScript.

JavaScript in Templates

In XHTML muss ein JavaScript-Codeblock in eine CDATA-Sektion eingeschlossen wird. Bei Seiten mit vielen JavaScript-Codeblöcken kann das eine sehr mühsame Angelegenheit werden. Deshalb besitzt Symfony den Helfer javascript_tag(), der eine Zeichenkette in XHTML-konforme <script>-Tags einbettet. Listing 11-1 zeigt die Verwendung des Helfers.

Listing 11-1 - JavaScript mit dem Helfer javascript_tag() einfügen

[php]
<?php echo javascript_tag("
  function foobar()
  {
  ...
  }
") ?>
 => <script type="text/javascript">
    //<![CDATA[
      function foobar()
      {
        ...
      }
    //]]>
    </script>

Aber der häufigste Einsatzzweck von JavaScript, mehr noch als Codeblöcke, ist ein Hyperlink, der ein bestimmtes Script auslöst. Der link_to_function()-Helfer macht exakt dies, wie in Listing 11-2 gezeigt.

Listing 11-2 - Auslösen von JavaScript über einen Link mit dem link_to_function()-Helfer

[php]
<?php echo link_to_function('Klick mich!', "alert('foobar')") ?>
 => <a href="#" onClick="alert('foobar'); return none;">Klick mich!</a>

Wie bei dem link_to()-Helfer können Sie Optionen für das <a>-Tag als drittes Argument übergeben.

NOTE Genau wie link_to() eine Schwester namens button_to() hat, können Sie JavaScript über einen Button auslösen (<input type="button">), indem Sie den Helfer button_to_function() aufrufen. Und wenn Sie ein anklickbares Image bevorzugen, rufen Sie einfach link_to_function(image_tag('myimage'), "alert('foobar')") auf.

DOM-Elemente aktualisieren

Eine übliche Aufgabe in dynamischen Schnittstellen ist, ein Element der Seite zu aktualisieren, was Sie bisher in etwa wie Listing 11-3 geschrieben hätten.

Listing 11-3 - Ein Element mit JavaScript aktualisieren

[php]
<div id="indicator">Verarbeitung der Daten beginnt</div>
<?php echo javascript_tag("
  document.getElementById("indicator").innerHTML =
    "<strong>Verarbeitung der Daten abgeschlossen</strong>";
") ?>

Symfony besitzt einen Helfer, der für diesen Zweck JavaScript (kein HTML) erzeugt: update_element_function(). Listing 11-4 zeigt die Verwendung.

Listing 11-4 - Ein Element in JavaScript mit dem Helfer update_element_function() aktualisieren

[php]
<div id="indicator">Verarbeitung der Daten beginnt</div>
<?php echo javascript_tag(
  update_element_function('indicator', array(
    'content'  => "<strong>Verarbeitung der Daten abgeschlossen</strong>",
  ))
) ?>

Sie werden sich vielleicht wundern, worin genau der Nutzen dieses Helfer liegen soll -- schließlich ist der Code mindestens genauso lang wie der eigentliche JavaScript-Code. Nun, Sie könnten beispielsweise Inhalt vor oder nach einem Element einfügen, ein Element entfernen anstatt es nur zu aktualisieren, oder einfach abhängig von bestimmten Bedingungen nichts tun. In diesen Fällen wird der JavaScript-Code schon ewas unübersichtlicher, aber mit update_element_funkction() bleibt das Template übersichtlich, wie Sie in Listing 11-5 sehen können.

Listing 11-5 - Optionen des update_element_function()-Helfers

[php]
// Inhalt unmittelbar nach dem Element 'indicator' einfügen
update_element_function('indicator', array(
  'position' => 'after',
  'content'  => "<strong>Verarbeitung der Daten abgeschlossen</strong>",
));

// Das Element vor `indicator` entfernen, aber nur, wenn $condition true ist
update_element_function('indicator', array(
  'action'   => $condition ? 'remove' : 'empty',
  'position' => 'before',
))

Der Helfer macht Ihre Templates verständlicher als jeder JavaScript-Code, und Sie können ähnliche Verhaltensweisen über einen einzigen Syntax aufrufen. Deshalb ist auch der Name des Helfers so lang: Es macht den Code selbsterklärend, ohne die Notwendigkeit von weiteren Kommentaren.

Graceful Degradation

NOTE Anm. d. Übers.: "Graceful Degradation", wörtlich übersetzt in etwa "Sanfte Verschlechterung", besser "Anwenderfreundlicher Qualitätsverlust", bedeutet in unserem Falle, dass eine Webanwendung immer noch benutzbar bleibt, selbst wenn bestimmte Voraussetzungen wie z.B. JavaScript, Ajax, oder eine bestimmte Browserversion, nicht verfügbar sind. Möglicherweise muss der Benutzer Einschränkungen im Bedienkomfort hinnehmen oder auf bestimmte Merkmale verzichten.

Das <noscript>-Tag erlaubt es Ihnen, HTML-Code anzugeben, der nur den Browsern angezeigt wird, die kein JavaScript unterstützen. Symfony ergänzt das um einee Helfer, der genau umgekehrt funktioniert: Er modifiziert Code derart, dass ihn nur Browser anzeigen, die JavaScript unterstützen. Die Helfer if_javascript() und end_if_javascript() erleichtern die Erstellung von Anwendungen, die abwärtskompatibel sind, wie in Listing 11-6 gezeigt.

Listing 11-6 - Verwendung des if_javascript()-Helfers, um "Graceful Degradation" zu ermöglichen

[php]
<?php if_javascript(); ?>
  <p>Sie haben JavaScript aktiviert.</p>
<?php end_if_javascript(); ?>

<noscript>
  <p>Sie haben JavaScript nicht aktiviert.</p>
</noscript>

NOTE Wenn Sie die Helfer if_javascript() und end_if_javascript() verwenden, können Sie auf ein zusätzliches echo verzichten.

Prototype

Prototype ist eine großartige JavaScript-Bibliothek, welche die Fähigkeiten dieser Scriptsprache im Browser erweitert, indem sie die Funktionen hinzufügt, die Sie sich schon immer gewünscht haben, und indem es neue Wege zur Manipulation des DOMs anbietet. Die Webseite dieses Projekts ist http://prototypejs.org/.

Die Dateien von Prototype kommen zusammen mit dem Symfony-Framework und stehen in jedem neuen Symfony-Projekt unter web/sf/prototype/ zur Verfügung. Sie können Prototype also verwenden, indem Sie folgenden Code in eine Action einfügen:

[php]
$prototypeDir = sfConfig::get('sf_prototype_web_dir');
$this->getResponse()->addJavascript($prototypeDir.'/js/prototype');

oder indem Sie folgendes in view.yml eintragen:

all:
  javascripts: [%SF_PROTOTYPE_WEB_DIR%/js/prototype]

NOTE Da die Ajax-Helfer von Symfony, die im nächsten Abschnitt beschrieben werden, auf Prototype basieren, wird die Prototype-Bibliothek automatisch eingebunden, sobald Sie einen der Helfer verwenden. Das bedeutet, dass Sie das Prototype-Script nicht selbst hinzufügen müssen, wenn Ihr Template einen _remote-Helfer aufruft.

Sobald die Prototype-Bibliothek geladen ist, können Sie all die neuen Funktionen verwenden, die es zum JavaScript-Kern hinzufügt. Es ist allerdings nicht Zweck dieses Buches, sie alle zu beschreiben. Im Web werden Sie ganz einfach jede Menge gute Dokumentation über Prototype finden, z.B. auf folgenden Webseiten:

Eine der Funktionen, die Prototype zu JavaScript hinzufügt, ist die Dollar-Funktion $(). Im Prinzip ist das ein einfacher Ersatz für document.getElementById(), nur etwas mächtiger. In Listing 11-7 sehen Sie ein Anwendungsbeispiel.

Listing 11-7 - Verwendung von $(), um in JavaScript ein Element anhand seiner ID zu finden

[php]
node = $('elementID');

// bedeutet das gleiche wie
node = document.getElementById('elementID');

// Die Funktion kann auch mehrere Elemente gleichzeitig ermitteln
// In diesem Fall ist der Rückgabewert ein Array aus DOM-Elementen
nodes = $('firstDiv', 'secondDiv');

Prototype besitzt auch eine Funktion, die dem JavaScript-Kern wirklich fehlt. Sie gibt ein Array aller DOM-Elemente zurück, die eine bestimmte Klasse haben:

[php]
nodes = document.getElementByClassName('myclass');

Sie werden diese Funktion jedoch nur selten verwenden wollen, weil Prototype eine noch viel mächtigere Funktion namens Double Dollar (Doppeldollar) besitzt, $$(). Diese Funktion gibt ein Array von DOM-Elementen zurück, die über einen CSS-Selektor ausgewählt werden. Der obige Aufruf könnte also auch wie folgt geschrieben werden:

[php]
nodes = $$('.myclass');

Dank der Mächtigkeit der CSS-Selektoren können Sie das DOM einfacher nach Klasse, ID, Eltern-Kind- oder Vorgänger-Nachfolger-Verhältnis durchsuchen, als Sie dies mit einem XPath-Ausdruck bewerkstelligen könnten. Sie können sogar auf Elemente zugreifen, indem Sie einen komplexen Selektor definieren, der all das kombiniert:

[php]
nodes = $$('body div#main ul li.last img > span.legend');

Ein letztes Beispiel, wie Prototype den JavaScript-Syntax erweitert, ist der Array-Iterator each. Das ist die gleiche Funktionalität wie in PHP, erweitert um die Möglichkeit, anonyme Funktionen und Closures in JavaScript zu definieren. Sie werden sie vermutlich häufiger in Ihren selbstgeschriebenen JavaScript verwenden.

[php]
var vegetables = ['Möhren', 'Kopfsalat', 'Knoblauch'];
vegetables.each(function(food) { alert('Ich liebe ' + food); });

Weil das Programmieren von JavaScript mit Prototype so viel mehr Spaß macht als es per Hand zu schreiben, und weil es auch Teil von Symfony ist, sollten Sie sich ein paar Minuten Zeit nehmen, um die entsprechende Dokumentation zu lesen.

Ajax-Helfer

Was, wenn Sie ein Element auf Ihrer Seite aktualisieren könnten, aber nicht mit einem JavaScript wie in Listing 11-5, sondern mit einem PHP-Script, das auf dem Server ausgeführt wird? Das würde Ihnen die Möglichkeit geben, Teile der Seite gemäß der Serverantwort zu verändern. Der Helfer remote_function() tut genau das, wie in Listing 11-8 gezeigt.

Listing 11-8 - Verwendung des remote_function()-Helfers

[php]
<div id="meinbereich"></div>
<?php echo javascript_tag(
  remote_function(array(
    'update'  => 'meinbereich',
    'url'     => 'meinmodul/meineaction',
  ))
) ?>

NOTE Der Parameter url kann entweder eine interen URI sein (modul/action?key1=wert1&...) oder der Name einer Routing-Regel, wie bei url_for().

Beim Aufruf wird dieses Script das Element mit der ID meinbereich mit der Antwort der Action meinmodul/meineaction aktualisieren. Diese Form der Interaktion nennt sich Ajax und ist das Herz jeder interaktiven Webanwendung. In der Wikipedia (http://de.wikipedia.org/wiki/AJAX) wird dies so beschrieben:

".. ein Konzept der asynchronen Datenübertragung zwischen einem Server und dem Browser, das es ermöglicht, innerhalb einer HTML-Seite eine HTTP-Anfrage durchzuführen, ohne die Seite komplett neu laden zu müssen. Das eigentliche Novum besteht in der Tatsache, dass nur gewisse Teile einer HTML-Seite oder auch reine Nutzdaten sukzessiv bei Bedarf nachgeladen werden,..."

Ajax verwendet XMLHttpRequest, ein JavaScript-Objekt, das sich in etwa wie ein versteckter Frame verhält, und das Sie durch eine Serveranfrage aktualisieren können, um Ihre Webseite zu verändern. Das Objekt selbst ist relativ systemnah, und es ist auf den verschiedenen Browserplattformen unterschiedlich implementiert, weshalb die manuelle Bearbeitung von Ajax-Requests üblicherweise in einer Menge Code endet. Glücklicherweise kapselt Prototype all den notwendigen Code für Ajax-Aufrufe und stellt ein einfacheres Ajax-Objekt zur Verfügung, das auch Symfony verwendet. Das ist auch der Grund, weshalb die Prototype-Bibliothek immer automatisch geladen wird, sobald Sie einen Ajax-Helfer im Template verwenden.

ACHTUNG Die Ajax-Helfer funktionieren nicht, wenn die URL der Action nicht zur gleichen Domain gehört wie die aktuelle Seite. Dies ist eine sicherheitbedingte Einschränkung, die vom Browser fest vorgegeben ist, und kann nicht umgangen werden.

Eine Interaktion mit Ajax besteht aus drei Teilen: einem Aufrufer (Link, Button, Formular, Uhr oder irgendein Steuerelement, das der Benutzer betätigen kann, um die Aktion zu starten), einer Server-Action, und einem Bereich auf der Seite, wo die Antwort der Action angezeigt wird. Sie können komplexere Interaktionen erstellen, indem die Action Daten zurückgibt, die dann von einer JavaScript-Funktion clientseitig weiterverarbeitet werden. Symfony stellt mehrere Helfer zur Verfügung, um Ajax-Interaktionen in Ihr Template einzufügen. Sie enthalten alle das Wort remote ("entfernt") in ihrem Namen. Beachten Sie, dass die Ajax-Helfer HTML-Code ausgeben, kein JavaScript.

SIDEBAR Wie funktionieren Actions mit Ajax?

Actions, die als Remotefunktionen aufgerufen werden, sind ganz normale Actions. Sie durchlaufen wie andere Actions auch das Routing, können mit ihrem return-Wert angeben, welcher View ausgegeben werden soll, können dem Template Variablen übergeben und das Model verändern.

Der Unterschied ist, dass folgender Aufruf true zurückgibt, wenn die Action über Ajax aufgerufen wurde:

[php]
$isAjax = $this->getRequest()->isXmlHttpRequest();

Symfony weiß, dass die Action in einem Ajax-Kontext läuft und passt die Bearbeitung der Antwort entsprechend an. Daher wird standardmäßig bei Ajax-Actions in der Entwicklungs-Umgebung kein Webdebug-Toolbar eingebunden. Ebenso wird der Dekorationsschritt übersprungen (Ajax-Templates werden für gewöhnlich nicht in ein Layout eingebettet). Wenn Sie einen Ajax-View dekorieren wollen, müssen Sie für diesen View explizit has_layout: true in der view.yml des Moduls angeben.

Da die Reaktionsgeschwindigkeit für Ajax-Interaktionen äußerst wichtig ist, kann es sinnvoll sein, die Erzeugung eines Views komplett zu vermeiden und die Antwort direkt aus der Action zurückzugeben, sofern die sie nicht zu komplex ist. Verwenden Sie die renderText()-Methode in Ihrer Action, um das Template zu überspringen, und beschleunigen Sie auf diese Weise Ihre Ajax-Requests.

Neu in Symfony 1.1: In den meisten Ajax-Actions wird es darauf hinauslaufen, dass das Template einfach nur ein Partial einbindet, weil der Code der Ajax-Antwort bereits in der eigentlichen Seite verwendet wird. Um zu vermeiden, ein ganzes Template nur für eine einzige Codezeile zu schreiben, kann die Action die Methode renderPartial() aufrufen. Diese Methode verbindet die Vorteile der Wiederverwendbarkeit und Cache-Fähigkeiten von Partials mit der Geschwindigkeit von renderText().

[php]
public function executeMeineAction()
{
  // Verschiedene Dinge tun
  return $this->renderPartial('meinmodul/meinpartial');
}

Ajax-Links

Ajax-Links stellen einen großen Anteil der Ajax-Interaktionen dar, die in Web-2.0-Anwendungen verwendet werden. Der Helfer link_to_remote() gibt einen Link aus, der -- kaum überraschend -- eine Remote-Funktion aufruft. Die Syntax ist ähnlich der von link_to() (allerdings ist der zweite Parameter ein assoziatives Array mit Ajax-Optionen), wie in Listing 11-9 gezeigt.

Listing 11-9 - Ajax-Link mit dem link_to_remote()-Helfer

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update' => 'feedback',
    'url'    => 'post/delete?id='.$post->getId(),
)) ?>

In diesem Beispiel würde ein Klick auf den Link 'Diesen Eintrag löschen' im Hintergrund einen Aufruf auf die Action post/delete losschicken. Die Antwort vom Server erscheint dann im Element mit der id feedback. Der Prozess ist in Abbildung 11-1 dargestellt.

Abbildung 11-1 - Ein Remote-Update durch einen Hyperlink auslösen

Ein Remote-Update durch einen Hyperlink auslösen

Sie können für den Link natürlich auch ein Bild anstelle eines Texts verwenden, den Namen einer Routing-Regel anstelle einer internen modul/action-URL angeben, und im dritten Argument dem <a>-Tag Optionen übergeben, wie in Listing 11-10 gezeigt.

Listing 11-10 - Optionen des link_to_remote()-Helfers

[php]
<div id="emails"></div>
<?php echo link_to_remote(image_tag('refresh'), array(
    'update' => 'emails',
    'url'    => '@list_emails',
), array(
    'class'  => 'ajax_link',
)) ?>

Ajaxifizierte Formulare

Webformulare rufen nach dem Absenden typischerweise eine weitere Action auf, aber das bedeutet auch, dass die ganze Seite neu geladen wird. Es wäre praktisch, für Formulare eine Entsprechung zu link_to_function() zu haben, so dass das Absenden eines Formulars nur ein Element der Seite mit der Serverantwort aktualisiert. Genau dies ist, was der Helfer form_remote_tag() macht. Seine Syntax zeigt Listing 11-11.

Listing 11-11 - Ajax-Formulare mit dem form_remote_tag()-Helfer

[php]
<div id="item_list"></div>
<?php echo form_remote_tag(array(
    'update'   => 'item_list',
    'url'      => 'item/add',
)) ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <?php echo submit_tag('Add') ?>
</form>

form_remote_tag() schreibt ein öffnendes <form>, wie der normale form_tag()-Helfer auch. Wenn Sie das Formular abschicken, wird im Hintergrund ein POST-Request auf die Action item/add mit dem Feld item als Parameter ausgeführt. Die Antwort wird den Inhalt des Elements item_list ersetzen, wie in Abbildung 11-2 gezeigt. Ein solches Ajax-Formular schließen Sie mit dem üblichen schließenden </form>-Tag.

Abbildung 11-2 - Auslösen einer Remote-Aktualisierung mit einem Formular

Auslösen einer Remote-Aktualisierung mit einem Formular

ACHTUNG Ajax-Formulare können nicht Multipart sein. Das ist eine Einschränkung des XMLHttpRequest-Objekts. Sie können daher keine Datei-Uploads über ein Ajax-Formular durchführen. Es gibt allerdings einige Workarounds dafür, z.B. ein versteckter iframe anstelle des XMLHttpRequest-Objekts.

Wenn Sie wollen, dass das Formular sowohl im klassischen HTML-Seiten-Modus als auch im Ajax-Modus funktioniert, definieren Sie am besten ein normales Formular, aber geben Sie zusätzlich zum normalen Absende-Button (Submit) einen zweiten (<input type="button" />) an, der das Formular per Ajax übertragt. In Symfony erzeugen Sie diesen Button mit submit_to_remote(). Dies hilft Ihnen dabei, Ajax-Interaktionen zu erstellen, die dennoch abwärtskompatibel sind. Ein Beispiel zeigt Listing 11-12.

Listing 11-12 - Ein Formular mit klassischer und moderner Ajax-Übertragung

[php]
<div id="item_list"></div>
<?php echo form_tag('@item_add_regular') ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <?php if_javascript(); ?>
    <?php echo submit_to_remote('ajax_submit', 'Mit Ajax hinzufügen', array(
        'update'   => 'item_list',
        'url'      => '@item_add',
    )) ?>
  <?php end_if_javascript(); ?>
  <noscript>
    <?php echo submit_tag('Hinzufügen') ?>
  </noscript>
</form>

Ein weiteres Beispiel für die Kombination aus klassischem Absendebutton und Ajax-Button ist ein Formular zum Bearbeiten von Artikeln. Es kann einen Vorschau-Button zeigen, der mit Ajax arbeitet, und einen Veröffentlichen-Button, welcher eine klassische Übertragung durchführt.

NOTE Wenn der Benutzer die Eingabetaste drückt, wird das Form mit der Aktion übertragen, die im <form>-Tag definiert ist -- in diesem Beispiel also eine normale Action.

Heutzutage können Formulare nicht nur reagieren, wenn sie abgeschickt werden, sondern auch, wenn der Wert eines Feldes vom Benutzer aktualisiert wird. In Symfony können Sie dafür den Helfer observe_field() verwenden. Listing 11-13 zeigt ein Beispiel, wie Sie diesen Helfer zur Erstellung eines "Autovorschlags" verwenden können. Jedes Mal, wenn ein Zeichen in das Feld item getippt wird, wird ein Ajax-Aufruf ausgelöst, der das Feld item_vorschlag auf der Seite aktualisiert.

Listing 11-13 - Aufruf einer Remote-Funktion durch observe_field(), wenn sich ein Feldinhalt ändert

[php]
<?php echo form_tag('@item_add_regular') ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <div id="item_vorschlag"></div>
  <?php echo observe_field('item', array(
      'update'   => 'item_vorschlag',
      'url'      => '@item_eingegeben',
  )) ?>
  <?php echo submit_tag('Hinzufügen') ?>
</form>

Die Action aus der Routing-Regel @item_eingegeben wird dann jedes Mal aufgerufen, wenn der User das überwachte Feld (item) ändert, selbst wenn er das Formular garnicht abschickt. Die Action erhält dann den aktuellen Inhalt von item in einem Requestparameter namens value. Wenn Sie etwas anderes als den Wert des überwachten Felds übergeben wollen, können Sie im Parameter with der Helfer-Optionen einen JavaScript-Ausdruck angeben. Wenn Sie z.B. wollen, dass die Action den Wert in einem Parameter namens param erhält, dann schreiben Sie den Helferaufruf wie in Listing 11-14 gezeigt.

Listing 11-14 - Eigene Parameter mit der with-Option an die Reomte-Action übergeben

[php]
<?php echo observe_field('item', array(
    'update'   => 'item_vorschlag',
    'url'      => '@item_eingegeben',
    'with'     => "'param=' + value",
)) ?>

Beachten Sie, dass dieser Helfer kein HTML-Element ausgibt, sondern Scriptcode, der das Verhalten des Elements verändert, das als Parameter übergeben wurde. Später in diesem Kapitel werden Sie noch weitere Beispiele solcher JavaScript-Helfer finden.

Wenn Sie alle Felder eines Formulars überwachen wollen, sollten Sie besser den Helfer observe_form() verwenden, der die Remotefunktion jedesmal aufruft, wenn eines der Formularfelder verändert wurde.

Periodischer Aufruf von Remote-Funktionen

Zu guter Letzt gibt es noch den Helfer periodically_call_remote(), welcher eine Ajax-Interaktion alle paar Sekunden aufruft. Er ist nicht mit einem bestimmten HTML-Element verbunden, sondern läuft unsichtbar im Hintergrund, quasi als Verhalten der ganzen Seite. Nützlich ist dies z.B., um die Position des Mauszeigers zu überwachen oder den Inhalt eines großen Eingabefelds automatisch zu speichern. Listing 11-15 zeigt ein Beispiel dieses Helfers.

Listing 11-15 - Periodischer Aufruf einer Remote-Funktion mit periodically_call_remote()

[php]
<div id="notification"></div>
<?php echo periodically_call_remote(array(
    'frequency' => 60,
    'update'    => 'notification',
    'url'       => '@watch',
    'with'      => "'param=' + \$F('mycontent')",
)) ?>

Wenn Sie kein Sekundenintervall (frequency) zwischen den einzelnen Aufrufen angeben, wird der Standardwert von 10 Sekunden verwendet. Beachten Sie, dass der Parameter with als JavaScript ausgewertet wird, weshalb Sie dort Prototype-Funktionen verwenden können, wie hier $F().

Parameter für Remote-Aufrufe

Bei allen Ajax-Helfer aus dem vorigen Abschnitt können Sie außer den Parametern update und url noch weitere angeben. Das assoziative Array mit Ajax-Parametern kann das Verhalten von Remote-Aufrufen und die Behandlung ihrer Rückantwort verändern und tunen.

Verschiedene Elemente je nach Antwortstatus aktualisieren

Wenn die Remote-Action fehlschlägt, können Sie durch den Remote-Helfer auch ein anderes Element aktualisieren lassen als jenes, das bei einer erfolgreichen Antwort verwendet wird. Zu diesem Zweck teilen Sie den Wert des Parameters update einfach in ein assoziatives Array und geben darin unterschiedliche Elemente an, die im Erfolgsfall (success) oder bei einem Fehlschlag (failure) zu aktualisieren sind. Dies kann z.B. nützlich sein, wenn eine Seite viele Ajax-Interaktionen enthält, und zusätzlich einen Bereich zum Anzeigen von Fehlern. Listing 11-16 zeigt diese bedingte Aktualisierung.

Listing 11-16 - Bedingte Aktualisierung

[php]
<div id="error"></div>
<div id="feedback"></div>
<p>Hallo, Welt!</p>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update'   => array('success' => 'feedback', 'failure' => 'error'),
    'url'      => 'post/delete?id='.$post->getId(),
)) ?>

TIP Nur HTTP-Fehlercodes (also 500, 404 und alle anderen, die nicht im Bereich 2XX sind) lösen die Aktualisierung des Elements failure ausm unabhängig davon, ob Ihre Action den Wert sfView::ERROR zurückgibt. Wenn Sie also wollen, dass Ihre Action einen Ajax-Fehler zurückgibt, müssen Sie in ihr $this->getResponse()->setStatusCode(404) oder etwas ähnliches aufrufen.

Ein Element an einer bestimmten Position aktualisieren

Wie beim Helfer update_element_function() können Sie mit dem Parameter position angeben, welches Element relativ zu dem angegebenen aktualisiert werden soll. Listing 11-17 zeigt ein Beispiel.

Listing 11-17 - Verwendung einer Position zur Angabe des Antwortortes

[php]
<div id="feedback"></div>
<p>Hallo, Welt!</p>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'position' => 'after',
)) ?>

Dies fügt die Antwort des Ajax-Aufrufs nach dem Element feedback ein, also zwischen dem <div> und dem <p>. Auf diese Weise können Sie mehrere Ajax-Aufrufe machen und sehen die Antworten aufgereiht unterhalb des Elements update.

Der Parameter position kann folgende Werte annehmen:

  • before: Vor dem Element
  • after: Nach dem Element
  • top: Oben innerhalb des Elements
  • bottom: Unten innerhalb des Elements

Ein Element abhängig von einer Bedingung aktualisieren

Ein Remote-Aufruf akzeptiert noch einen zusätzlichen Parameter, damit vom User eine Bestätigung abgefragt wird, bevor der XMLHttpRequest abgeschickt wird, wie in Listing 11-18 gezeigt.

Listing 11-18 - Verwendung des Bestätigungsparameters confirm, um nach einer Bestätigung zu fragen, bevor die Remote-Funktion aufgerufen wird

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'confirm'  => 'Sind Sie sicher?',
)) ?>

Wenn der User den Link anklickt, wird zunächst eine Dialogbox mit der Nachfrage "Sind Sie sicher?" angezeigt, und nur wenn der Benutzer diese Rückfrage mit "OK" bestätigt, wird die Action post/delete aufgerufen.

Der Remote-Aufruf kann auch von einem Test abhängen, der browserseitig im JavaScript durchgeführt wird. Geben Sie dazu den Parameter condition an, wie in Listing 11-19 gezeigt.

Listing 11-19 - Bedingter Aufruf der Remote-Funktion aufgrund eines clientseitigen Tests

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update'    => 'feedback',
    'url'       => 'post/delete?id='.$post->getId(),
    'condition' => "$('elementID') == true",
)) ?>

Die Requestmethode des Ajax-Aufrufs festlegen

Standardmäßig werden Ajax-Aufrufe mit der POST-Methode durchgeführt. Wenn Sie einen Ajax-Aufruf ausführen wollen der keine Daten verändert, oder wenn Sie ein Formular mit eingebauter Validierung als Ergebnis des Ajax-Aufrufs anzeigen wollen, müssen Sie die Requestmethode auf GET ändern. Die Option method ändert die Requestmethode des Ajax-aufrufs, wie in Listing 11-20 gezeigt.

Listing 11-20 - Verändern der Ajax-Requestmethode

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update'    => 'feedback',
    'url'       => 'post/delete?id='.$post->getId(),
    'method'    => 'get',
)) ?>

Scriptausführung erlauben

Es mag Sie vielleicht überraschen, dass JavaScript im HTML-Code in der Ajax-Antwort (also der Code, der vom Server kommt und ins Element update eingefügt wurde) standardmäßig nicht ausgeführt werden. Dies soll das Risiko von entfernten Angriffen minimieren und dafür sorgen, dass Scriptausführung nur dann erlaubt wird, wenn der Entwickler sicher weiß, welcher Code in der Antwort auftritt.

Sie müssen daher explizit die Möglichkeit zur Ausführung von Scriptcode in der Antwort aktivieren, und zwar mit der Option script. Listing 11-21 zeigt das Beispiel eines Ajax-Aufrufs, wobei festgelegt ist, dass das JavaScript in der Antwort ausgeführt werden darf.

Listing 11-21 - Scriptausführung in einer Ajax-Antwort erlauben

[php]
<div id="feedback"></div>
<?php
  // Wenn die Antwort auf die Action post/delete JavaScript enthält,
  // darf es im Browser ausgeführt werden.
    echo link_to_remote('Diesen Eintrag löschen', array(
    'update' => 'feedback',
    'url'    => 'post/delete?id='.$post->getId(),
    'script' => true,
)) ?>

Wenn das Remote-Template Ajax-Helfer enthält (so wie z.B. remote_function()), müssen Sie sich bewusst sein, dass diese PHP-Funktionen JavaScript-Code erzeugen, der nicht ausgeführt wird, solange Sie nicht 'script' => true angegeben haben.

NOTE Auch wenn Sie die Ausführung von Scripten in der Remote-Antwort erlauben, werden Sie die Scripte nicht im Remote-Code sehen, selbst wenn Sie ein Tool benutzen, dass den erzeugten Code anzeigt. Die Scripte werden ausgeführt, aber stehen nicht im Code. Dieses scheinbar seltsame Verhalten ist völlig normal.

Callbacks erzeugen

Ein wichtiger Punkt bei Ajax-Interaktionen ist, dass sie für den User nicht sichtbar sind, bis der Antwortbereich auf der Seite dann auch wirklich aktualisiert wurde. Dies bedeutet, dass die Benutzer z.B. bei langsamen Netzverbindungen oder Serverproblemen annehmen könnten, dass ihre Aktion durchgeführt wurde, obwohl es in Wirklichkeit nicht so ist. Aus diesem Grunde ist es wichtig, den Benutzer über den Fortschritt der Ajax-Interaktion auf dem Laufenden zu halten.

Standardmäßig ist jede Remoteanfrage ein asynchroner Prozess, während dem verschiedene JavaScript-Callbacks ausgelöst werden können (für Fortschrittsanzeiger und dergleichen). Alle Callbacks haben auf das request-Objekt Zugriff, welches das darunterliegende XMLHttpRequest enthält. Die Callbacks entsprechend den Events von einer Ajax-Interaktion:

  • before: Bevor der Request initiiert wird
  • after: Unmittelbar nachdem der Request initiiert wurde, aber vor dem Ladevorgang
  • loading: Wenn die Remoteantwort vom Browser geladen wird
  • loaded: Wenn der Browser das Laden der Remoteantwort beendet hat
  • interactive: Wenn der Benutzer mit der Remoteantwort interagieren kann, auch wenn der Ladevorgang noch nicht beendet ist
  • success: Wenn der XMLHttpRequest durchgeführt ist, und der HTTP-Statuscode im Bereich 2XX ist
  • failure: Wenn der XMLHttpRequest durchgeführt ist, und der HTTP-Statuscode nicht im Bereich 2XX ist
  • 404: Wenn der Request einen 404-Fehler zurückgibt
  • complete: Wenn der XMLHttpRequest abgeschlossen ist (erst nach success oder failure, sofern sie definiert sind)

Es ist üblich, einen Lade-Indikator einzublenden, sobald ein Remoteaufruf angestoßen wird, und ihn wieder auszublenden, wenn die Antwort empfangen wurde. Um dies zu erreichen, ergänzen Sie im Ajax-Aufruf einfach die Parameter loading und complete, wie in Listing 11-22 gezeigt.

Listing 11-22 - Ajax-Callbacks verwenden, um einen Aktivitätsindikator ein- und auszublenden

[php]
<div id="feedback"></div>
<div id="indicator">Laden...</div>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'loading'  => "Element.show('indicator')",
    'complete' => "Element.hide('indicator')",
)) ?>

Die Methoden show und hide und das JavaScript-Objekt Element sind übrigens weitere nützliche Zusätze von Prototype.

Grafische Effekte erzeugen

Symfony hat die grafischen Effekte der script.aculo.us-Bibliothek integriert, damit sie auf Ihren Webseiten mehr als nur <div>-Elemente anzeigen und verstecken können. Sie finden eine gute Syntax-Dokumentation für die Effekte im Wiki auf http://script.aculo.us/](http://script.aculo.us/). Grundsätzlich stellt die Bibliothek JavaScript-Objekte und -Funktionen zur Verfügung, die das DOM manipulieren, um komplexe grafische Effekte zu erreichen. Einige Beispiele sehen Sie in Listing 11-23. Da das Ergebnis eine grafische Animation bestimmter Bereiche auf einer Webseite ist, empfehlen wir Ihnen, dass Sie diese Effekte selbst ausprobieren, um zu verstehen, was sie tatsächlich machen. Auf der script.aculo.us-Webseite finden Sie eine Galerie, in der Sie eine Vorstellung von dynamischen Effekten bekommen können.

Listing 11-23 - Grafiksche Effekte in JavaScript mit Script.aculo.us

[php]
// Hebt das Element 'meinfeld' hervor
Effect.Highlight('meinfeld', { startcolor:'#ff99ff', endcolor:'#999999' })

// Ein Element ausblenden
Effect.BlindDown('id_des_elements');

// Ein Element wegblenden
Effect.Fade('id_des_elements', { transition: Effect.Transitions.wobble })

Symfony kapselt das JavaScript-Objekt Effect in einem Helfer namens visual_effect(), der ebenfalls Teil der Javascript-Helfergruppe ist. Er gibt JavaScript zurück, der in einem normalen Link verwendet werden kann, wie in Listing 11-24 gezeigt.

Listing 11-24 - Grafische Effekte in Templates, mit dem Helfer visual_effect()

[php]
<div id="geheimes_div" style="display:none">Ich war schon die ganze Zeit hier!</div>
<?php echo link_to_function(
  'Das geheime DIV zeigen',
  visual_effect('appear', 'geheimes_div')
) ?>
// Erzeugt den Aufruf Effect.Appear('geheimes_div')

Der visual_effects()-Helfer kann auch in Ajax-Callbacks verwendet werden, wie Listing 11-25 zeigt, das wie Listing 11-22 einen Aktivitätsindikator anzeigt, nur visuell ansprechender. Das Element indicator erscheint schrittweise beim Beginn des Ajaxaufrufs und verschwindet schrittweise, sobald die Antwort ankommt. Zusätzlich wird das Feedback-Element hervorgehoben, nachdem es vom Remoteaufruf aktualisiert wurde, um die Aufmerksamkeit des Benutzers auf diesen Teil des Fensters zu lenken.

Listing 11-25 - Grafische Effekte in Ajax-Callbacks

[php]
<div id="feedback"></div>
<div id="indicator" style="display: none">Laden...</div>
<?php echo link_to_remote('Diesen Eintrag löschen', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'loading'  => visual_effect('appear', 'indicator'),
    'complete' => visual_effect('fade', 'indicator').
                  visual_effect('highlight', 'feedback'),
)) ?>

Wie Sie sehen, können Sie grafische Effekte im Callback kombinieren, indem Sie sie einfach aneinanderhängen.

JSON

Die JavaScript Object Notation (JSON) ist ein leichtgewichtiges Datenaustauschformat. Eigentlich ist es nichts weiter als ein JavaScript-Hash (wie in Listing 11-26 gezeigt), das zum Transport von Objektdaten verwendet wird. JSON hat allerdings zwei große Vorteile für Ajax-Interaktionen: Es ist in JavaScript einfach zu lesen, und es kann die Größe der Webantwort verringern.

Listing 11-26 - Ein Beispielobjekt in JSON

var myJsonData = {"menu": {
  "id": "file",
  "value": "Datei",
  "popup": {
    "menuitem": [
      {"value": "Neu", "onclick": "CreateNewDoc()"},
      {"value": "Öffnen", "onclick": "OpenDoc()"},
      {"value": "Schließen", "onclick": "CloseDoc()"}
    ]
  }
}}

Wenn eine Ajax-Action strukturierte Daten an die aufrufende Seite zur weiteren Verarbeitung in JavaScript zurückgeben mächte, ist JSON das richtige Format. Es kann z.B. nützlich sein, um mit nur einem Ajax-Aufruf mehrere Elemente in der aufrufenden Seite zu aktualisieren.

Stellen Sie sich z.B. eine aufrufende Seite wie in Listing 11-27 vor. Zwei Elemente in ihr könnten möglicherweise aktualisiert werden. Ein Remote-Helfer kann allerdings nur ein Element aktualisieren (entweder titel oder name), aber nicht beide.

Listing 11-27 - Beispieltemplate für mehrfache Ajax-Aktualisierung

[php]
<h1 id="titel">Einfacher Brief</h1>
<p>Hallo, <span id="name">Name</span>,</p>
<p>Wir haben Ihre Email erhalten und werden Sie in Kürze beantworten.</p>
<p>Mit freundlichen Grüßen,</p>

Um beide Felder zu aktualisieren, könnte die Ajax-Antwort einen JSON-Header mit folgendem Array enthalten:

 [["titel", "Mein einfacher Brief"], ["name", "Herr Schmidt"]]

Dann kann der Remote-Aufruf diese Antwort einfach interpretieren und mit ein wenig JavaScript mehrere Felder auf einmal aktualisieren. Der Code in Listing 11-28 zeigt, was dem Template 11-27 noch hinzugefügt werden müsste, um diesen Effekt zu erreichen.

Listing 11-28 - Mehr als ein Element durch eine Remote-Antwort aktualisieren

[php]
<?php echo link_to_remote('Aktualisiere den Brief', array(
  'url'      => 'publishing/refresh',
  'complete' => 'updateJSON(request, json)'
)) ?>

<?php echo javascript_tag("
function updateJSON(request, json)
{
  var nbElementsInResponse = json.length;
  for (var i = 0; i < nbElementsInResponse; i++)
  {
     Element.update(json[i][0], json[i][1]);
  }
}
") ?>

Der Callback complete kann auf den json-Header aus der Antwort zugreifen und ihn einer dritten Funktion übergeben. Diese selbstgeschriebene updateJSON()-Funktion iteriert über das Array aus dem JSON-Header und aktualisiert für jeden Eintrag das Element, dessen Namen im ersten Parameter steht, mit dem Inhalt aus dem zweiten Parameter.

Listing 11-29 zeigt, wie die Action publishing/refresh eine JSON-Antwort zurückgibt.

Listing 11-29 - Einfache Action, die einen JSON-Header zurückgibt

[php]
class publishingActions extends sfActions
{
  public function executeRefresh()
  {
    $output = '[["titel", "Mein einfacher Brief"], ["name", "Herr Schmidt"]]';
    $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');

    return sfView::HEADER_ONLY;
  }

Das HTTP-Protokoll ermöglicht es, dass JSON-Daten in einem Antwort-Header gespeichert wird. Da die Antwort auch keinen weiteren Inhalt hat, wird sie von der Action unmittelbar zurückgeschickt. Das umgeht die View-Ebene komplett und ist so schnell wie ein Aufruf von ->renderText(), aber mit einer noch kleineren Antwort.

ACHTUNG Der Ansatz aus Listing 11-29 hat eine schwerwiegende Einschränkung: die maximale Größe von HTTP-Headern. Zwar gibt es keine offizielle Limitierung, aber große Header können möglicherweise fehlerhaft übertragen oder vom Browser falsch interpretiert werden. Wenn Ihr JSON-Array also groß ist, sollten Ihre Remote-Action eine normale Antwort zurückgeben, mit JSON als normales JavaScript-Array.

JSON hat sich mittlerweile zum Standard für Web-Anwendungen entwickelt. Webservices bieten häufig Antworten in JSON anstatt XML an, damit der Dienst in den Client integriert werden kann (mashup) anstatt in den Server. Wenn Sie sich also fragen, welches Format Sie zur Kommunikation zwischen Ihrem Server und einer JavaScript-Funktion verwenden sollen, ist JSON vermutlich die beste Wahl.

TIP Ab Version 5.2 bietet PHP zwei Funktionen, json_encode() und json_decode(), die ein PHP-Array nach JSON umwandeln und umgekehrt. (http://www.php.net/manual/en/ref.json.php). Sie erleichtern die Verwendung von JSON-Arrays und Ajax im Allgemeinen.

Komplexe Interaktionen mit Ajax

Unter den Ajax-Helfern von Symfony finden sie auch einige Werkzeuge, mit denen Sie komplexe Interaktionen über einen einzigen Aufruf erstellen können. Hierdurch können Sie das Benutzererlebnis mit Interaktionen erweitern, wie man sie von Desktop-Anwendungen her kennt (Drag'n'drop, Auto-Vervollständigung, direkte Bearbeitung), ohne selbst komplexe JavaScripte schreiben zu müssen. Der folgende Abschnitt beschreibt die Helfer für komplexe Interaktionen mit einfachen Beispielen. Weitere Parameter und Kniffe werden in der Dokumentation von script.aculo.us beschrieben.

ACHTUNG Wenn Sie komplexe Interaktionen verwenden, nehmen Sie sich die Zeit, diese so auszugestalten, dass sie sich normal "anfühlen". Benutzen Sie sie nur, wenn Sie sicher sind, dass das Benutzererlebnis wirklich erweitert wird, und vermeiden Sie sie, wenn das Risiko besteht, dass sie Ihre Benutzer eher verwirren.

Autocompletion (Auto-Vervollständigung)

Die Fähigkeit einer Texteingabe-Komponente, während des Tippens bereits eine Liste mit Wörtern zu zeigen, die auf die Eingabe passen, bezeichnet man als Autocompletion (Auto-Vervollständigung). Mit einem einzigen Helfer namens input_auto_complete_tag() können Sie diesen Effekt erreichen, vorausgesetzt, die Remote-Action gibt eine Antwort zurück, die als HTML-Aufzählungsliste formatiert ist, ähnlich wie in Listing 11-30 gezeigt.

Listing 11-30 - Beispiel einer Antwort, die für einen Autocompleter verwendet werden kann

<ul>
  <li>vorschlag1</li>
  <li>vorschlag2</li>
  ...
</ul>

Fügen Sie den Helfer so in ein Template, wie Sie es mit einer normalen Texteingabe machen würden, so wie im Beispiel in Listing 11-31.

Listing 11-31 - Verwendung Autocompletion-Helfers in einem Template

[php]
<?php echo form_tag('mymodule/myaction') ?>
  Autor nach Namen suchen:
  <?php echo input_auto_complete_tag('autor', 'Standardname',
    'autor/autocomplete',
    array('autocomplete' => 'off'),
    array('use_style'    => true)
  ) ?>
  <?php echo submit_tag('Suchen') ?>
</form>

Hier wird die Action autor/autocomplete jedes Mal aufgerufen, wenn der Benutzer ein Zeichen in das Feld autor eingibt. Es obliegt Ihnen, die Action so zu gestalten, dass sie eine Liste der zum Requestparameter autor passenden Einträge ermittelt und sie im entsprechenden Format (Listing 11-30) zurückzugeben. Der Helfer wird die Liste dann unterhalb des Felds autor einblenden, und sobald man auf einen der vorgeschlagenen Werte klickt oder einen mit den Cursortasten auswählt, wird die Eingabe vervollständigt, wie in Abbildung 11-3 gezeigt.

Figure 11-3 - Beispiel einer Autocompletion

Beispiel einer Autocompletion

Das dritte Argument des input_auto_complete_tag()-Helfers kann folgende Parameter aufnehmen:

  • use_style: Dekoriert die Antwortliste automatisch mit Styles.
  • frequency: Frequenz des periodischen Aufrufs (Standardwert 0.4s).
  • tokens: Ermöglicht blockweise fortschreitende Autocompletion. Wenn Sie diesen Parameter z.B. auf ',' setzen und der Benutzer jan, georg eingegeben hat, wird nur der Wert 'georg' an die Action geschickt.

NOTE Wie auch die im Folgenden beschriebenen Helfer unterstützt input_auto_complete_tag() die weiter oben beschriebenen Optionen der Remote-Helfer. Um ein besseres Benutzererlebnis zu erreichen, gilt es als guter Stil, die grafischen Effekte loading und complete zu verwenden.

Drag'n'drop

Die Möglichkeit, ein Element mit der Maus zu nehmen, es zu verschieben und woanders loszulassen, wird bei Desktop-Anwendungen häufig verwendet, im Webbrowser jedoch nur selten. Das liegt vor allem daran, dass dieses Verhalten in JavaScript nur sehr kompliziert zu programmieren ist. Glücklicherweise benötigt es in Symfony genau eine Zeile.

Das Framework stellt Ihnen zwei Helfer namens draggable_element() und drop_receiving_element() zur Verfügung, die Sie als Verhaltens-Modifikatoren ansehen können; diese fügen Code ein, der die angegebenen Elemente überwacht und um Funktionalität erweitert. Verwenden Sie sie, um ein Element als "ziehbar" (draggable) zu deklarieren, oder um ein Element zu einem Empfänger für gezogene Elemente zu machen. Ein ziehbares Element kann aufgenommen werden, indem man es mit der Maus anklickt. Bis der Mausbutton losgelassen wird, kann das Element über das Fenster bewegt bzw. gezogen werden. Das Empfängerelement ruft eine Remotefunktion auf, wenn ein ziehbares Element auf ihm losgelassen wird. Listing 11-32 zeigt diese Art der Interaktion anhand eines Warenkorbs, der Elemente empfangen kann.

Listing 11-32 - Ziebare Elemente und ein Empfängerelement in einem Warenkorb

[php]
<ul id="items">
  <li id="item_1" class="lebensmittel">Möhre</li>
  <?php echo draggable_element('item_1', array('revert' => true)) ?>
  <li id="item_2" class="lebensmittel">Apfel</li>
  <?php echo draggable_element('item_2', array('revert' => true)) ?>
  <li id="item_3" class="lebensmittel">Orange</li>
  <?php echo draggable_element('item_3', array('revert' => true)) ?>
</ul>
<div id="warenkorb">
  <p>Ihre Warenkorb ist leer</p>
  <p>Ziehen Sie die Artikel hierher, um sie dem Warenkorb hinzuzufügen</p>
</div>
<?php echo drop_receiving_element('warenkorb', array(
  'url'        => 'warenkorb/dazu',
  'accept'     => 'food',
  'update'     => 'warenkorb',
)) ?>

Jede Zeile aus der ungeordneten Liste kann mit der Maus aufgenommen und durch das Fenster gezogen werden. Wird sie losgelassen, kehrt sie auf ihre ursprüngliche Position zurück. Wenn sie über dem Element warenkorb losgelassen wird, wird ein Remoteaufruf auf die Action warenkorb/dazu ausgelöst. Die Action kann anhand des Requestparameters id feststellen, welcher Artikel auf das warenkorb-Element gezogen wurde. Listing 11-32 simuliert also eine echte Einkaufs-Sitzung: Sie nehmen Artikel auf, lassen Sie auf dem Warenkorb fallen und gehen dann zur Kasse.

TIP In Listing 11-32 wurden die Helferaufrufe direkt nach den Elementen eingefügt, die sie verändern. Dies ist keine Notwendigkeit. Sie können genauso gut alle Aufrufe von draggable_element() und drop_receiving_element() am Ende des Templates gruppieren. Wichtig ist nur das erste Argument im Helferaufruf, das den Bezeichner des zu verändernden Elements angibt.

Der Helfer draggable_element() nimmt folgende Parameter entgegen:

  • revert: Wenn true, wird das Element auf seine ursprüngliche Position zurückkehren, wenn es losgelassen ist. Sie können auch eine beliebige Funktion angeben, die aufgerufen werden soll, wenn der Ziehvorgang beendet wird.
  • ghosting: Das Element wird geklont und der Klon gezogen, so dass das Original an seinem Platz bleibt, bis der Klon losgelassen wird.
  • snap: Bei false findet kein Einrasten statt. Wenn Sie jedoch xy, [x,y] oder function(x,y){ return [x,y] } angeben, kann das Element nur zu den Kreuzungspunkten eines Gitters mit dem Interval x und y gezogen werden

Der Helfer drop_receiving_element() nimmt folgende Parameter entgegen:

  • accept: Ein String oder ein Stringarray mit CSS-Klassenangaben. Das Element akzeptiert dann nur losgelassene Elemente, die mindestens eine dieser CSS-Klassen besitzen.
  • hoverclass: CSS-Klasse, die das Element zusätzlich erhält, wenn der Benutzer ein akzeptierbares Element darüberzieht.

Sortierbare Listen

Ein weiterer Verwendungszweck für ziehbare Elemente ist die Möglichkeit, eine Liste zu ordnen, indem man die Elemente mit der Maus bewegt. Der Helfer sortable_element() fügt dieses Verhalten zu einem Element hinzu, und Listing 11-33 ist ein gutes Beispiel, wie dieses Feature umgesetzt wird.

Listing 11-33 - Beispiel einer sortierbaren Liste

[php]
<p>Was mögen Sie am liebsten?</p>
<ul id="order">
  <li id="item_1" class="sortable">Möhren</li>
  <li id="item_2" class="sortable">Äpfel</li>
  <li id="item_3" class="sortable">Orangen</li>
  // Rosenkohl mag sowieso niemand
  <li id="item_4">Rosenkohl</li>
</ul>
<div id="feedback"></div>
<?php echo sortable_element('order', array(
  'url'    => 'item/sort',
  'update' => 'feedback',
  'only'   => 'sortable',
)) ?>

Mit ein wenig Magie im sortable_element()-Helfer wird das <ul>-Element sortierbar, d.h. die Kindknoten können per Drag'n'drop neu geordnet werden. Jedes mal, wenn der User ein Element aufnimmt und an einer neuen Position wieder loslässt, wird ein Ajax-Request mit folgenden Parametern durchgeführt:

POST /sf_sandbox/web/frontend_dev.php/item/sort HTTP/1.1
  order[]=1&order[]=3&order[]=2&_=

Die ganze sortierte Liste wird als Array übergeben (im Format order[$rang]=$id, $rang beginnt bei 0, und das Feld $id enthält das, was nach dem Unterstrich (_) in der id des Elements steht). Die id des sortierbaren Elements (im obigen Beispiel order) wird als Name des Parameter-Arrays verwendet.

Der sortable_element()-Helfer akzeptiert folgende Parameter:

  • only: Ein String oder ein Stringarray mit CSS-Klassenangaben. Es können im sortierbaren Element nur die Kindknoten bewegt werden, die mindestens eine dieser CSS-Klassen besitzen.
  • hoverclass: CSS-Klasse, die das Element zusätzlich erhält, wenn der Mauszeiger darüber liegt.
  • overlap: Wenn horizontal, werden die Elemente inline angezeigt, bei vertical (der Standardwert) wird nur ein Element pro Zeile angezeigt (wie im Beispiel).
  • tag: Wenn die zu sortierende Liste keine <li>-Elemente sind, müssen Sie hierüber angeben, welche Kindknoten ziehbar sind (z.B. div oder dl).

TIPP Seit Symfony 1.1 können Sie den sortable_element()-Helfer auch ohne die Option url verwenden, wodurch dann beim Umsortieren kein AJAX-Aufruf durchgeführt wird. Dies ist z.B. dann nützlich, wenn Sie den AJAX-Aufruf erst beim Drücken eines Buttons durchführen wollen.

"Am Ort" bearbeiten (In-Place Editing)

Immer mehr Webanwendungen erlauben es den Benutzern, Seiteninhalte direkt auf der Seite zu bearbeiten, ohne den Inhalt erst in einem Formular anzeigen zu müssen. Das Prinzip dieser Interaktion ist einfach: Ein Textblock wird hervorgehoben, sobald der Benutzer mit der Maus darüberfährt. Wenn er dann in den Block klickt, wird der Text in ein Eingabefeld verwandelt, das mit dem Blockinhalt gefüllt wird. Zusätzlich erscheint ein Speichern-Button. Der Benutzer kann den Text im Eingabefeld bearbeiten, und wenn er ihn speichert, verschwindet das Feld und der Text wird wieder normal angezeigt. Mit Symfony können Sie diese Editier-Verhalten mit dem Helfer input_in_place_editor_tag() zu einem Element hinzufügen. Listing 11-34 zeigt, wie Sie ihn einsetzen.

Listing 11-34 - Beispiel zu editierbarem Text

[php]
<div id="bearbeite_mich">Mich können Sie bearbeiten!</div>
<?php echo input_in_place_editor_tag('bearbeite_mich', 'mymodule/myaction', array(
  'cols'        => 40,
  'rows'        => 10,
)) ?>

Sobald ein Benutzer den editierbaren Text anklickt, wird er durch ein Texteingabefeld ersetzt. Dieses ist bereits mit dem Text gefüllt und kann bearbeitet werden. Wenn das Formular abgeschickt wird, wird per Ajax die Action mymodule/myaction mit dem bearbeiteten Text als Parameter value aufgerufen. Das Ergebnis der Action aktualisiert dann das editierbare Element. Dies ist sehr einfach zu schreiben und sehr mächtig.

Der Helfer input_in_place_editor_tag() akzeptiert folgende Parameter:

  • cols and rows: Die Größe des Texteingabefelds, das beim Editieren erscheint (wenn rows größer als 1 ist, wird es ein <textarea>-Tag).
  • loadTextURL: Der URI einer Action, die aufgerufen wird, um den zu bearbeiteten Text anzuzeigen. Das ist nützlich, wenn der Inhalt des editierbaren Elements spzeielle Formatierungen verwendet und Sie den Benutzer diesen ohne Formatierungen bearbeiten lassen wollen.
  • save_text und cancel_text: Der Text auf dem Speichern-Link (Standard: "ok") und auf dem Abbrechen-Link (Standard: "cancel").

Weitere Optionen und deren Auswirkungen sind auf http://mir.aculo.us/2007/7/17/in-place-editing-the-summer-2007-rewrite/ dokumentiert.

Zusammenfassung

Wenn Sie es leid sind, in Ihre Templates JavaScript-Code einzufügen, um clientseitige Verhaltensweisen hervorzurufen, eröffnen Ihnen die JavaScript-Helfer eine einfache Alternative. Sie automatisieren nicht nur das grundlegende Linkverhalten und das Aktualisieren von Elementen, sondern stellen auch einen Weg zu Verfügung, um Ajax-Interaktionen in einem Rutsch zu entwickeln. Mit Hilfe der mächtigen Syntaxerweiterung von Prototype und den großartigen grafischen Effekten von script.aculo.us sind selbst komplexe Interaktionen mit wenigen Zeilen geschrieben.

Und da mit Symfony ein interaktive Applikationen so einfach wie statische Seiten erstellt werden können, können Sie davon ausgehen, dass fast alle Interaktionen von Desktop-Anwendungen jetzt auch für Webanwendungen verfügbar sind.