JSON View für CakePHP
September 29th, 2006
Tags: ajax, cakephp, html, javascript, opensource, prototype, Tutorials, webservicesIch bin endlich mal auf den JSON trip gekommen ;) Gibt es ja schon seit einiger Zeit, aber ich dachte mir immer .. "warum? xml is auch ok.."
Aber direkt in Javascript ein Daten-Objekt zu haben ist einfach göttlich. Vor allem in Verbindung mit Prototype 1.5.0 RC1. Bevor ich jetzt zum eigentlichen Thema des Posts komme möchte ich die Möglichkeit des Zusammenspiels von JSON, CakePHP und Prototype verdeutlichen.
Bei Cake gibt es ja die Model Funktionen findBy, findAll, read, etc... welche ein assoziatives Daten-Array aus der Datenbank liefert. Nun mit meinem JSON View wird das Array in JSON umgewandelt, die Daten als text/x-json geliefert und der notwendige X-JSON header für Prototype gesetzt. Super ist auch das jedes Modell ein eigenes Objekt mit Kindobjekten darstellt!
Um den View zu benutzen braucht man lediglich $this->view = "json"; in die Controller-Action zu schreiben und per $this->set die Daten bereitstellen. Super easy.. :-)
Mit Prototype 1.5.0 RC1 gibt es neue String Funktionen und die Template Klasse. Die Objekt Funktion .each( ) kommt hier zum Einsatz und macht es möglich mit nur wenigen Zeilen ein MySQL Result über AJAX in eine schicke HTML Liste zu verwandeln. JSON ist eben ein purer Datencontainer und genau das gibt uns die Freiheit und Geschwindigkeit die wir wollen.
Hier die von bake.php erstellte und von mir modfizierte Controller-Action:
-
function index($output_json = false)
-
{
-
$this->User->recursive = 0;
-
if ($output_json)
-
{
-
$this->view = "json";
-
$this->set('json_output', $this->User->findAll());
-
}
-
else
-
{
-
$this->set('users', $this->User->findAll());
-
}
-
}
Sobald ich der url /users/index/ ein "json" anhänge (bzw. irgendwas... *hust*) wird pures JSON geliefert. Über die Weiche bin ich mir noch nicht einig, aber das ist ja nur Kosmetik. Mir ging es erstmal darum das es läuft .. eine Funktion, zwei Ausgabemöglichkeiten.
So.. nun kommt der Clou.. Die Ajax Funktion welche mir eine Liste aller Benutzer erstellt ohne das ich grossartig mich um irgendwelche Bezeichner kümmern muss.
-
getUsers: function(){
-
var userTpl = new Template('<ul><li>#{username}</li><li>#{email}</ul>');
-
new Ajax.Request('/users/index/json', {
-
onComplete:function(html,json){
-
json.each(function(Data){
-
$('out').innerHTML+=userTpl.evaluate(Data.User);
-
});
-
}
-
});
-
}
Wie man im Template schon erkennen kann, verwende ich die Feldnamen der MySQL-Tabelle als Variable. Also fast genauso wie in CakePHP selbst. Bei dem onComplete müssen zwei Parameter gesetzt werden wobei zu beachten ist, dass der erste eigentlich nicht gebraucht wird (html) und dass der zweite (json) nur in einem Ajax-Objekt gefüllt wird wenn auch wirklich JSON ausgeliefert wird!
Das ist der eigentliche Grund für das Erstellen einer neuen View-Klasse welche die relevanten Header setzt. Innerhalb der each-Schleife stehen alle Model Arrays zur Verfügung die von CakePHP Assoziationen herrühren. In meiner Testumgebung ist das natürlich "User", aber auch "Profile". Ihr könnt das wie gewohnt mit $this->recursive regulieren. Die Anzahl und Tiefe der verfügbaren Objekte ist, soweit ich weiss, unbegrenzt. Mit der .each() Funktion lässt sich also das gesamte Ergebnis-Set in Javascript problemlos aufsplitten und verwerten. $('out') ist lediglich ein leeres DIV. Man muss allerdings beachten das die Template Klasse HTML generiert und keine DOM Objekte - was zum Teil eine Limitierung sein kann.
Alternativ zu der Template() Methode, kann man sich natürlich auch dem Script.aculo.us Builder bedienen um mit den Ergebnissen echte DOM Objekte zu erzeugen. Beispiel:
-
json.each(function(Data){
-
$('out').appendChild( Builder.node('p', Data.User.username) );
-
});
Ist Geschmacksache und auch abhängig davon ob man Script.aculo.us benutzen mag. Mit dem Builder kann man selektiver sein und sich freier im DOM bewegen.
Kommen wir nun zum View und der JSON Klasse.
-
<?php
-
/**
-
* JSON View
-
*
-
* @license MIT-style
-
* @author Kjell Bublitz
-
* @version 1.0
-
*/
-
vendor('JSON');
-
-
class JsonView extends View
-
{
-
function render($action = null, $layout = 'ajax')
-
{
-
$expectedVar = 'json_output';
-
-
{
-
$JsonClass = new Services_JSON();
-
-
$rawControllerData = $this->_viewVars[$expectedVar];
-
$jsonEncoded = $JsonClass->encode($rawControllerData);
-
}
-
else
-
{
-
}
-
-
// uncached, with xjson bit for prototype.js
-
-
// default echo
-
}
-
}
-
?>
Die Variable "expectedVar" enthält den Namen der Variable welche es im Controller zu setzen gilt. Wie oben im Controller zu sehen.
-
$this->set('json_output', $this->User->findAll())
Wenn diese gefunden wird geht von da an alles automatisch. Da dieser View nur für AJAX gedacht ist habe ich auch gleich das caching abgestellt. Den Layout-Parameter habe ich auf das Standard-Layout "Ajax" gestellt - der Einfachheit halber.
Dieser View muss als json.php im Verzeichnis "/view/" gespeichert werden. Im Controller (oder Action) müsst ihr dann $view auf "json" setzen und der View ist aktiv.
Nun zur Klasse: Wie auch die Json Komponente von Zach Cox, benutze ich ebenfalls die Services_JSON Klasse von Michal Megurski. Ich konnte bisher noch keine Probleme festellen. Vielleicht müsste man sich noch um das Charset-Encoding kümmern...
Wenn ihr die JSON Klasse von Pear.net runtergeladen habt, kopiert diese in euer "/vendor/" Verzeichnis und es sollte funktionieren. So.. und nun wünsche ich viel Spass :-)


October 19th, 2006 at 08:30
Wollte lediglich testen ob man sich registrieren muss um abzustimmen ;)
Gruß
Markus
April 20th, 2007 at 12:50
I also did some work on JSON with Cakephp, I've ported the Services_JSON package to a Cakephp component which makes working with it a little easier.
By the way do you mind if you translate your post to english?
April 20th, 2007 at 12:55
By the way do you mind if I translate your post to english?
September 17th, 2007 at 11:02
Allerdings ist mir beim Einbau in Cake 1.2 aufgefallen, dass von 1.1 zu 1.2 $this->_viewVars in $this->viewVars geändert wurde!