<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Der Interaktionsdesigner - PHP, jQuery und CSS &#187; CakePHP</title>
	<atom:link href="http://www.interaktionsdesigner.de/category/cakephp/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.interaktionsdesigner.de</link>
	<description>Webentwicklung mit TYPO3, jQuery, CakePHP und Spaß an neuen Projekten</description>
	<lastBuildDate>Wed, 07 Dec 2011 14:26:54 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.4</generator>
		<item>
		<title>CakePHP Expandable Behavior</title>
		<link>http://www.interaktionsdesigner.de/2011/02/11/cakephp-expandable-behavior/</link>
		<comments>http://www.interaktionsdesigner.de/2011/02/11/cakephp-expandable-behavior/#comments</comments>
		<pubDate>Fri, 11 Feb 2011 14:24:47 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=786</guid>
		<description><![CDATA[Darf ich vorstellen: mein CakePHP Expandable Behavior. Ein einfaches, kleines Script welches das hinzufügen von neuen Spalten in einem beliebigen CakePHP Model erlaubt, ohne die Datenbank zu verändern. Neue Spalten werden in einer Tabelle und die Inhalte in einer anderen Tabelle gespeichert. Beim Auslesen werden die Ergebnisse so zusammen gefasst, dass es für den normalen [...]]]></description>
			<content:encoded><![CDATA[<p>Darf ich vorstellen: mein <strong>CakePHP Expandable Behavior</strong>. Ein einfaches, kleines Script welches das hinzufügen von neuen Spalten in einem beliebigen CakePHP Model erlaubt, <strong>ohne die Datenbank zu verändern</strong>. Neue Spalten werden in einer Tabelle und die Inhalte in einer anderen Tabelle gespeichert.</p>
<p>Beim Auslesen werden die Ergebnisse so zusammen gefasst, dass es für den normalen Programmierer kein Unterschied gibt. Die neuste Version findet man auf <a title="CakePHP Expandable Behavior" href="https://github.com/apeunit/Expandable" target="_blank">GitHub</a>, die Erklärung zur Installation und Nutzung in diesem Eintrag.</p>
<p><span id="more-786"></span></p>
<h2>Voraussetzungen</h2>
<p>Das Expandable Behavior erwartet zwei Tabellen <code>Keys</code> und <code>Values</code>:</p>
<pre>CREATE TABLE IF NOT EXISTS 'keys' (
&nbsp;&nbsp;'id' int(3) unsigned NOT NULL AUTO_INCREMENT,
&nbsp;&nbsp;'model' varchar(25) COLLATE utf8_bin NOT NULL,
&nbsp;&nbsp;'key' varchar(50) COLLATE utf8_bin NOT NULL,
&nbsp;&nbsp;PRIMARY KEY ('id')
) ENGINE=InnoDB&nbsp;&nbsp;DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE IF NOT EXISTS 'values' (
&nbsp;&nbsp;'id' int(6) unsigned NOT NULL AUTO_INCREMENT,
&nbsp;&nbsp;'key_id' int(3) NOT NULL,
&nbsp;&nbsp;'model_id' int(6) NOT NULL,
&nbsp;&nbsp;'value' text COLLATE utf8_bin NOT NULL,
&nbsp;&nbsp;PRIMARY KEY (`id`)
) ENGINE=InnoDB&nbsp;&nbsp;DEFAULT CHARSET=utf8 COLLATE=utf8_bin;</pre>
<p>Außerdem sollte man das <strong>Containable Behavior</strong> für das Model aktiviert haben. Ansonsten fehlt nur noch die Datei aus dem <a title="CakePHP Expandable Behavior" href="https://github.com/apeunit/Expandable" target="_blank">Repository auf GitHub</a>.</p>
<h2>Installation</h2>
<p>Ein schönes Beispiel ist das Model <code>Document</code>. In meinem Projekt gibt es die Möglichkeit beliebige Dateien hochzuladen, jede Datei hat dabei andere Eigenschaften die dazu mit gespeichert werden sollen. Jetzt könnte man alle Spalten anlegen und nur die benötigten ausfüllen, was aber zu einer äußerst unangenehmen Tabelle führt. Da doch lieber das Expandable Behavior. Die Tabelle <code>documents</code> besitzt folgende Spalten:</p>
<pre>ID
created
modified
user_id
title
file
extension
is_hidden</pre>
<p>Für ein OpenOffice Dokument soll der Benutzer jetzt die Anzahl Seiten eintragen können. Das macht für ein Video natürlich keinen Sinn. Jetzt kommt das Expandable Behavior ins Spiel.</p>
<pre>class Document extends AppModel {
&nbsp;&nbsp;var $actsAs = array('Containable', 'Expandable');
}</pre>
<p>Fertig!</p>
<h2>Speichern</h2>
<p>Im View kann man jetzt ein Formular mit beliebigen Feldern anlegen, zum Beispiel für ein neues OpenOffice Dokument:</p>
<pre>&lt;?php
echo $this-&gt;Form-&gt;create('Document');
echo $this-&gt;Form-&gt;input('title');
echo $this-&gt;Form-&gt;input('file', array('type' =&gt; 'file'));
echo $this-&gt;Form-&gt;input('pages');
echo $this-&gt;Form-&gt;end('Save');
?&gt;</pre>
<p>Das Feld <code>pages</code> wird beim Speichern erkannt, eventuell in der Datenbank <code>keys</code> ein neuer Eintrag erzeugt und in <code>values</code> die Inhalte gespeichert. So einfach.</p>
<p>Neue Felder werden nur angelegt, wenn <code>debug</code> größer als 0 ist.</p>
<h2>Validierung</h2>
<p>Es lassen sich wie gewohnt alle Cake Validierungsregeln auf die dynamischen Felder anwenden.</p>
<pre>class Document extends AppModel {
&nbsp;&nbsp;$validate = array('pages' =&gt; array('rule' =&gt; 'numeric'));
}</pre>
<h2>Auslesen</h2>
<p>Hier muss man auf eine Sache achten. Normalerweise sieht die Abfrage so aus:</p>
<pre>$document = $this-&gt;Document-&gt;find('first', array(
&nbsp;&nbsp;'conditions' =&gt; array('Document.id' =&gt; $document_id),
));</pre>
<p>Da erhält man jetzt für jeden dynamisch angelegten Key einen leeren Eintrag weil die Inhalte nicht automatisch geladen werden. Das muss beim auslesen noch mit angegeben werden:</p>
<pre>$document = $this-&gt;Document-&gt;find('first', array(
&nbsp;&nbsp;'conditions' =&gt; array('Document.id' =&gt; $document_id),
&nbsp;&nbsp;'contain' =&gt; array('Value')
));</pre>
<p>Über das Containable Behavior werden alle zugehörigen Inhalte ausgelesen, das Expandable Behavior kümmert sich um den Rest.</p>
<pre>$this-&gt;Document-&gt;getKeys();</pre>
<p>Die Funktion <code>getKeys()</code> aus dem Behavior liefert ein Array mit allen dynamisch angelegten Schlüsseln. Ganz praktisch um dynamische Formulare aufzubauen.</p>
<h2>ToDo</h2>
<p>Ganz fertig ist die Arbeit noch nicht.</p>
<ul>
<li>Ich habe im <code>beforeFind()</code> Callback alles versucht, aber ich kriege Cake einfach nicht dazu die Inhalte automatisch mit auszulesen.</li>
<li>Es wäre natürlich nett die <code>values</code> auch im Paginator nutzen zu können. Das muss noch untersucht werden.</li>
<li>Die Keys sollten verschiedene Typen ermöglichen, z.B. für Zahlen, Text oder Floats. Im Moment wird einfach alles in einem Feld vom Typ <code>Text</code> gespeichert.</li>
</ul>
<h2>Fazit</h2>
<p>Das Behavior funktioniert bisher super, ist allerdings noch in einer sehr frühen Entwicklungsphase. Wer einen Fehler findet ist herzlich eingeladen einen Patch über <a title="CakePHP Expandable Behavior" href="https://github.com/apeunit/Expandable" target="_blank">GitHub</a> zu schicken.</p>
<p>Bisher macht es großen Spaß neue Felder anzulegen ohne sich über die Datenbank Gedanken zu machen. Mal sehen was daraus noch wird.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2011/02/11/cakephp-expandable-behavior/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mobile Webseiten mit jQuery Mobile und CakePHP</title>
		<link>http://www.interaktionsdesigner.de/2011/01/27/mobile-webseiten-mit-jquery-mobile-und-cakephp/</link>
		<comments>http://www.interaktionsdesigner.de/2011/01/27/mobile-webseiten-mit-jquery-mobile-und-cakephp/#comments</comments>
		<pubDate>Thu, 27 Jan 2011 18:54:07 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[jQuery Mobile]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=768</guid>
		<description><![CDATA[Zwei großartige Frameworks, vereint um die mobile Welt zu verbessern, bereichern und verändern. Mit CakePHP baut man in wenigen Schritten eine mobile Applikation die auf allen Smartphones gut aussieht mit Hilfe von jQuery Mobile. CakePHP vorbereiten Um in allen Teilen der Applikation zu erkennen, ob ein mobiles Endgerät auf die Seite zugreift, definiert man im [...]]]></description>
			<content:encoded><![CDATA[<p>Zwei großartige Frameworks, vereint um die mobile Welt zu verbessern, bereichern und verändern. Mit <strong>CakePHP</strong> baut man in wenigen Schritten eine mobile Applikation die auf allen Smartphones gut aussieht mit Hilfe von <strong>jQuery Mobile</strong>.</p>
<p><span id="more-768"></span></p>
<h2>CakePHP vorbereiten</h2>
<p>Um in allen Teilen der Applikation zu erkennen, ob ein mobiles Endgerät auf die Seite zugreift, definiert man im AppController eine Variable <code>$isMobile</code>.</p>
<pre>class AppController extends Controller {
&nbsp;&nbsp;var $isMobile = false;
}</pre>
<p>In der Funktion <code>beforeFilter()</code> wird mit Hilfe des RequestHandlers das mobile Endgerät erkannt:</p>
<pre>function beforeFilter() {
&nbsp;&nbsp;if($this-&gt;RequestHandler-&gt;isMobile() || isset($this-&gt;params['url']['mobile'])) {
&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;layout = 'mobile';
&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;isMobile = true;
&nbsp;&nbsp;}
&nbsp;&nbsp;$this-&gt;set('is_mobile', $this-&gt;isMobile);
}</pre>
<p>Die Variable <code>isMobile</code> wird auf <code>true</code> gesetzt und das Layout <code>mobile</code> genutzt. Um es in der gewohnten Umgebung zu testen, erlaube ich auch den URL Parameter <code>mobile</code>, mit dem jeder View per <code>?mobile=1</code> im mobilen Layout angezeigt werden kann.</p>
<p>Bei einer richtig guten Applikation ist die Arbeit damit getan und alle Views funktionieren auch in der mobilen Version. Realistischer ist aber das man in der mobilen Version anderen HTML Code braucht und nur einen Teil der Funktionalität zur Verfügung stellt. Das erreicht man über angepasste Views.</p>
<p>In der Funktion <code>beforeRender()</code> kann der View verändert werden. Meine Lieblingsfunktion sieht so aus:</p>
<pre>function beforeRender() {
&nbsp;&nbsp;if($this-&gt;isMobile) {
&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;action = 'mobile/'.$this-&gt;action;
&nbsp;&nbsp;}
}</pre>
<p>Entweder man fügt diese Funktion im <code>AppController</code> ein und leitet für die gesamte Applikation alle Views in den mobile Ordner weiter, oder nur in den betreffenden Controllern.</p>
<p>Nach dem abarbeiten der Funktion im Controller wird der View in den mobile Ordner weiter geleitet. Cake sucht jetzt für den Aufruf von <code>/users/login</code> den View in <code>app/views/users/mobile/login.ctp</code>.</p>
<h2>jQuery Mobile im CakePHP Layout</h2>
<p>Jetzt kommt der schöne Teil! Eine mobile Applikation mit jQuery mobile bauen. Grundlage ist ein HTML5 Grundgerüst:</p>
<pre>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&nbsp;&nbsp;&lt;head&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;title&gt;HTML5 Gerüst&lt;/title&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;meta charset="utf8"&gt;
&nbsp;&nbsp;&lt;/head&gt;
&nbsp;&nbsp;&lt;body&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;h1&gt;Hallo Welt&lt;/h1&gt;
&nbsp;&nbsp;&lt;/body&gt;
&lt;/html&gt;</pre>
<p>Die üblichen Variablen aus dem CakePHP View können natürlich nach belieben eingesetzt werden, ich denke da an <code>$title_for_layout</code> und <code>$content_for_layout</code>. Wirklich entscheidend ist aber jQuery mobile.</p>
<p>Auf der Downloadseite gibt es ein praktisches Copy Paste Snippet um jQuery und jQuery Mobile aus dem CDN einzubinden:</p>
<pre>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&nbsp;&nbsp;&lt;head&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;title&gt;HTML5 Gerüst&lt;/title&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;meta charset="utf8"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.css" /&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;script src="http://code.jquery.com/jquery-1.4.4.min.js"&gt;&lt;/script&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;script src="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.js"&gt;&lt;/script&gt;
&nbsp;&nbsp;&lt;/head&gt;
&nbsp;&nbsp;&lt;body&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;div data-role="page"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;div data-role="header"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;h1&gt;Hallo Welt!&lt;/h1&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/div&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;div data-role="content"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;p&gt;Meine erste mobile Seite&lt;/p&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/div&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;/div&gt;
&nbsp;&nbsp;&lt;/body&gt;
&lt;/html&gt;</pre>
<p>Bei jQuery Mobile läuft sehr viel über das <code>data</code> Attribut eines Elements. Seite, Kopf und Inhalt werden darüber deklariert. In jQuery ließt man diese Eigenschaften übrigens sehr einfach über die <code>data()</code> Funktion aus.</p>
<pre>$('div').data('role'); // returns string
$('div').data(); // returns object</pre>
<h2>Konfiguration</h2>
<p>Es gibt Standardeinstellungen, Callbacks und Events auf die man in Javascript reagieren kann. Allerdings muss man den eigenen Code vor dem Einbinden von jQuery Mobile anlegen! So siehts aus:</p>
<pre>&lt;script src="jquery.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
&nbsp;&nbsp;$(document).bind("mobileinit", function(){
&nbsp;&nbsp;&nbsp;&nbsp;//apply overrides here
&nbsp;&nbsp;});
&lt;/script&gt;
&lt;script src="jquery.mobile.min.js"&gt;&lt;/script&gt;</pre>
<h2>Formulare</h2>
<p>jQuery Mobile erwartet einen Container der das Label und das Inputfeld umschließt. Kein Problem sagt der Cake Entwickler erst, dann bemerkt er aber das der Container das Data Attribut <code>fieldcontainer</code> braucht. Zum Glück auch kein Problem, da man dem FormHelper beim erstellen des Formulars Standardeigenschaften mitgeben kann:</p>
<pre>echo $this-&gt;Form-&gt;create('User', array(
&nbsp;&nbsp;'action' =&gt; 'login',
&nbsp;&nbsp;'inputDefaults' =&gt; array(
&nbsp;&nbsp;&nbsp;&nbsp;'div' =&gt; array('data-role' =&gt; 'fieldcontain')
&nbsp;&nbsp;),
));</pre>
<p>jQuery Mobile kümmert sich jetzt von selbst um die korrekte Darstellung. Bei viel Platz werden Label und Inputelement nebeneinander, bei weniger Platz untereinander dargestellt. Submitbuttons werden automatisch schick dargestellt.</p>
<h2>Fazit</h2>
<p>Es gibt eine Menge Dinge man beim Arbeit mit jQuery Mobile entdecken kann. Es macht großen Spaß und funktioniert gut über die meisten mobilen Endgeräte hinweg. Die Dokumentation von jQuery Mobile ist mit jQuery Mobile erstellt. Dazu ein Trick: Wenn man die Seite gefunden hat, die genau das darstellt was man sucht, dann entfernt man die Raute aus der URL und öffnet anschließend den Quelltext der Seite um zu verstehen wie das Feature aufgebaut ist.</p>
<p>Da alles dynamisch geladen wird, vom Framework angepasst und erweitert wird, wird man im Firebug durch jede Menge Kram abgelenkt. Will man also herausfinden wie durchsuchbare Listen aufgebaut sind, navigiert man auf die entsprechende Seite in der Doku:</p>
<pre>http://jquerymobile.com/demos/1.0a2/#docs/lists/lists-search.html</pre>
<p>Und entfernt die Raute:</p>
<pre>http://jquerymobile.com/demos/1.0a2/docs/lists/lists-search.html</pre>
<p>Und erkennt das es äußerst einfach, genial und zukunftsfähig aufgebaut ist:</p>
<pre>&lt;div data-role="page"&gt;
&nbsp;&nbsp;&lt;div data-role="header"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;h1&gt;Search filter bar&lt;/h1&gt;
&nbsp;&nbsp;&lt;/div&gt;&lt;!-- /header --&gt;
&nbsp;&nbsp;&lt;div data-role="content"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;ul data-role="listview" data-filter="true"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Acura&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Audi&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;BMW&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Cadillac&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Chrysler&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Dodge&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Ferrari&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Ford&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;GMC&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Honda&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Hyundai&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Infiniti&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Jeep&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Kia&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Lexus&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Mini&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Nissan&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Porsche&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Subaru&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Toyota&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Volkswagon&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="index.html"&gt;Volvo&lt;/a&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/ul&gt;
&nbsp;&nbsp;&lt;/div&gt;&lt;!-- /content --&gt;
&lt;/div&gt;&lt;!-- /page --&gt;</pre>
<p>Ist das nicht schön?</p>
<p>Beim schreiben sind mir eine ganze Menge Themen aufgefallen die unbedingt noch behandelt werden müssen: <strong>Links</strong>, <strong>Ajax</strong> und <strong>Maps</strong>.</p>
<p>Wie sind deine Erfahrungen mit jQuery Mobile?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2011/01/27/mobile-webseiten-mit-jquery-mobile-und-cakephp/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Das perfekte Zusammenspiel zwischen Git und CakePHP</title>
		<link>http://www.interaktionsdesigner.de/2010/06/05/das-perfekte-zusammenspiel-zwischen-git-und-cakephp/</link>
		<comments>http://www.interaktionsdesigner.de/2010/06/05/das-perfekte-zusammenspiel-zwischen-git-und-cakephp/#comments</comments>
		<pubDate>Sat, 05 Jun 2010 21:13:53 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[Git]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=706</guid>
		<description><![CDATA[Die großen Projekte entwickeln wir bei der Ape Unit GmbH im Moment mit Teams zwischen 3 und 5 Kollegen. Um sich nicht gegenseitig das Leben schwer zu machen, nutzen wir Git zur Codeverwaltung und CakePHP für anspruchsvolle Webapplikationen. Mit ein paar Tricks und einer handvoll Wissen wir dieses Zusammenspiel ein Hort der Freude! Dieser Artikel [...]]]></description>
			<content:encoded><![CDATA[<p>Die großen Projekte entwickeln wir bei der <a title="Webentwicklung und Medien aus Berlin" href="http://www.apeunit.com" target="_blank">Ape Unit GmbH</a> im Moment mit Teams zwischen 3 und 5 Kollegen. Um sich nicht gegenseitig das Leben schwer zu machen, nutzen wir <strong>Git</strong> zur Codeverwaltung und <strong>CakePHP</strong> für anspruchsvolle Webapplikationen.</p>
<p>Mit ein paar <strong>Tricks</strong> und einer handvoll Wissen wir dieses Zusammenspiel ein Hort der Freude! Dieser Artikel beschreibt das <strong>Zusammenspiel zwischen Versionsverwaltung und Framework</strong>.</p>
<p><span id="more-706"></span></p>
<h2>Das Repository</h2>
<p>Auf unserem Entwicklungsserver liegt das <strong>zentrale Coderepository</strong>. Hier wird es zum Projektstart angelegt und jeder beteiligte Entwickler kriegt bei bedarf Zugriff darauf. Zum starten wird nur eine leere Datei versionisiert, der Projektleiter legt dann alle benötigten Grundlagen von seinem Rechner aus an.</p>
<pre># Auf dem Server
$ mkdir PROJEKT.git
$ cd PROJEKT.git
$ touch init.txt
$ git init
$ git add .
$ git commit -m "Init"</pre>
<p>Auf dem eigenen Rechner wird vom Projekt (eventuell per VPN von zuhause) eine Arbeitskopie erstellt.</p>
<pre># Zuhause
$ cd Ordner_fuer_alle_Projekte
$ git clone gitosis@SERVER:git/PROJEKT.git</pre>
<p>Beim clonen legt git einen neuen Ordner mit dem Namen des Projektes an.</p>
<h2>CakePHP</h2>
<p>Im ersten Schritt werden alle Cake Dateien in den Ordner kopiert. Anschließend werden die benötigten Tabellen in der Datenbank angelegt. Über die<strong> Cake Console</strong> werden dann alle benötigten Dateien gebacken.</p>
<pre>$ cd cake/console/
$ ./cake bake</pre>
<p>Jetzt kommt der Trick! Um in Zukunft jeden Stress mit der Datenbank zu vermeiden,<strong> backt man das Datenbankschema mit in die Cake Applikation</strong>.</p>
<pre>$ ./cake schema generate</pre>
<p>Mit diesem Befehl liest Cake die Datenbank aus und legt die Struktur in der PHP Datei unter <em>/config/schema/schema.php</em> ab. Ändert ein Entwickler eine Tabelle auf seinem Rechner, muss er das Schema neu erstellen und in Git bekannt machen.</p>
<pre>$ ./cake schema generate
$ cd ../..
$ git add .
$ git commit -m "Einiges ist passiert, inkl. neuer Datenbank"</pre>
<p>Der Kollege oder alternativ der gleiche Entwickler zuhause holt sich die neuste Version vom Server und aktualisert mit Cake Magie seine Datenbank.</p>
<pre>$ git pull
$ cd cake/console
$ ./cake schema update</pre>
<p>Fertig ist die Datenbank!</p>
<h2>Dateien ignorieren</h2>
<p>Beim initialen anlegen macht es Sinn, <strong>Git einige Dateien und Ordner zu entziehen</strong>, die nicht versioniert werden müssen, da sie bei jedem Entwickler individuell vorhanden sind. Dafür legt man im Root Verzeichnis vom Projekt eine Datei Names<strong> .gitignore</strong> an.</p>
<p>Hier werden Dateien und Ordner festgehalten, die nicht eingebunden werden:</p>
<pre>app/tmp/
config/database.php</pre>
<h2>Cache Dateien anlegen</h2>
<p>Das Problem ist bei der oben verwendeten <strong>.gitignore </strong>Datei, dass die <strong>Caches leer sind und keine Zugriffsrechte</strong> darauf bestehen. Deshalb muss man beim erstellen des Projekts die nötigen Ordner anlegen und die entprechenden Zugriffsrechte vergeben.</p>
<pre># cd app/
# mkdir tmp
# mkdir tmp/cache/
# mkdir tmp/cache/models/
# mkdir tmp/cache/persistent/
# sudo chmod -R 777 tmp/</pre>
<p>Nach dem das Administratorpasswort eingegeben wurde, wird eine sehr freundliche Berechtigung gesetzt (die zum testen okay ist, auf dem Liveserver aber tabu!) und Cake kann in die Order schreibenn. Da der Ordner aus der Versionisierung ausgeschlossen wurde, muss man diesen Schritt zum Glück nur ein einziges mal durchführen.</p>
<h2>Die Datenbank</h2>
<p>Weil ein frisch geclontes Cake Projekt noch keine Datenbankverbindung besitzt, muss man als erstes eine neue Datenbank über phpMyAdmin o.ä. erstellen. Anschließend wechselt man in die Konsole und erstellt<strong> anhand des Schemas alle benötigten Tabellen</strong>.</p>
<pre>$ cd cake/console
$ ./cake bake</pre>
<p>Da keine Datenbankverbindung besteht, werden die Zugangsdaten abgefragt. Anschließend</p>
<pre>$ ./cake schema create</pre>
<p>Jetzt noch die zwei Fragen mit <strong>y</strong> bestätigen und schon liegen alle Tabellen ordentlich in der frisch angelegten Datenbank.</p>
<h2>Fazit</h2>
<p>Was benutzt ihr um Demoinhalte zu übertragen? Im Schema gibt es zwei Callbacks: <strong>before() </strong>und <strong>after()</strong>; ich fürchte die werden beim neugenerieren überschrieben.</p>
<p>Ansonsten bleibt nur noch frohes Entwickeln zu wünschen übrig. <strong>Frohes entwickeln!</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2010/06/05/das-perfekte-zusammenspiel-zwischen-git-und-cakephp/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Überall auf die Benutzerdaten zugreifen</title>
		<link>http://www.interaktionsdesigner.de/2010/06/03/uberall-auf-die-benutzerdaten-zugreifen/</link>
		<comments>http://www.interaktionsdesigner.de/2010/06/03/uberall-auf-die-benutzerdaten-zugreifen/#comments</comments>
		<pubDate>Thu, 03 Jun 2010 14:31:35 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=703</guid>
		<description><![CDATA[Es geht um unser Lieblingsframework: CakePHP. Man kennt ja zum Beispiel die Einstellungen, auf die man von überall aus zugreifen kann über die statische Klasse Configure: Das funktioniert vom Model, über den Controller bis zum View. Wäre es nicht wunderbar auf die gleiche Art und Weise auf den angemeldeten Benutzer zugreifen zu können? Ich denke [...]]]></description>
			<content:encoded><![CDATA[<p>Es geht um unser Lieblingsframework:<strong> CakePHP</strong>. Man kennt ja zum Beispiel die Einstellungen, auf die man von überall aus zugreifen kann über die statische Klasse <strong>Configure</strong>:</p>
<pre>echo Configure::read('Hello.World');</pre>
<p>Das funktioniert vom Model, über den Controller bis zum View. Wäre es nicht wunderbar auf die gleiche Art und Weise auf den <strong>angemeldeten Benutzer </strong>zugreifen zu können? Ich denke da an:</p>
<pre>echo User::get('name');</pre>
<p>Ja, wäre es und funktioniert ganz einfach. Die Idee stammt von <a title="CakePHP Genie" href="http://www.pseudocoder.com/" target="_blank">Matt Curry</a> und ist in seinem <a title="Free CakePHP Book" href="http://www.pseudocoder.com/free-cakephp-book" target="_blank">kostenlosen Cake Buch</a> auf englisch beschrieben. Im Laufe der Zeit hab ich die Funktionen erweitert und angepasst.<span id="more-703"></span></p>
<h2>Das User Model</h2>
<p>Hier beginnt die Arbeit. Die Grundlage ist die Funktion <strong>getInstance() </strong>mit deren Hilfe die Daten gespeichert werden und sicher gestellt wird, dass immer die richtigen Daten zurück gegeben werden:</p>
<pre>function &amp;getInstance($user = null) {
&nbsp;&nbsp;static $instance = array();
&nbsp;&nbsp;if ($user) {
&nbsp;&nbsp;&nbsp;&nbsp;$instance[0] =&amp; $user;
&nbsp;&nbsp;}
&nbsp;&nbsp;return $instance[0];
}</pre>
<p>Um neue Daten hinzuzufügen habe ich die Funktion <strong>store()</strong> erstellt. Diese kann beliebig oft aus dem Controller aufgerufen werden. Neue Daten ersetzen alte Inhalte. Mit Hilfe dieser Funktion kann man <strong>nachträglich noch einzelne Daten</strong> hinzufügen.</p>
<pre>function store($data) {
&nbsp;&nbsp;$user = User::getInstance();
&nbsp;&nbsp;$user = Set::merge($user, $data);
&nbsp;&nbsp;User::getInstance($user);
}</pre>
<p>Und natürlich die Funktion um <strong>Daten auszulesen</strong>:</p>
<pre>function get($path = '') {
&nbsp;&nbsp;$_user =&amp; User::getInstance();
&nbsp;&nbsp;if(empty($path)) {
&nbsp;&nbsp;&nbsp;&nbsp;return $_user;
&nbsp;&nbsp;}
&nbsp;&nbsp;$path = str_replace('.', '/', $path);
&nbsp;&nbsp;if (strpos($path, 'User') !== 0) {
&nbsp;&nbsp;&nbsp;&nbsp;$path = sprintf('User/%s', $path);
&nbsp;&nbsp;}
&nbsp;&nbsp;if (strpos($path, '/') !== 0) {
&nbsp;&nbsp;&nbsp;&nbsp;$path = sprintf('/%s', $path);
&nbsp;&nbsp;}
&nbsp;&nbsp;$value = Set::extract($path, $_user);
&nbsp;&nbsp;if (!$value) {
&nbsp;&nbsp;&nbsp;&nbsp;return false;
&nbsp;&nbsp;}
&nbsp;&nbsp;return $value[0];
}</pre>
<p>Wie man sieht wird mit der Funktion <a title="Set::extract() im Cake Manual" href="http://book.cakephp.org/view/1501/extract" target="_blank">Set::extract()</a> aus den gespeicherten Daten gelesen. Die Core Utilities sind einfach großartig. Unbedingt durchstöbern!</p>
<h2>Im App Controller</h2>
<p>Hier werden die Daten vom angemeldeten Benutzer gesetzt. Als erstes muss natürlich das Model importiert werden, unabhängig von einer Anmeldung. Damit alle Controller mit den Daten arbeiten können, gehört der folgende Code in die Funktion <strong>beforeFilter()</strong>.</p>
<pre>App::import('Model', 'User');
$User = new User;</pre>
<p>Als nächstes wird geprüft ob der Benutzer angemeldet ist. Wenn das der Fall ist, werden die Benutzerdaten <strong>aus der Session ins Model </strong>geschrieben:</p>
<pre>if($this-&gt;Session-&gt;check('Auth.User')) {
&nbsp;&nbsp;$User-&gt;store($this-&gt;Session-&gt;read('Auth'));
}</pre>
<p>Hat man im Laufe der Abarbeitung der Anfrage noch weitere Informationen vom Benutzer gesammelt oder errechnet, kann man die einfach hinzufügen:</p>
<pre>$User-&gt;store(array('is_cool' =&gt; true));</pre>
<h2>Überall</h2>
<p>Überall, im View, im Controller oder im Model kann jetzt auf die <strong>Benutzerdaten</strong> zugegriffen werden:</p>
<pre>echo User::get('User.name');
echo User::get('email');</pre>
<p>Ich mag diese Form sehr! Sie ist schön, kurz und einprägsam. Man kann sie auch noch vielfältig erweitern.</p>
<h2>Sehr schöne Erweiterung</h2>
<p>Kaum können sich Benutzer anmelden, sollen sie <strong>verschiedene Rechte</strong> auf einer Plattform haben. Mit den oben beschriebenen Grundlagen erreicht man schon sehr viel. Noch besser wird es, wenn man eine Funktion<strong> check()</strong> im Model hinzufügt um auf das vorhanden sein von Feldern zu prüfen.</p>
<pre>function check($path) {
&nbsp;&nbsp;$value = User::get($path);
&nbsp;&nbsp;if (!$value || empty($value) || $value === false) {
&nbsp;&nbsp;&nbsp;&nbsp;return false;
&nbsp;&nbsp;}
&nbsp;&nbsp;return true;
}</pre>
<p>Damit kann man schon gut lesbare Abfragen schreiben.</p>
<pre>if(User::check('is_cool')) { /* usw. */ }</pre>
<p>Aber so richtig sauber sieht das nicht aus. Und was passiert wenn es mehrere Rollen gibt die der Nutzer einnehmen kann? Dann wäre eine <strong>is()</strong> Funktion wesentlich schöner.</p>
<pre>if(User::is('cool')) //ist der benutzer cool?
if(User::is('cool', 'okay')) //ist der benutzer cool oder okay?</pre>
<p><strong>Das sieht nett aus!</strong> Und der Schlüssel/die Funktion ist einfach implementiert. Im <strong>User Model</strong> einfach folgendes hinzufügen:</p>
<pre>function is() {
&nbsp;&nbsp;$roles = func_get_args();
&nbsp;&nbsp;foreach($roles as $role) {
&nbsp;&nbsp;&nbsp;&nbsp;if(User::check('User.is_'.$role)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
&nbsp;&nbsp;return false;
}</pre>
<p>Die Rechte werden im Benutzerarray erwartet in der Form<strong> is_Rechtename</strong>. Im Controller also schnell hinzugefügt:</p>
<pre>$User-&gt;store(array('is_cool' =&gt; 1, 'is_okay' =&gt; 1, 'is_bloede' =&gt; 0));</pre>
<p>Die Funktion <strong>is()</strong> nimmt den übergebenen Namen, setzt ein<strong> is_</strong> davor und prüft ob die Variable in den Benutzerdaten gesetzt ist.</p>
<h2>Fazit</h2>
<p>Ich finde dieses Pattern einfach großartig und arbeite nur noch damit! Ich hab versucht das ganze in ein Plugin zu packen um nicht jedes mal aus einem alten Projekt die Funktionen rüber zu kopieren, aber es daran gescheitert, dass das Benutzer Model, wenn es statisch aufgerufen wird natürlich nicht mehr seine Behaviors (UserStorage) beinhaltet. <strong>Jemand eine Idee?</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2010/06/03/uberall-auf-die-benutzerdaten-zugreifen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Solide Grundlagen für den CakePHP View</title>
		<link>http://www.interaktionsdesigner.de/2010/05/17/solide-grundlagen-fur-den-cakephp-view/</link>
		<comments>http://www.interaktionsdesigner.de/2010/05/17/solide-grundlagen-fur-den-cakephp-view/#comments</comments>
		<pubDate>Mon, 17 May 2010 14:00:59 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=677</guid>
		<description><![CDATA[Nach dem wir in der Ape Unit GmbH zwei große Projekte mit CakePHP aufgebaut haben und ich größtenteils für die Views zuständig war, gibt es eine Reihe von Grundlagen auf die ich in einem Cake Projekt nicht mehr verzichten möchte. Welche das sind, wie sie eingebaut und verwendet werden, ist das Thema dieses Artikels. Im [...]]]></description>
			<content:encoded><![CDATA[<p>Nach dem wir in der <a title="Webentwicklung und Medienproduktion" href="http://www.apeunit.com" target="_blank">Ape Unit GmbH</a> zwei große Projekte mit <strong>CakePHP</strong> aufgebaut haben und ich größtenteils für die Views zuständig war, gibt es <strong>eine Reihe von Grundlagen</strong> auf die ich in einem Cake Projekt nicht mehr verzichten möchte.</p>
<p>Welche das sind, wie sie eingebaut und verwendet werden, ist das Thema dieses Artikels.</p>
<p><span id="more-677"></span></p>
<h2>Im App Controller</h2>
<p>Alles fängt im Controller an. Meine erste Tat ist jetzt stets einen <strong>App Controller</strong> anzulegen. Der liegt im Verzeichnis <em>/app/</em> und definiert eine Reihe von Grundlagen.</p>
<pre>class AppController extends Controller {
&nbsp;&nbsp;var $components = array('Auth', 'Session', 'RequestHandler');
&nbsp;&nbsp;var $helpers = array('Form', 'Html', 'Javascript', 'Time', 'Text', 'Layout');
}</pre>
<p>Die Components <strong>Auth</strong> und <strong>Session</strong> sind wahrscheinlich schon hinreichend bekannt. Der <strong>RequestHandler</strong> ist mein bester Freund, denn er beinhaltet alle Informationen über den aktuellen Aufruf. Mit seiner Hilfe ist es zum Beispiel möglich zu erkennen ob es sich um <strong>einen asynchronen Aufruf</strong> handelt oder um ein <strong>mobiles Endgerät</strong>.</p>
<p>Die Helper sollten auch soweit klar sein, bis auf <strong>Layout</strong>. Der ist nicht im Core enthalten sondern in der Bakery <a title="Template Inheritance in CakePHP" href="http://bakery.cakephp.org/articles/view/anything_for_layout-making-html-from-the-view-available-to-the-layout" target="_blank">verfügbar</a>. Mit diesem Helper im Projekt ist  <strong>Template Inheritance</strong> möglich. Das heißt man definiert im Layout (<em>/app/views/layouts/default.ctp</em>) einen Block, zum Beispiel den <strong>Footer</strong>.</p>
<pre>&lt;div id="footer"&gt;
&nbsp;&nbsp;&lt;? $this-&gt;Layout-&gt;output($footer_for_layout, $this-&gt;element('default_footer')); ?&gt;
&lt;/div&gt;</pre>
<p>Im Element <strong>default_footer</strong> (<em>/app/views/elements/default_footer.ctp</em>) befindet sich der Standardfooter. Und den überschreibt man im Bedarf einfach in einem View:</p>
<pre>&lt;?=$layout-&gt;blockStart('footer');?&gt;
Ein &lt;strong&gt;neuer&lt;/strong&gt; Footer!
&lt;?=$layout-&gt;blockEnd();?&gt;</pre>
<p>Grandios oder? Der im View definierte Inhalt wird dann im entsprechenden Block im Layout ausgegeben. Das Prinzip habe ich bei <strong>Django</strong> gesehen und jetzt zum Glück auch in Cake zur Verfügung.</p>
<p>Noch ein Wort zum <strong>RequestHandler</strong>. In der Funktion <strong>AppController::beforeFilter()</strong> benutze ich folgende Abfrage um auf einen asynchronen Aufruf zu reagieren:</p>
<pre>if($this-&gt;RequestHandler-&gt;isAjax()) {
&nbsp;&nbsp;Configure::write('debug', 0);
&nbsp;&nbsp;if($this-&gt;RequestHandler-&gt;prefers() == 'json') {
&nbsp;&nbsp;   die(json_encode($this-&gt;viewVars));
&nbsp;&nbsp;}
&nbsp;&nbsp;else {
&nbsp;&nbsp;   $this-&gt;layout = 'ajax';
&nbsp;&nbsp;}
}</pre>
<p>Wenn es sich um einen Ajaxaufruf handelt<strong> $this-&gt;RequestHandler-&gt;isAjax()</strong> dann wird als erstes eine Debugausgabe verhindert <strong>Configure::write('debug', 0);</strong> (das sollte man sich merken!!) und dann überprüft, was für ein Datentyp zurück erwartet wird.</p>
<p>Wenn <strong>JSON Code</strong> angefordert wird, werden alle Variablen für den View in einem JSON Objekt zurück gegeben, andernfalls wird das <strong>Ajax Layout</strong> verwendet, welches einfach den View zurück gibt ohne das ganze HTML Gerüst.</p>
<p>Wichtig ist bei der Verwendung dieser Abfrage daran zu denken <strong>keine sensiblen Daten an den View zu übergeben</strong> und dort zu prüfen ob der Nutzer die Daten sehen darf oder nicht. Aber da wir ja alle strikt dem MVC Pattern folgen, ist das ja auch gar nicht möglich.</p>
<h2>Das Layout</h2>
<p>Okay, <strong>jQuery</strong> keine Frage:</p>
<pre>echo $this-&gt;Javascript-&gt;link(array(
&nbsp;&nbsp;'jquery.min'
));</pre>
<p>Die Endung .js kann man sich sparen, Cake sucht die angegebenen Dateien automatisch im Ordner <em>/app/webroot/js/</em>.</p>
<p>Zur angenehmen Verteilung des Inhalts benutze ich jetzt immer das <a title="Ein grandioses CSS Framework" href="http://www.960.gs" target="_blank">960.gs CSS Framework</a>. Es gibt einen grandiosen <a title="960.gs Konfigurator" href="http://www.spry-soft.com/grids/" target="_blank">Konfigurator</a> mit dessen Hilfe man sich sehr schnell ein passendes Grid zusammenstellt. Die erzeugte CSS Datei legt man im Ordner <em>/app/webroot/css/</em> ab und bindet sie mit Hilfe des <strong>HTML Helpers</strong> ein.</p>
<p>Außerdem benutze ich den <a href="http://meyerweb.com/eric/tools/css/reset/" target="_blank">CSS Reset von Eric Meyer</a> um eine einheitliche Ausgangslage in allen Browsern zu erreichen. In der Datei <strong>layout.css</strong> binde ich dann je nach Komplexibilität weitere CSS Dateien über <strong>import url()</strong> ein.</p>
<pre>echo $this-&gt;Html-&gt;link(array(
&nbsp;&nbsp;'960',
&nbsp;&nbsp;'reset',
&nbsp;&nbsp;'layout'
));</pre>
<p>Um in jQuery immer die richtige Baseurl für Ajaxanfragen zur Verfügung zu haben, nutze ich das Base Tag:</p>
<pre>&lt;base href="http://&lt;?=env('HTTP_HOST').$this-&gt;base?&gt;/" /&gt;</pre>
<h2>HTML Gerüst</h2>
<p>Das<strong> 960.gs</strong> verlangt einen umfassenden Container mit der Klasse <strong>container_XX</strong>, wobei das XX für die Anzahl der Spalten steht. Was sich außerdem bewäht hat ist, <strong>Controller</strong> und <strong>Action</strong> als Klasse auszugeben um per CSS einzelne Ansichten individuell gestallten zu können. In meinem Layout führt das zur folgenden Monsterzeile:</p>
<pre>&lt;div id="wrap" class="container_16 &lt;?=$this-&gt;params['controller']?&gt; &lt;?=$this-&gt;params['action']?&gt; &lt;?=$this-&gt;params['controller']?&gt;&lt;?=ucfirst($this-&gt;params['action'])?&gt;"&gt;</pre>
<p>Alle Views des Controllers <strong>Blogs</strong> haben jetzt die Klasse <strong>#wrap.blogs</strong>. Und jede einzelne View lässt sich mit <strong>#wrap.blogsAdd</strong> gezielt ansprechen. Außerdem können auch alle Formular zum hinzufügen von neuen Einträgen angesprochen werden: <strong>#wrap.add form</strong>.</p>
<p>Damit ist man schon sehr flexibel.</p>
<p>Noch ein Satz zu <strong>960.gs</strong>: die Aufteilung des Inhalts in Spalten erfolgt über das verteilen von Klassen. Diese haben immer das Format <strong>grid_XX</strong>, darauf folgt immer ein Container mit der Klasse <strong>clear</strong>.</p>
<pre>&lt;div class="grid_8"&gt;Linke Hälfte&lt;/div&gt;
&lt;div class="grid_8"&gt;Rechte Hälfte&lt;/div&gt;
&lt;div class="clear"&gt;&lt;/div&gt;</pre>
<p>Das teilt den Inhalt in zwei gleiche Teile. Es ist über die Klassen <strong>pull_XX</strong> und <strong>push_XX</strong> auch möglich Abstände einzufügen. Einfach ein bisschen rumspielen.</p>
<h2>Hilfe und Informationen</h2>
<p>Alle <strong>Core Components</strong> und <strong>Helper</strong> liegen bestens dokumentiert in eurem Projekt! Um schnell zu erfahren wie die verdammte Funktion im <strong>RequestHandler</strong> heißt, öffnet man einfach die entsprechende Datei in <em>/cake/libs/controller/components/</em>.</p>
<p>Im Verzeichnis <em>/cake/libs/</em> ist Cake genau so aufgebaut wie im eigenen Ordner <em>/app/</em>. Daran muss man unbedingt denken und diese <strong>wertvolle Quelle </strong>nutzen. Aber: <strong>niemals Änderungen vornehmen!!!</strong> <strong>NIEMALS</strong>. Absolut verboten, denn damit verliert man die Updatefähigkeit. Alle Dateien die man anpacken darf liegen im <em>/app/ </em>Ordner. Wenn man jetzt wirklich nicht drum rum kommt und zum Beispiel im <strong>Time Helper</strong> etwas ändern muss, dann kopiert man die komplette Datei und legt sie in den Ordner <em>/app/views/helpers/</em>. Jetzt darf man sie verändern <strong>wenn es unbedingt sein muss</strong>.</p>
<p>Unter <a href="http://www.cakephp.org" target="_blank">http://www.cakephp.org</a> findet man eine ganze Reihe nützlicher Links und Ressourcen, dass ist ja klar. Für deutschsprachige Cake Entwickler bietet sich außerdem noch das <a title="CakePHP Forum für deutschsprachige Entwickler" href="http://www.cakeforum.de" target="_blank">CakeForum</a> an, da bin <a title="Icke im Cake Forum" href="http://www.cakephp-forum.com/member/paul/" target="_blank">ich</a> auch ab und zu unterwegs.</p>
<h2>Fazit</h2>
<p><strong>CakePHP ist ein grandioses Framework</strong>, die aktuelle Version 1.3 macht großen Spaß und ich kann es kaum erwarten das unsere Projekte endlich das digitale Licht des Internets erblicken.</p>
<p>Infohäppchen gibt es in meinem <a title="Paul Lunow auf Twitter" href="http://www.twitter.com/paul_lunow" target="_blank">Twitter Account</a>, richtig gute Hilfe und Unterstüztung leistet die <a title="Webentwicklung aus Berlin" href="http://www.apeunit.com" target="_blank">Ape Unit GmbH</a> gegen Bares oder Mitarbeit und wenn du einen Job suchst, dann freue ich mich auf einen Besuch auf unserer Seite der <a title="Webentwickler gesucht in Berlin" href="http://www.apeunit.com/jobs" target="_blank">offenen Stellen</a>!</p>
<p>Frohes backen!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2010/05/17/solide-grundlagen-fur-den-cakephp-view/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Ajax in Webapplikationen mit CakePHP und jQuery</title>
		<link>http://www.interaktionsdesigner.de/2009/11/21/ajax-in-webapplikationen-mit-cakephp-und-jquery/</link>
		<comments>http://www.interaktionsdesigner.de/2009/11/21/ajax-in-webapplikationen-mit-cakephp-und-jquery/#comments</comments>
		<pubDate>Sat, 21 Nov 2009 14:38:09 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=618</guid>
		<description><![CDATA[Jeder Webentwickler weiß, dass kein Benutzer mehr auf das Neuladen einer Seite warten will. Vorallem nicht, wenn es in Webapplikationen darum geht Elemente hinzuzufügen, zu bearbeiten oder zu entfernen. Zum Beispiel in einem Tool welches umfangreiche Exportmöglichkeiten besitzt und gerade von einer jungen, dynamischen Agentur entwickelt wird. In dem Programm legt man ein Exportscript an [...]]]></description>
			<content:encoded><![CDATA[<p>Jeder Webentwickler weiß, dass kein Benutzer mehr auf das Neuladen einer Seite warten will. Vorallem nicht, wenn es in Webapplikationen darum geht Elemente hinzuzufügen, zu bearbeiten oder zu entfernen.</p>
<p>Zum Beispiel in einem Tool welches umfangreiche Exportmöglichkeiten besitzt und gerade von einer <a title="Meine junge dynamische Agentur!" href="http://www.kiwi-service.de" target="_blank">jungen, dynamischen Agentur</a> entwickelt wird. In dem Programm legt man ein Exportscript an und kann diesem verschiedene Exportdateien zuweisen die beim durchlaufen dynamisch erstellt werden.</p>
<p>Diese einfache 1:n Beziehung ist mit Cake schnell gebacken, mit ein paar Zeilen erweitert und läuft. Allerdings verteilt auf mehreren Seiten. Zum Glück gibt uns Cake einfache Möglichkeiten an die Hand um das zu verhindern.</p>
<p>In diesem Artikel will ich aufschreiben wie man praktisch jedes Cake Projekt zusammen mit dem Lieblings-Javascriptframework jQuery in eine coole Web 2.0 Anwendung verwandeln kann.</p>
<p><span id="more-618"></span></p>
<h2>Grundlagen</h2>
<p>Ich gehe mal von einer laufenden Cakeanwendung aus. Da gibt es die Tabelle Export und die Tabelle Exportfiles. Die beiden sind über eine 1:n Beziehung verknüpft, die Anwendung wurde gebacken und man verfügt über die normalen CRUD Funktionen.</p>
<p><em>Wenn Interesse an einem Tutorial dazu besteht, wäre ein kurzer Hinweis in den Kommentaren oder per Mail nett.</em></p>
<h2>Der AppController</h2>
<p>Den schönsten Teil bringt Cake schon mit: den <strong>RequestHandler</strong>. Mit dessen Hilfe kann man auf Ajaxanfragen reagieren. Als erstes muss er in der Datei <strong>app/app_controller.php</strong> eingebunden werden:</p>
<pre>$components = array('RequestHandler');</pre>
<p>Anschließend stehen in jedem Controller über <strong>$this-&gt;RequestHandler</strong> umfangreiche Möglichkeiten zur Verfügung. Besonders toll wirds im Callback <strong>beforRender()</strong>. Dieser wird im AppController definiert und von Cake automatisch aufgerufen, wenn die Logik im entsprechenden Controller abgearbeitet wurde.</p>
<p>So sieht die Killerfunktion aus:</p>
<pre>function beforeRender() {
&nbsp;&nbsp;if($this-&gt;RequestHandler-&gt;isAjax()) {
&nbsp;&nbsp;   Configure::write('debug', 0);
&nbsp;&nbsp;   if($this-&gt;RequestHandler-&gt;prefers() == 'json') {
&nbsp;&nbsp;      die(json_encode($this-&gt;viewVars));
&nbsp;&nbsp;   }
&nbsp;&nbsp;   else {
&nbsp;&nbsp;      $this-&gt;layout = 'ajax';
&nbsp;&nbsp;   }
&nbsp;&nbsp;}
}</pre>
<p>Was hier passiert ist so gut wie selbsterklärend: Wenn es sich um eine asynchrone Anfrage handelt <strong>if($this-&gt;RequestHandler-&gt;isAjax())</strong>, dann wird als erstes jegliche Debugausgabe verboten. Das muss man sich unbedingt merken, sonst besteht die Gefahr auszuflippen weil <strong>debug()</strong> "aufeinmal" nichts mehr ausgibt. Auf der anderen Seite kann man aber nicht mit einem JSON String in Javascript weiterarbeiten, wenn dem ein Datenbanklog <span style="text-decoration: line-through;">am Hintern</span> an der schließenden Klammer klebt. <em>Also nicht vergessen!</em></p>
<p>Nach dem Cake weiß, das es eine Ajaxanfrage bearbeitet, wird geprüft, welche Rückantwort erwartet wird <strong>$this-&gt;RequestHandler-&gt;prefers()</strong>. Wenn diese JSON ist, werden alle <strong>viewVars</strong>, dass sind alle Variablen die über <strong>$this-&gt;set() </strong>im Controller gesetzt wurden, als JSON Objekt zurück gegeben. Damit stehen dem Javascript alle Informationen zur Verfügung die sonst im View verarbeitet werden. Toll oder?</p>
<p>Dieses <strong>die(json_encode()) </strong>finde ich noch nicht ganz so elegant wie der ganze Rest von Cake, tut aber seine Arbeit. Vielleicht hat jemand einen schöneren Ansatz?!</p>
<p>Wenn die Anfrage kein JSON erwartet, wird nicht das Standardlayout genutzt, sondern auf das Ajax umgeschaltet. Das befindet sich in der Datei <strong>app/views/layouts/ajax.ctp</strong> und besteht nur aus einer Zeile:</p>
<pre>&lt;?php echo $content_for_layout; ?&gt;</pre>
<h2>Standardlayout</h2>
<p>Um Probleme beim ändern der BaseURL zu vermeiden habe ich mir angewöhnt im Standardlayout das Tag <strong>base</strong> zu verwenden um mit jQuery schnell auf die URL zugreifen zu können. In der Datei <strong>app/views/layouts/default.ctp</strong> muss dafür folgende Zeile hinzugefügt werden:</p>
<pre>&lt;base href="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;&lt;?=$this-&gt;base?&gt;/" host="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;" /&gt;</pre>
<p>Ich weiß, das Attribut <strong>host</strong> verhindert eine komplette Validierung, allerdings brauche ich in einigen Fällen nur den Host und in anderen die komplette BaseURL, da zum Beispiel die erstellen Formulare mit <strong>$form-&gt;create(...) </strong>schon die BaseURL enthalten.</p>
<p>Das wird man gleich sehen.</p>
<h2>jQuery Action</h2>
<p>Da sind jetzt in wenigen Zeilen mächtige Grundlagen gelegt worden, die man mit jQuery an seiner Seite ausnutzen möchte. Im Formular zur Erstellung eines neuen Exports habe ich ein Button "Neue Exportdatei", die bei Klick in den Container <strong>div#new</strong> das Formular lädt um eine neue Datei zu speichern. Nichts leichter als das:</p>
<pre>$ajax({
&nbsp;&nbsp;url: $('base').attr('href')+'exportfiles/add/export:'+$('#ExportEditForm').attr('rel'),
&nbsp;&nbsp;dataType: 'html',
&nbsp;&nbsp;success: function(form) {
&nbsp;&nbsp;    $('#new').html(form);
&nbsp;&nbsp;}
};</pre>
<p>Als benannten Parameter wird die ID des aktuellen Exports übergeben. Im Controller steht diese Information im Array <strong>$this-&gt;params['named']</strong> zur Verfügung. Von da wird sie an den View übergeben und hier wird, sofern gesetzt ein verstecktes Formularfeld erzeugt, anstatt der gebackenen Liste der vorhandenen Exports.</p>
<p>Mit der Verwendung des Ajax-Layouts und der Angabe <strong>dataType: 'html'</strong> steht in der Variable <strong>form</strong> der Success Funktion genau das Formular zur Verfügung, welches im View definiert wurde.</p>
<h2>JSON benutzen</h2>
<p>Wenn das Formular ausgefüllt wurde, muss man das Abschicken abfangen. Leider unterstützt die <strong>Live</strong> Funktion noch nicht den Event <strong>submit</strong>, deshalb muss man sich mit einem Klick auf den Submitbutton helfen.</p>
<pre>$('#new .submit input').live('click', function() { ... });</pre>
<p>Das wirklick spannende ist natürlich auch hier die Ajaxanfrage und das Geheimnis liegt im <strong>dataType: 'json'</strong>.</p>
<pre>$.ajax({
&nbsp;&nbsp;url: $('base').attr('host')+$form.attr('action'),
&nbsp;&nbsp;data: $form.serialize(),
&nbsp;&nbsp;type: 'POST',
&nbsp;&nbsp;dataType: 'json',
&nbsp;&nbsp;success: function(content) {
&nbsp;&nbsp;  if(content.saved) {
&nbsp;&nbsp;      $form.closest('fieldset').prev().find('ul')
&nbsp;&nbsp;          .find('li.empty').remove().end()
&nbsp;&nbsp;          .append('&lt;li&gt;Neuer Eintrag &lt;b&gt;'+$form.find('input[type=text]:first').val()+'&lt;/b&gt; angelegt.&lt;/li&gt;');
&nbsp;&nbsp;  }
&nbsp;&nbsp;  else {
&nbsp;&nbsp;      alert("Leider ist ein Fehler beim speichern aufgetreten.");
&nbsp;&nbsp;  }
&nbsp;&nbsp;}
});</pre>
<p>In der Variable <strong>$form</strong> ist das jQuery Objekt gespeichert, welches das Formular widerspiegelt, dass gerade abgeschickt wurde. Da man immer wieder darauf zugreifen muss, macht es Sinn das in eine Variable zu legen um Zeit bei der Abarbeitung zu sparen.</p>
<p>In der Variable <strong>content</strong> stehen jetzt dank <em>AppController</em> und <em>RequestHandler</em> alle View-Variablen zur Verfügung. Über das Auslesen des Attributs <strong>Action</strong> des Formulars muss man sich nicht mal mehr Gedanken machen wo das Formular überhaupt abgearbeitet wird. Wichtig ist nur das in der entsprechenden Funktion ein Status gesetzt wird:</p>
<pre>$this-&gt;set('saved', true);</pre>
<h2>Fazit</h2>
<p>Nach genau dem gleichen Prinzip funktioniert auch das Löschen oder jeder andere Anwendungsfall der einem einfällt. Ich bin im Moment mal wieder begeistert von diesen beiden tollen Frameworks und den schier unerschöpflichen Möglichkeiten.</p>
<p>Demnächst gibt es dann hoffentlich mal die Vorstellung von einem fertigen Tool. Bis dahin freue ich mich auf Fragen und Verbesserungsvorschläge. Vielen Dank fürs lesen!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2009/11/21/ajax-in-webapplikationen-mit-cakephp-und-jquery/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>CakePHP HABTM mit automatischer Vervollständigung</title>
		<link>http://www.interaktionsdesigner.de/2009/08/10/cakephp-habtm-mit-automatischer-vervollstandigung/</link>
		<comments>http://www.interaktionsdesigner.de/2009/08/10/cakephp-habtm-mit-automatischer-vervollstandigung/#comments</comments>
		<pubDate>Mon, 10 Aug 2009 09:38:33 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=578</guid>
		<description><![CDATA[Wenn man für ein beliebiges Model Posts speichern und eingeben kann, vielleicht weil man dieser Anleitung gefolgt ist, dann ist das schon ein schöner Fortschritt. Allerdings passieren immer wieder Tippfehler, und ähnlich klingende Begriffe finden sich in der Tagcloud. Da hilft nur eins: während der Eingabe dem Benutzer direkt passende Tags vorschlagen. Eine automatische Vervollständigung [...]]]></description>
			<content:encoded><![CDATA[<p>Wenn man für ein beliebiges Model Posts speichern und eingeben kann, vielleicht weil man <a title="Pauls Blog erklärt HABTM" href="http://www.interaktionsdesigner.de/2009/04/17/mit-cakephp-in-30-minuten-tags-zu-einer-tabelle-hinzufugen/#comment-375" target="_blank">dieser Anleitung</a> gefolgt ist, dann ist das schon ein schöner Fortschritt. Allerdings passieren immer wieder Tippfehler, und ähnlich klingende Begriffe finden sich in der Tagcloud.</p>
<p>Da hilft nur eins: während der Eingabe dem Benutzer <strong>direkt passende Tags vorschlagen</strong>. Eine automatische Vervollständigung wie beim Mailen in den  großen sozialen Netzwerken oder beim suchen bei Google.</p>
<p>Zum Glück haben sehr schlaue Leute schon diverse Plugins erstellt, welche einen JSON String oder eine simple Textliste verarbeiten können. Zum Beispiel <a title="jQuery Autobox2" href="http://www.bigredswitch.com/blog/2008/12/autobox2/" target="_blank">Autobox2</a>. Gleich mal die <a title="jQuery Autobox2 Demo" href="http://www.bigredswitch.com/blog/wp-content/uploads/2008/12/autobox.html" target="_blank">Demo</a> angucken, runterladen und dann ins eigene Projekt integrieren.</p>
<p><strong>So funktionierts...</strong><span id="more-578"></span></p>
<h2>Vorbereitung</h2>
<p>Bestimmt ist <strong>jQuery</strong> schon im gesamten Projekt verfügbar, wenn nicht, muss es unter <em>views/layouts/default.ctp</em> mit Hilfe des <a title="Der Javascript Helper von CakePHP" href="http://book.cakephp.org/view/207/Javascript" target="_blank">Javascript Helpers</a> eingebunden werden:</p>
<pre>echo $javascript-&gt;link('jquery.min');
echo $javascript-&gt;link('jquery.action');
echo $scripts_for_layout;</pre>
<p>Der Javascript Helper sucht die Dateien <strong>automatisch</strong> im Ordner <em>webroot/js/</em> und bindet sie ein, sofern sie vorhanden sind. In der Datei <em>jquery.action.js</em> befindet sich mein eigenes Script in dem das Verhalten der Seite gesteuert wird.</p>
<p>Sehr wichtig für den nächsten Schritt ist die Ausgabe der Variable <em>$scripts_for_layout</em>. Diese kann von jedem View aus befüllt werden. Das brauchen wir im View der<strong> Edit/Add Methode</strong> vom Model:</p>
<pre>$javascript-&gt;link(array(
&nbsp;&nbsp;'jquery.templating',
&nbsp;&nbsp;'jquery.ui.autobox.ext.js',
&nbsp;&nbsp;'jquery.ui.autobox.js'
), false);</pre>
<p>Damit werden die Javascript Dateien <strong>nicht überall</strong> eingebunden, sondern nur auf den Seiten auf denen sie auch benötigt werden.</p>
<p>Nach dem gleichen Prinzip, aber mit dem <a title="HTML Helper von CakePHP" href="http://book.cakephp.org/view/205/HTML" target="_blank">HTML Helper</a> wird die Autobox CSS Datei eingebunden.</p>
<pre>$html-&gt;css('jquery.ui.autobox.css', null, array(), false);</pre>
<p>Und da wir gerade dabei sind, bietet es sich an diese Datei zu öffnen und in der <strong>Zeile 66</strong> den Pfad zum <em>close.gif</em> anzugeben. Wenn sich alles in den korrekten Ordnern befindet ist es einfach:</p>
<pre>background: url('../img/close.gif');</pre>
<p>Aussehen wird es gut, aber im Moment weiß das Script nicht wo es sich befindet. Damit die <strong>Baseurl</strong> auch in Javascript zur Verfügung steht, habe ich mir angewöhnt im HTML Gerüst das Basetag zu erweitern:</p>
<pre>&lt;base href="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;&lt;?=$this-&gt;base?&gt;/" host="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;" /&gt;</pre>
<p>Mit dieser Zeile im Layout wird später vieles einfacher. Leider validiert es mit dem Attribut host nicht mehr, wer darauf Wert legt, muss sich also eine andere Lösung überlegen.</p>
<h2>jQuery Action</h2>
<p>Jetzt gehts los und das Plugin wird initalisiert. Ich mache das gerne in der Datei <em>jquery.action.js</em>. Um es auf die schnelle zu testen, kopiere ich einfach das Beispiel aus der Demo. Gemäß des letzten Posts zu HABTM heißt mein Feld <strong>temp_tags</strong>. Das resultiert in der ID <strong>ModelTempTags</strong>:</p>
<pre>jQuery(function($) {
&nbsp;&nbsp;
&nbsp;&nbsp;var list1 = [{text: 'Curious George'}, {text: 'George of the Jungle'}, {text: 'Felix the Cat'}];
&nbsp;&nbsp;
&nbsp;&nbsp;$('#ModelTempTags').autobox({
&nbsp;&nbsp;&nbsp;&nbsp;list: list1,
&nbsp;&nbsp;&nbsp;&nbsp;match: function(typed) { return this.text.match(new RegExp(typed, "i")); },
&nbsp;&nbsp;&nbsp;&nbsp;insertText: function(obj) { return obj.text },
&nbsp;&nbsp;&nbsp;&nbsp;templateText: "&lt;li&gt;Hey: &lt;%= text %&gt;&lt;/li&gt;"
&nbsp;&nbsp;});
});</pre>
<p>Seite neuladen, und... herrlich! <strong>Funktioniert</strong>. Nur eine Kleinigkeit: Die vorhandenen Tags werden nicht übernommen. Zum Glück hilft eine einzige Zeile um das Problem zubeheben. Innerhalb der Autobox Konfiguration wird die Vorbelegung (<strong>prevals</strong>) angegeben:</p>
<pre>prevals: $('#LinkTempTags').val().split(', ')</pre>
<p>Mit dieser Zeile wird der Inhalt ausgelesen, beim Komma geteilt und der Autobox als Vorbelegung übergeben. Dann kanns jetzt losgehen mit der <strong>asynchronen Datenübertragung</strong>. Das Attribut <strong>list</strong> in der Autobox Konfiguration wird ausgetauscht mit einer URL:</p>
<pre>ajax: $('base').attr('href')+'tags/index',</pre>
<p>Damit wird im Parameter <strong>val</strong> die Benutzereingabe an den <em>Tags Controller</em> geschickt. Und der muss jetzt noch reagieren.</p>
<h2>Antworten mit CakePHP</h2>
<p>Der <strong>Controller Tags</strong> ist dafür zuständig eine Liste zu genierieren. In der Variable <strong>$this-&gt;params['url']['val']</strong> steht die Benutzereingabe zur Verfügung. Mit der wird eine Datenbankabfrage gestartet:</p>
<pre>$tags = $this-&gt;Tag-&gt;find('all', array('conditions' =&gt; array('title LIKE' =&gt; $this-&gt;params['url']['val'].'%'), 'order' =&gt; 'Tag.title ASC'));</pre>
<p>Mit Hilfe der <strong>RequestHandler Componente</strong> wird ein Ajaxaufruf erkannt. Dafür muss die Componente im <strong>Controller</strong> eingebunden werden.</p>
<pre>class TagsController extends AppController {
&nbsp;&nbsp;var $components = array('RequestHandler');</pre>
<p>In der<strong> Index Funktion</strong> wird bei einem Ajaxaufruf der Renderingprozess gestoppt und purer Text zurück gegeben.</p>
<pre>if($this-&gt;RequestHandler-&gt;isAjax()) {
&nbsp;&nbsp;Configure::write('debug', 0);
&nbsp;&nbsp;$this-&gt;autoRender = false;
&nbsp;&nbsp;return '['.implode(',', Set::format($tags, '{text: "{0}"}', array('{n}.Tag.title'))).']';
}</pre>
<p>Besondere Aufmerksamkeit verdient die letzte Zeile. <em>Implode</em> bekommt ein <em>Array</em>, das aus dem Core Utility <strong>Set</strong> stammt: <a title="CakePHP Set::format()" href="http://book.cakephp.org/view/672/format" target="_blank">Format</a>. Eine großartige Funktion die ein <strong>verschachteltes Array umformatiert</strong>. In diesem Fall zu JSON Objekten, welche dann mit einem Komma verbunden werden.</p>
<p>Das wars! Kaum zu glauben, aber ein Neuladen macht die Einbindung komplett. Und den Webentwickler fröhlich!</p>
<h2>Speichern im Model</h2>
<p>Das Plugin erstellt für jeden Tag ein verstecktes Formularfeld, welches den gleichen Namen hat wie das Ursprungsfeld. Damit die eingegebenen Daten übertragen werden, muss der Name auf ein Array hinweise:</p>
<pre>echo $form-&gt;input('temp_tags', array('name' =&gt; 'data[Model][temp_tags][]');</pre>
<p>Der Trick sind die <strong>beiden eckigen Klammern am Ende</strong>. Damit steht die Eingabe im Model als Array zur Verfügung:</p>
<pre>debug($this-&gt;data['temp_tags']);</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2009/08/10/cakephp-habtm-mit-automatischer-vervollstandigung/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IMAP Postfächer mit CakePHP abfragen</title>
		<link>http://www.interaktionsdesigner.de/2009/05/11/imap-postfacher-mit-cakephp-abfragen/</link>
		<comments>http://www.interaktionsdesigner.de/2009/05/11/imap-postfacher-mit-cakephp-abfragen/#comments</comments>
		<pubDate>Mon, 11 May 2009 09:25:32 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=463</guid>
		<description><![CDATA[Für ein aktuelles Projekt musste die Anwendung per IMAP auf ein Postfach zugreifen und die Mails auslesen. Das einzige Plugin welches ich gefunden habe war für CodeIgniter, benutzte aber glücklicherweise wenig Corefunktionen was es ermöglicht hat es für CakePHP zu portieren. Es ist jetzt also sehr leicht möglich die Mails aus einem Postfach auszulesen. Im [...]]]></description>
			<content:encoded><![CDATA[<p>Für ein aktuelles Projekt musste die Anwendung pe<strong>r IMAP auf ein Postfach</strong> zugreifen und die <strong>Mails auslesen</strong>. Das einzige Plugin welches ich gefunden habe war für CodeIgniter, benutzte aber glücklicherweise wenig Corefunktionen was es ermöglicht hat es für <strong>CakePHP</strong> zu portieren.</p>
<p>Es ist jetzt also sehr leicht möglich die Mails aus einem Postfach auszulesen. Im folgenden Beitrag wird gezeigt wie. <span id="more-463"></span></p>
<h2>Grundlagen</h2>
<p>Das installierte <a title="IMAP und PHP" href="http://www.php.net/manual/en/imap.setup.php" target="_blank">PHP muss IMAP unterstützten</a>. Das lässt sich herausfinden in dem die Funktion<em> imap_start() </em>aufgerufen wird. Wenn die Funktion exsistiert ist alles okay.</p>
<p>Die <a title="IMAP Component für PHP" href="http://www.interaktionsdesigner.de/stuff/imap-component.php" target="_blank">IMAP Componente</a> muss in dem Ordner<em> app/controllers/components/</em> mit dem Dateinamen <em>Imap.php </em>gespeichert werden.</p>
<h2>Benutzen</h2>
<p>In einem beliebigen <strong>Controller</strong> wird die Componente wie gewohnt geladen.</p>
<pre>$components = array('Imap');</pre>
<p>In einem <strong>Shellprogramm</strong> (abgelegt in <em>app/vendors/shells/</em>) muss die Componente über Import reingeholt werden:</p>
<pre>App::import('Component', 'Imap');
$this-&gt;Imap = new ImapComponent();</pre>
<h2>Verbinden</h2>
<p>Die Componente erwartet ein Array mit den Zugangsdaten zum Postfach.</p>
<pre>private $imap_config = array(
&nbsp;&nbsp;'imap_user' =&gt; 'test@kiwi-service.de',
&nbsp;&nbsp;'imap_pass' =&gt; 'total-geheimes-supersicheres-passwort',
&nbsp;&nbsp;'imap_flags' =&gt; '',
&nbsp;&nbsp;'imap_mailbox' =&gt; 'test@kiwi-service.de',
&nbsp;&nbsp;'imap_server' =&gt; 'kiwi-service.de',
&nbsp;&nbsp;'imap_port' =&gt; '143'
);</pre>
<p>Die einzelnen Keys sind ja selbsterklärend. Nur die Componente muss sie noch wissen. Das geschieht per Übergabe eines Arrays:</p>
<pre>$this-&gt;Imap-&gt;items($this-&gt;imap_config);</pre>
<p>Oder einzelnd mit</p>
<pre>$this-&gt;Imap-&gt;item('imap_user', 'test@kiwi-service.de');</pre>
<p>Wenn alle Daten gesetzt sind, wird zum Postfach verbunden.</p>
<pre>$this-&gt;Imap-&gt;connect();</pre>
<p>Der Rückgabewert dieser Funktion ist negativ wenn es nicht funktioniert hat.</p>
<h2>Emails bekommen</h2>
<p>Die Anzahl der neuen Nachrichten liefert die Funktion <strong>msg_count()</strong>.</p>
<p>Eine Liste aller Nachrichten liefert die Funktion <strong>msg_list()</strong>. Das Ergebnis ist ein <strong>mehrdimensionales Array</strong> und kann in einer Schleife durchgegangen werden.</p>
<pre>$messages = $this-&gt;Imap-&gt;msg_list();
foreach($messages as $message) {
&nbsp;&nbsp;debug('Titel: '.$message['title']);
}</pre>
<p>Eine gute Hilfe gegen verrückte Sonderzeichen und Kodierungen ist die Kombination von <em>utf8_encode</em> und <em>quoted_printable_decode</em>. Damit werden die meisten Umlaute richtig dargestellt.</p>
<p>Für den Text der Mail wäre das dann innerhalb der foreach-Schleife</p>
<pre>debug(utf8_encode(quoted_printable_decode($message['body'])));</pre>
<h2>Ende</h2>
<p>Zu guter letzt sollte dann auch noch die Verbindung wieder getrennt werden.</p>
<pre>$this-&gt;Imap-&gt;close();</pre>
<p>Auch hier ein negativer Rückgabewert wenn es nicht funktioniert hat.</p>
<p>Die Klasse bietet noch einige Funktionen, wer auf der suche ist sollte da mal einen Blick rein werfen.</p>
<p>Ansonsten, frohes Backen!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2009/05/11/imap-postfacher-mit-cakephp-abfragen/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Mit CakePHP in 30 Minuten Tags zu einer Tabelle hinzufügen</title>
		<link>http://www.interaktionsdesigner.de/2009/04/17/mit-cakephp-in-30-minuten-tags-zu-einer-tabelle-hinzufugen/</link>
		<comments>http://www.interaktionsdesigner.de/2009/04/17/mit-cakephp-in-30-minuten-tags-zu-einer-tabelle-hinzufugen/#comments</comments>
		<pubDate>Fri, 17 Apr 2009 09:49:05 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=451</guid>
		<description><![CDATA[Im Web 2.0 geht nichts mehr ohne Tags. Und ohne ein Rapid Development Framework kann das ganz schön haarig werden. Zum Glück springt CakePHP mit leuchtenden Augen (oder waren es die Augen des Entwicklers?) in die Bresche und begeistert mit sehr einfachen Umsetzung. Mit der folgenden Anleitung bekommt ein beliebiges Model die Möglichkeit Tags zu [...]]]></description>
			<content:encoded><![CDATA[<p>Im Web 2.0 geht nichts mehr ohne <strong>Tags</strong>. Und ohne ein <a title="CakePHP" href="http://cakephp.org/" target="_blank">Rapid Development Framework</a> kann das ganz schön haarig werden. Zum Glück springt <strong>CakePHP</strong> mit leuchtenden Augen (oder waren es die Augen des Entwicklers?) in die Bresche und begeistert mit sehr einfachen Umsetzung.</p>
<p>Mit der folgenden Anleitung bekommt ein <strong>beliebiges Model</strong> die Möglichkeit Tags zu speichern, inklusive <strong>Has and belongs to many Beziehung</strong>.</p>
<p><span id="more-451"></span></p>
<h2>Anforderungen</h2>
<p>Alle Einträge der Datenbank <strong>Posts</strong> (z.B.) sollen mit einer beliebigen Anzahl <strong>Tags</strong> beschrieben werden können. Die Tags werden in einer eigenen Datenbank gespeichert. Die Relationen zu den Einträgen wird in einer weiteren Datenbank gespeichert.</p>
<p>Zur komfortablen Eingabe sollen die <strong>Tags mit Komma getrennt</strong> eingegeben und bearbeitet werden. In der Ausgabe sollen alle Tags als Array zur Verfügung stehen.</p>
<h2>Die Datenbanken</h2>
<p>Eine Datenbank ist bereits vorhanden welche alle Posts speichert.</p>
<p>Die Tags bekommen eine eigene Datenbank mit den Spalten <strong>id</strong> (<em>primary, auto_increment</em>) und <strong>name</strong>.</p>
<p>Um CakePHP die Magie zu ermöglichen muss die dritte Tabelle, zum speichern der Relationen, <strong>tags_posts</strong> heißen. Sie beinhaltet die Spalten <strong>tag_id</strong> und <strong>post_id</strong>.</p>
<h2>Das Model</h2>
<p>Das Model der Tags ist denkbar einfach:</p>
<pre>&lt;?
&nbsp;&nbsp;class Tag extends AppModel {
&nbsp;&nbsp;&nbsp;&nbsp;var $name = 'Tag';
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;$hasAndBelongsToMany = array('Post');
&nbsp;&nbsp;}
?&gt;</pre>
<p>Die Schönheit ist in der letzten Zeile: <strong>$hasAndBelongsToMany = array('Post');</strong> hier wird dem Model erklärt das es zum Model Post gehört und dementsprechend wird die Datenbank <strong>tags_posts</strong> erwartet.</p>
<p>Das Model <strong>Post</strong> kriegt die gleiche Informationen, in umgekehrte Richtung:</p>
<pre>$hasAndBelongsToMany = array('Tag');</pre>
<h2>Speichern von Tags</h2>
<p>Jetzt wird es spannend! Die Tags sollen kommaspeariert eingegeben werden und beim speichern automatisch auseinander geschnitten werden. Dazu wird im <strong>View</strong> der Funktionen <em>add</em> und <em>edit</em> der Posts ein neues Element angelegt. Ich nenne es <strong>temp_tags</strong>.</p>
<pre>echo $form-&gt;input('temp_tags', array(
&nbsp;&nbsp;'label' =&gt; 'Tags (mit Komma trennen)',
));</pre>
<p>Die Verarbeitung des Feldes erfolgt im <strong>Model von Post</strong> und zwar in der Funktion <strong>beforeSave()</strong>. Diese wird ausgeführt bevor ein Datensatz gespeichert wird.</p>
<pre>function beforeSave() {
&nbsp;&nbsp;// hier gehts ab!
}</pre>
<p>Als erstes wird überprüft ob das Feld <strong>temp_tags</strong> überhaupt existiert. Ist dies der Fall wird in der Variable <em>$tags</em> ein Array gespeichert, aufgeteilt nach Komma. Anschließend wird die Variable<strong> temp_tags</strong> aus dem Datensatz gelöscht:</p>
<pre>if(isset($this-&gt;data[$this-&gt;name]['temp_tags'])) {
&nbsp;&nbsp;$tags = explode(",", $this-&gt;data[$this-&gt;name]['temp_tags']);
&nbsp;&nbsp;unset($this-&gt;data[$this-&gt;name]['temp_tags']);</pre>
<p>Ich habe auf die Daten über <em>$this-&gt;data[$this-&gt;name]</em> zugegriffen. Das ermöglicht die Kopie der gesamten Funktion in das nächste Projekt ohne sich noch einmal Gedanken über die Namen machen zu müssen.</p>
<p>Das Array <em>$tags</em> wird jetzt Element für Element untersucht. Dabei sollen keine Leerzeichen stören (<a title="Praktische Funktion" href="http://www.php.net/trim" target="_blank">trim</a>).</p>
<pre>foreach($tags as $tag) {
&nbsp;&nbsp;$tag = trim($tag);
}</pre>
<p>Der erste Schritt besteht darin herauszufinden ob der Tag schon vorhanden ist und wenn ja, für die neue Relation die ID herauszubekommen.</p>
<pre>$id = $this-&gt;Tag-&gt;findByName($tag);</pre>
<p>Wenn das nicht geklappt hat wird das Model Tag darauf vorbereitet einen neuen Eintrag zu speichern (<em>create</em>) und muss es dann auch sofort tun.</p>
<pre>if(!$id['Tag']['id']) {
&nbsp;&nbsp;$this-&gt;Tag-&gt;create();
&nbsp;&nbsp;$this-&gt;Tag-&gt;save(array('name' =&gt; $tag));
&nbsp;&nbsp;$id['Tag']['id'] = $this-&gt;Tag-&gt;id;
}</pre>
<p>In Normalfall steht jetzt aufjedenfall in der Variable <em>$id['Tag']['id']</em> die ID des Tags. Entweder weil es ausgelesen wurde, oder weil ein neuer Eintrag erzeugt wurde.</p>
<p>Das wird jetzt dem Model mitgeteilt</p>
<pre>$this-&gt;data['Tag']['Tag'][] = $id['Tag']['id'];</pre>
<p>Fehlt nur noch die ggf. definierte Elternmethode und eine positive Rückmeldung.</p>
<pre>parent::beforeSave();
return true;</pre>
<h2>Fertig!</h2>
<pre>}</pre>
<p>Das Auslesen im View geschieht von alleine durch die habtm Beziehung. Eine reine Freude dieser PHP Kuchen. Nicht vergessen die Daten zu überprüfen und zu sichern, wenn dieser Schnippsel auf einer öffentlichen Seite zum Einsatz kommt!</p>
<p>Hier noch mal der gesamte Quelltext der Funktion <strong>beforeSave()</strong>:</p>
<pre style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; ">  <span style="color:#407ba3;">function</span> <span style="color:#000080;">beforeSave</span>() {
    <span style="color:#ff8000;">//Kommaseparierte Liste von Tags als Tags speichern
</span>    <span style="color:#407ba3;">if</span>(<span style="color:#000080;">isset</span>(<span style="color:#0080ff;">$this</span>-&gt;data[<span style="color:#0080ff;">$this</span>-&gt;name][<span style="color:#df0100;">'temp_tags'</span>])) {
     <span style="color:#0080ff;">$tags</span> = <span style="color:#000080;">explode</span>(<span style="color:#ff0000;">","</span>, <span style="color:#0080ff;">$this</span>-&gt;data[<span style="color:#0080ff;">$this</span>-&gt;name][<span style="color:#df0100;">'temp_tags'</span>]);
     <span style="color:#000080;">unset</span>(<span style="color:#0080ff;">$this</span>-&gt;data[<span style="color:#0080ff;">$this</span>-&gt;name][<span style="color:#df0100;">'temp_tags'</span>]);

     <span style="color:#407ba3;">foreach</span>(<span style="color:#0080ff;">$tags</span> <span style="color:#407ba3;">as</span> <span style="color:#0080ff;">$tag</span>) {
      <span style="color:#0080ff;">$tag</span> = <span style="color:#000080;">trim</span>(<span style="color:#0080ff;">$tag</span>);

      <span style="color:#ff8000;">//ID vorhanden oder nicht?
</span>      <span style="color:#0080ff;">$id</span> = <span style="color:#0080ff;">$this</span>-&gt;Tag-&gt;<span style="color:#000080;">findByName</span>(<span style="color:#0080ff;">$tag</span>);
      <span style="color:#407ba3;">if</span>(!<span style="color:#0080ff;">$id</span>[<span style="color:#df0100;">'Tag'</span>][<span style="color:#df0100;">'id'</span>]) {
        <span style="color:#ff8000;">//Tag neu anlegen
</span>        <span style="color:#0080ff;">$this</span>-&gt;Tag-&gt;<span style="color:#000080;">create</span>();
        <span style="color:#0080ff;">$this</span>-&gt;Tag-&gt;<span style="color:#000080;">save</span>(<span style="color:#407ba3;">array</span>(<span style="color:#df0100;">'name'</span> =&gt; <span style="color:#0080ff;">$tag</span>));
        <span style="color:#0080ff;">$id</span>[<span style="color:#df0100;">'Tag'</span>][<span style="color:#df0100;">'id'</span>] = <span style="color:#0080ff;">$this</span>-&gt;Tag-&gt;id;
      }

      <span style="color:#0080ff;">$this</span>-&gt;data[<span style="color:#df0100;">'Tag'</span>][<span style="color:#df0100;">'Tag'</span>][] = <span style="color:#0080ff;">$id</span>[<span style="color:#df0100;">'Tag'</span>][<span style="color:#df0100;">'id'</span>];
     }
    }

    <span style="color:#407ba3;">parent</span>::<span style="color:#000080;">beforeSave</span>();
    <span style="color:#407ba3;">return</span> <span style="color:#407ba3;">true</span>;
  }</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2009/04/17/mit-cakephp-in-30-minuten-tags-zu-einer-tabelle-hinzufugen/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Mit jQuery und CakePHP voneinander abhängige Selectboxen</title>
		<link>http://www.interaktionsdesigner.de/2009/04/11/mit-jquery-und-cakephp-voneinander-abhangige-selectboxen/</link>
		<comments>http://www.interaktionsdesigner.de/2009/04/11/mit-jquery-und-cakephp-voneinander-abhangige-selectboxen/#comments</comments>
		<pubDate>Sat, 11 Apr 2009 09:30:17 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=417</guid>
		<description><![CDATA[CakePHP ist genial, nicht nur was die Erstellung von Formularen angeht. Aber leider setzt der integrierte AjaxHelper Prototype voraus und als überzeugter jQuery-Nutzer kommt das für mich nicht in Frage. Nun gibt es aber beim aktuellen Projekt folgendes Problem: Einem Projekt wird ein Kunde zugeordnet und jedes Projekt kriegt einen Ansprechpartner. Dieser Ansprechpartner ist in [...]]]></description>
			<content:encoded><![CDATA[<p><strong>CakePHP ist genial</strong>, nicht nur was die Erstellung von Formularen angeht. Aber leider setzt der integrierte AjaxHelper Prototype voraus und als <strong>überzeugter jQuery-Nutzer</strong> kommt das für mich nicht in Frage.</p>
<p>Nun gibt es aber beim aktuellen Projekt folgendes Problem: Einem Projekt wird ein Kunde zugeordnet und jedes Projekt kriegt einen Ansprechpartner. Dieser Ansprechpartner ist in der User-Datenbank gespeichert und besitzt eine ID zu einem Kunden.</p>
<p>Legt man ein neues Projekt an und wählt einen Kunden aus, sollen nur noch die passenden Ansprechpartner angezeigt werden.</p>
<p><strong>Nichts leichter als das!</strong> Hier stelle ich meine sehr flexible Möglichkeit für die Umsetzung mit <strong>jQuery</strong> und <strong>CakePHP</strong> vor. <em>Grundwissen allerdings vorrausgesetzt.</em><span id="more-417"></span></p>
<h2>Vorbereitungen</h2>
<p><em>(Alle CakePHP Pfadangaben beziehen sich auf den </em><em>app/ Ordner)</em><br />
<strong>jQuery muss eingebunden werden</strong>. Dafür will ich den <strong>Ajaxhelper</strong> benutzen. Da das gesamte Projekt mit jQuery arbeiten soll, habe ich es für alle Controller auf einen Schlag eingebunden, und zwar in der Datei <em>app_controller.php</em>.</p>
<pre>class AppController extends Controller {
&nbsp;&nbsp;var $helpers = array('Ajax');
}
</pre>
<p>Die Javascriptdateien kommen in den Ordner <em>/webroot/js/</em> und sind anschließend im<strong> Default Layout</strong> (oder welchem auch immer) in der Datei <em>views/layouts/default.ctp</em> schnell eingebunden:</p>
<pre>echo $javascript-&gt;link(
&nbsp;&nbsp;array(
&nbsp;&nbsp;&nbsp;&nbsp;'jquery.min.js',
&nbsp;&nbsp;&nbsp;&nbsp;'jquery.action.js',
&nbsp;&nbsp;)
);</pre>
<p>Damit der absolute Pfad der Anwendung in der JavaScript Datei zur Verfügung steht, wird im Layout das Tag <strong>&lt;base&gt;</strong> gesetzt. Das wird dann ausgelesen:</p>
<pre>&lt;base href="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;&lt;?=$this-&gt;base?&gt;/" /&gt;</pre>
<h2>Das Formular</h2>
<p>Um für das neue Anlegen und das Bearbeiten von Projekten nicht zwei Formulare pflegen zu müssen, habe ich das Formular in ein <strong>Element</strong> ausgelagert, es befindet sich jetzt in der Datei <em>views/elements/projectform.ctp</em> und wird im View <em>add.ctp</em> und <em>edit.ctp</em> mit dem Befehl</p>
<pre>echo $this-&gt;element("projectform");</pre>
<p>eingebunden. Die Datei selbst besteht, gekürzt, aus folgenden Zeilen:</p>
<pre>$c = array('') + $customers;
echo $form-&gt;input('customer_id', array(
&nbsp;&nbsp;'class' =&gt; 'relation',
&nbsp;&nbsp;'rel' =&gt; 'ProjectContact',
&nbsp;&nbsp;'data' =&gt; 'users|customer_id',
&nbsp;&nbsp;'options' =&gt; $c
));
echo $form-&gt;input('contact', array(
&nbsp;&nbsp;'options' =&gt; $contacts
));</pre>
<p>In der ersten Zeile wird das Array <strong>$customers</strong>, welches im Controller gesetzt wurde, um ein leeres Element am Anfang erweitert. Als nächstes folgen ein paar Erweiterungen für das zu erzeugende HTML Element auf die mit jQuery dynamisch zugegriffen wird.</p>
<p>'class' =&gt; '<strong>relation</strong>' ist der grundlegende Hinweis für jQuery das hier was passieren soll.<br />
'rel' =&gt; '<strong>ProjectContact</strong>' bezeichnet die ID des Selectors welches die dynamischen Inhalte darstellt.<br />
'data' =&gt; '<strong>users|customer_id</strong>' enthält zwei Teile: <strong>users</strong> steht für das Model aus dem die Daten gezogen werden, <strong>customer_id</strong> ist die Spalte die mit dem Wert dieser Selectbox verglichen wird.<br />
'options' =&gt; <strong>$c</strong> weißt dann nur noch das neue Array dem Element hinzu.</p>
<h2>jQuery Action</h2>
<p>Weiter gehts in der Datei <em>webroot/js/jquery.action.js</em>. Als erstes muss die Baseurl ausgelesen werden.</p>
<pre>if($("base").length == 1)
&nbsp;&nbsp;BASEURL = $("base").attr("href");
else {
&nbsp;&nbsp;alert("Keine BaseUrl gefunden!!!");
}</pre>
<p>Als nächstes kommt die <strong>geballte Action für die Selectbox</strong>. Erstmal der ganze fette Block <strong>jQuery-Schönheit</strong> vorweg, anschließend Zeile für Zeile erklärt. Für weitere dynamische Selectboxen muss man diesen Code dann nicht mehr anpassen:</p>
<pre style="text-align:left;font-family:monospace; ">$(<span style="color:#fe2d00;">"select.relation"</span>).<span style="color:#4080ff;">each</span>(<span style="color:#3f7bc6;">function</span>() {
  <span style="color:#e27600;">//Vorhandene Elemente entfernen
</span>  target = <span style="color:#fe2d00;">"#"</span>+$(<span style="color:#3f7bc6;">this</span>).<span style="color:#4080ff;">attr</span>(<span style="color:#fe2d00;">"rel"</span>);
  $(target).<span style="color:#4080ff;">find</span>(<span style="color:#fe2d00;">"option"</span>).<span style="color:#4080ff;">remove</span>();

  <span style="color:#e27600;">//Eventhandler binden
</span>  $(<span style="color:#3f7bc6;">this</span>).<span style="color:#4080ff;">change</span>(<span style="color:#3f7bc6;">function</span>() {
    $(target).<span style="color:#4080ff;">find</span>(<span style="color:#fe2d00;">"option"</span>).<span style="color:#4080ff;">remove</span>();

    <span style="color:#e27600;">//Ajaxanfrage abschicken
</span>    data = $(<span style="color:#3f7bc6;">this</span>).<span style="color:#4080ff;">attr</span>(<span style="color:#fe2d00;">"data"</span>).<span style="color:#4080ff;">split</span>(<span style="color:#fe2d00;">"|"</span>);
    val = $(<span style="color:#3f7bc6;">this</span>).<span style="color:#4080ff;">val</span>();
    <span style="color:#3f7bc6;">if</span>(val &gt; <span style="color:#fe2d00;">0</span>) {
      $.<span style="color:#4080ff;">ajax</span>({
        url: BASEURL+data[<span style="color:#fe2d00;">0</span>]+<span style="color:#df2800;">'/get/'</span>,
        data: {field: data[<span style="color:#fe2d00;">1</span>], value: val},
        dataType: <span style="color:#df2800;">'json'</span>,

        success: <span style="color:#3f7bc6;">function</span>(j) {
          $.<span style="color:#4080ff;">each</span>(j, <span style="color:#3f7bc6;">function</span>(i) {
            $(target).<span style="color:#4080ff;">append</span>(<span style="color:#df2800;">'&lt;option value="'</span>+i+<span style="color:#df2800;">'"&gt;'</span>+<span style="color:#3f7bc6;">this</span>+<span style="color:#df2800;">'&lt;/option&gt;'</span>);
          });
        },
        error: <span style="color:#3f7bc6;">function</span>(e) {
          <span style="color:#4080ff;">alert</span>(<span style="color:#fe2d00;">"Fehler bei der Ajaxanfrage! Siehe Konsole."</span>);
          console.<span style="color:#4080ff;">log</span>(e);
        }
      });
    }
  })
});</pre>
<p><strong>$("select.relation").each(function() {</strong> Jede Selectbox mit der Klasse <strong>relation</strong> dient als Auslöser und braucht spezielle Behandlung.<br />
In der Variable <strong>target</strong> wird die ID der zu ändernde Selectbox, für die spätere Verwendung gespeichert <strong>"#"+$(this).attr("rel");<br />
$(target).find("option").remove();</strong> entfernt die vorhandenen Einträge.</p>
<p>Jetzt wird der Selectbox ein Eventhandler zugewiesen, der ausgeführt wird, sobald sich der Inhalt der Selectbox ändert: <strong>$(this).change(function() {</strong>.<br />
Falls es die zweite Änderung ist, müssen vorhandene Inhalte entfernt werden <strong>$(target).find("option").remove();</strong> und per Ajax neue Inhalte geladen. Die Variable <strong>val</strong> speichert die aktuelle Auswahl und <strong>data</strong> beinhaltet die Informationen von weiter oben. Wenn eine gültige ID ausgewählt wurde, dann wird die Ajaxanfrage gesendet.</p>
<p><strong>url: BASEURL+data[0]+'/get/'</strong>, gesucht wird die <em>Funktion get</em> im Controller <strong>data[0]</strong> (im Beispiel = <em>users</em>).<br />
<strong>data: {field: data[1], value: val}</strong>, für die Datenabfrage wird <strong>field</strong> und <strong>value</strong> standardmäßig per GET übergeben<br />
und als Rückgabe ein JSON String erwartet <strong>dataType: 'json'</strong>.</p>
<p>Wenn die Anfrage erfolgreich war, wird diese Funktion ausgeführt: <strong>success: function(j) {</strong><br />
<strong>j</strong> beinhaltet das erhaltene JSON Array und wird Schritt für Schritt durchgegangen <strong>$.each(j, function(i) {</strong> um dem Zielelement <strong>target</strong> eine neue Option hinzuzufügen <strong>$(target).append('&lt;option value="'+i+'"&gt;'+this+'&lt;/option&gt;')</strong>;</p>
<p>Danach folgt noch ein <em>Errorhandler</em>, der je nach belieben gestalltet werden kann, im Großen und Ganzen aber uninteressant ist.</p>
<p>Das wars schon. Jetzt gehts in den Controller User unter<em> controllers/users_controller.php</em>.</p>
<h2>Im Controller</h2>
<p>In der URL habe ich die Funktion <strong>get</strong> genannt, also muss sie hier definiert werden:</p>
<pre>class UsersController extends AppController {
&nbsp;&nbsp;function get() {
&nbsp;&nbsp;}
}</pre>
<p>Als erstes wird wie gewohnt eine <strong>Liste von Daten</strong> aus dem Model gezogen:</p>
<pre>$list = $this-&gt;User-&gt;find('list', array(
&nbsp;&nbsp;'conditions' =&gt; array(
&nbsp;&nbsp;&nbsp;&nbsp;'User.'.$this-&gt;params['url']['field'] =&gt; $this-&gt;params['url']['value']
&nbsp;&nbsp;)
));</pre>
<p>Und jetzt wird es spannend, mit Hilfe der <strong>RequestHandler</strong> Komponente, die in der Datei <em>app_controller.php</em> oder <em>constrollers/users_controller.php</em> eingebunden wird.</p>
<pre>var $components = array('RequestHandler');</pre>
<p>Jetzt steht das Objekt<strong> $this-&gt;RequestHandler</strong> zur Verfügung, mit dessen Hilfe z.B. abgefragt werden kann ob die ankommende Anfrage per Ajax gesendet wurde. Wenn dies der Fall ist, darf es <strong>keine Debugausgaben</strong> mehr geben und die Liste soll als <strong>JSON String </strong>ausgegeben werden. Und so versteht das auch CakePHP:</p>
<pre>if($this-&gt;RequestHandler-&gt;isAjax()) {
&nbsp;&nbsp;Configure::write('debug', 0);
&nbsp;&nbsp;$this-&gt;autoRender = false;
&nbsp;&nbsp;return json_encode($list);
}</pre>
<p>Fertig! jQuery kriegt jetzt von der Funktion einen JSON String zurück und kann diesen, wie beschrieben, in die Selectbox einfügen.</p>
<h2>Fazit</h2>
<p><em>Diese Lösung ist nur ein Vorschlag</em> und ich bin mir sicher das es viele verschiedene Lösungswege gibt. Wahnsinnig wichtig ist die Tatsache, <strong>dass hier kein bisschen auf die Sicherheit geachtet wurde</strong>. Dieses Beispiel sollte man <strong>nicht unverändert im Livebetrieb einsetzen</strong>!</p>
<p>Ich bin mir auch nicht sicher, ob der gewählte Weg der beste ist. Deshalb freue ich mich diesmal <strong>besonders</strong> über Kommentare mit Anmerkungen und Erweiterungen.</p>
<p><strong>Frohes backen!</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2009/04/11/mit-jquery-und-cakephp-voneinander-abhangige-selectboxen/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

