<?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>Pauls Blog beschäftigt sich mit Webentwicklungsthemen, im Focus stehen jQuery, TYPO3, CSS und PHP.</description>
	<lastBuildDate>Sat, 05 Jun 2010 21:13:53 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<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 beschreibt [...]]]></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><code class=''># Auf dem Server<br />
$ mkdir PROJEKT.git<br />
$ cd PROJEKT.git<br />
$ touch init.txt<br />
$ git init<br />
$ git add .<br />
$ git commit -m "Init"</code></pre>
<p>Auf dem eigenen Rechner wird vom Projekt (eventuell per VPN von zuhause) eine Arbeitskopie erstellt.</p>
<pre><code class=''># Zuhause<br />
$ cd Ordner_fuer_alle_Projekte<br />
$ git clone gitosis@SERVER:git/PROJEKT.git</code></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><code class=''>$ cd cake/console/<br />
$ ./cake bake</code></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><code class=''>$ ./cake schema generate</code></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><code class=''>$ ./cake schema generate<br />
$ cd ../..<br />
$ git add .<br />
$ git commit -m "Einiges ist passiert, inkl. neuer Datenbank"</code></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><code class=''>$ git pull<br />
$ cd cake/console<br />
$ ./cake schema update</code></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><code class=''>app/tmp/<br />
config/database.php</code></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><code class=''># cd app/<br />
# mkdir tmp<br />
# mkdir tmp/cache/<br />
# mkdir tmp/cache/models/<br />
# mkdir tmp/cache/persistent/<br />
# sudo chmod -R 777 tmp/</code></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><code class=''>$ cd cake/console<br />
$ ./cake bake</code></pre>
<p>Da keine Datenbankverbindung besteht, werden die Zugangsdaten abgefragt. Anschließend</p>
<pre><code class=''>$ ./cake schema create</code></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>0</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 da [...]]]></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><code class=''>echo Configure::read('Hello.World');</code></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><code class=''>echo User::get('name');</code></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><code class=''>function &amp;getInstance($user = null) {<br />
&nbsp;&nbsp;static $instance = array();<br />
&nbsp;&nbsp;if ($user) {<br />
&nbsp;&nbsp;[tab]$instance[0] =&amp; $user;<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;return $instance[0];<br />
}</code></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><code class=''>function store($data) {<br />
&nbsp;&nbsp;$user = User::getInstance();<br />
&nbsp;&nbsp;$user = Set::merge($user, $data);<br />
&nbsp;&nbsp;User::getInstance($user);<br />
}</code></pre>
<p>Und natürlich die Funktion um <strong>Daten auszulesen</strong>:</p>
<pre><code class=''>function get($path = '') {<br />
&nbsp;&nbsp;$_user =&amp; User::getInstance();<br />
&nbsp;&nbsp;if(empty($path)) {<br />
&nbsp;&nbsp;[tab]return $_user;<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;$path = str_replace('.', '/', $path);<br />
&nbsp;&nbsp;if (strpos($path, 'User') !== 0) {<br />
&nbsp;&nbsp;[tab]$path = sprintf('User/%s', $path);<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;if (strpos($path, '/') !== 0) {<br />
&nbsp;&nbsp;[tab]$path = sprintf('/%s', $path);<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;$value = Set::extract($path, $_user);<br />
&nbsp;&nbsp;if (!$value) {<br />
&nbsp;&nbsp;[tab]return false;<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;return $value[0];<br />
}</code></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><code class=''>App::import('Model', 'User');<br />
$User = new User;</code></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><code class=''>if($this-&gt;Session-&gt;check('Auth.User')) {<br />
&nbsp;&nbsp;$User-&gt;store($this-&gt;Session-&gt;read('Auth'));<br />
}</code></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><code class=''>$User-&gt;store(array('is_cool' =&gt; true));</code></pre>
<h2>Überall</h2>
<p>Überall, im View, im Controller oder im Model kann jetzt auf die <strong>Benutzerdaten</strong> zugegriffen werden:</p>
<pre><code class=''>echo User::get('User.name');<br />
echo User::get('email');</code></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><code class=''>function check($path) {<br />
&nbsp;&nbsp;$value = User::get($path);<br />
&nbsp;&nbsp;if (!$value || empty($value) || $value === false) {<br />
&nbsp;&nbsp;[tab]return false;<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;return true;<br />
}</code></pre>
<p>Damit kann man schon gut lesbare Abfragen schreiben.</p>
<pre><code class=''>if(User::check('is_cool')) { /* usw. */ }</code></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><code class=''>if(User::is('cool')) //ist der benutzer cool?<br />
if(User::is('cool', 'okay')) //ist der benutzer cool oder okay?</code></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><code class=''>function is() {<br />
&nbsp;&nbsp;$roles = func_get_args();<br />
&nbsp;&nbsp;foreach($roles as $role) {<br />
&nbsp;&nbsp;[tab]if(User::check('User.is_'.$role)) {<br />
&nbsp;&nbsp;[tab]&nbsp;&nbsp;return true;<br />
&nbsp;&nbsp;[tab]}<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;return false;<br />
}</code></pre>
<p>Die Rechte werden im Benutzerarray erwartet in der Form<strong> is_Rechtename</strong>. Im Controller also schnell hinzugefügt:</p>
<pre><code class=''>$User-&gt;store(array('is_cool' =&gt; 1, 'is_okay' =&gt; 1, 'is_bloede' =&gt; 0));</code></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 App Controller
Alles [...]]]></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><code class=''>class AppController extends Controller {<br />
&nbsp;&nbsp;var $components = array('Auth', 'Session', 'RequestHandler');<br />
&nbsp;&nbsp;var $helpers = array('Form', 'Html', 'Javascript', 'Time', 'Text', 'Layout');<br />
}</code></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><code class=''>&lt;div id="footer"&gt;<br />
&nbsp;&nbsp;&lt;? $this-&gt;Layout-&gt;output($footer_for_layout, $this-&gt;element('default_footer')); ?&gt;<br />
&lt;/div&gt;</code></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><code class=''>&lt;?=$layout-&gt;blockStart('footer');?&gt;<br />
Ein &lt;strong&gt;neuer&lt;/strong&gt; Footer!<br />
&lt;?=$layout-&gt;blockEnd();?&gt;</code></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><code class=''>if($this-&gt;RequestHandler-&gt;isAjax()) {<br />
&nbsp;&nbsp;Configure::write('debug', 0);<br />
&nbsp;&nbsp;if($this-&gt;RequestHandler-&gt;prefers() == 'json') {<br />
&nbsp;&nbsp;   die(json_encode($this-&gt;viewVars));<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;else {<br />
&nbsp;&nbsp;   $this-&gt;layout = 'ajax';<br />
&nbsp;&nbsp;}<br />
}</code></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><code class=''>echo $this-&gt;Javascript-&gt;link(array(<br />
&nbsp;&nbsp;'jquery.min'<br />
));</code></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><code class=''>echo $this-&gt;Html-&gt;link(array(<br />
&nbsp;&nbsp;'960',<br />
&nbsp;&nbsp;'reset',<br />
&nbsp;&nbsp;'layout'<br />
));</code></pre>
<p>Um in jQuery immer die richtige Baseurl für Ajaxanfragen zur Verfügung zu haben, nutze ich das Base Tag:</p>
<pre><code class=''>&lt;base href="http://&lt;?=env('HTTP_HOST').$this-&gt;base?&gt;/" /&gt;</code></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><code class=''>&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;</code></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><code class=''>&lt;div class="grid_8"&gt;Linke Hälfte&lt;/div&gt;<br />
&lt;div class="grid_8"&gt;Rechte Hälfte&lt;/div&gt;<br />
&lt;div class="clear"&gt;&lt;/div&gt;</code></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>0</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 und [...]]]></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><code class=''>$components = array('RequestHandler');</code></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><code class=''>function beforeRender() {<br />
&nbsp;&nbsp;if($this-&gt;RequestHandler-&gt;isAjax()) {<br />
&nbsp;&nbsp;   Configure::write('debug', 0);<br />
&nbsp;&nbsp;   if($this-&gt;RequestHandler-&gt;prefers() == 'json') {<br />
&nbsp;&nbsp;      die(json_encode($this-&gt;viewVars));<br />
&nbsp;&nbsp;   }<br />
&nbsp;&nbsp;   else {<br />
&nbsp;&nbsp;      $this-&gt;layout = 'ajax';<br />
&nbsp;&nbsp;   }<br />
&nbsp;&nbsp;}<br />
}</code></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><code class=''>&lt;?php echo $content_for_layout; ?&gt;</code></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><code class=''>&lt;base href="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;&lt;?=$this-&gt;base?&gt;/" host="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;" /&gt;</code></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><code class=''>$ajax({<br />
&nbsp;&nbsp;url: $('base').attr('href')+'exportfiles/add/export:'+$('#ExportEditForm').attr('rel'),<br />
&nbsp;&nbsp;dataType: 'html',<br />
&nbsp;&nbsp;success: function(form) {<br />
&nbsp;&nbsp;    $('#new').html(form);<br />
&nbsp;&nbsp;}<br />
};</code></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><code class=''>$('#new .submit input').live('click', function() { ... });</code></pre>
<p>Das wirklick spannende ist natürlich auch hier die Ajaxanfrage und das Geheimnis liegt im <strong>dataType: 'json'</strong>.</p>
<pre><code class=''>$.ajax({<br />
&nbsp;&nbsp;url: $('base').attr('host')+$form.attr('action'),<br />
&nbsp;&nbsp;data: $form.serialize(),<br />
&nbsp;&nbsp;type: 'POST',<br />
&nbsp;&nbsp;dataType: 'json',<br />
&nbsp;&nbsp;success: function(content) {<br />
&nbsp;&nbsp;  if(content.saved) {<br />
&nbsp;&nbsp;      $form.closest('fieldset').prev().find('ul')<br />
&nbsp;&nbsp;          .find('li.empty').remove().end()<br />
&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;');<br />
&nbsp;&nbsp;  }<br />
&nbsp;&nbsp;  else {<br />
&nbsp;&nbsp;      alert("Leider ist ein Fehler beim speichern aufgetreten.");<br />
&nbsp;&nbsp;  }<br />
&nbsp;&nbsp;}<br />
});</code></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><code class=''>$this-&gt;set('saved', true);</code></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 wie [...]]]></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><code class=''>echo $javascript-&gt;link('jquery.min');<br />
echo $javascript-&gt;link('jquery.action');<br />
echo $scripts_for_layout;</code></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><code class=''>$javascript-&gt;link(array(<br />
&nbsp;&nbsp;'jquery.templating',<br />
&nbsp;&nbsp;'jquery.ui.autobox.ext.js',<br />
&nbsp;&nbsp;'jquery.ui.autobox.js'<br />
), false);</code></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><code class=''>$html-&gt;css('jquery.ui.autobox.css', null, array(), false);</code></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><code class=''>background: url('../img/close.gif');</code></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><code class=''>&lt;base href="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;&lt;?=$this-&gt;base?&gt;/" host="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;" /&gt;</code></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><code class=''>jQuery(function($) {<br />
&nbsp;&nbsp;<br />
&nbsp;&nbsp;var list1 = [{text: 'Curious George'}, {text: 'George of the Jungle'}, {text: 'Felix the Cat'}];<br />
&nbsp;&nbsp;<br />
&nbsp;&nbsp;$('#ModelTempTags').autobox({<br />
&nbsp;&nbsp;[tab]list: list1,<br />
&nbsp;&nbsp;[tab]match: function(typed) { return this.text.match(new RegExp(typed, "i")); },<br />
&nbsp;&nbsp;[tab]insertText: function(obj) { return obj.text },<br />
&nbsp;&nbsp;[tab]templateText: "&lt;li&gt;Hey: &lt;%= text %&gt;&lt;/li&gt;"<br />
&nbsp;&nbsp;});<br />
});</code></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><code class=''>prevals: $('#LinkTempTags').val().split(', ')</code></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><code class=''>ajax: $('base').attr('href')+'tags/index',</code></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><code class=''>$tags = $this-&gt;Tag-&gt;find('all', array('conditions' =&gt; array('title LIKE' =&gt; $this-&gt;params['url']['val'].'%'), 'order' =&gt; 'Tag.title ASC'));</code></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><code class=''>class TagsController extends AppController {<br />
&nbsp;&nbsp;var $components = array('RequestHandler');</code></pre>
<p>In der<strong> Index Funktion</strong> wird bei einem Ajaxaufruf der Renderingprozess gestoppt und purer Text zurück gegeben.</p>
<pre><code class=''>if($this-&gt;RequestHandler-&gt;isAjax()) {<br />
&nbsp;&nbsp;Configure::write('debug', 0);<br />
&nbsp;&nbsp;$this-&gt;autoRender = false;<br />
&nbsp;&nbsp;return '['.implode(',', Set::format($tags, '{text: "{0}"}', array('{n}.Tag.title'))).']';<br />
}</code></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><code class=''>echo $form-&gt;input('temp_tags', array('name' =&gt; 'data[Model][temp_tags][]');</code></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><code class=''>debug($this-&gt;data['temp_tags']);</code></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 folgenden [...]]]></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><code class='php'>$components = array('Imap');</code></pre>
<p>In einem <strong>Shellprogramm</strong> (abgelegt in <em>app/vendors/shells/</em>) muss die Componente über Import reingeholt werden:</p>
<pre><code class='php'>App::import('Component', 'Imap');<br />
$this-&gt;Imap = new ImapComponent();</code></pre>
<h2>Verbinden</h2>
<p>Die Componente erwartet ein Array mit den Zugangsdaten zum Postfach.</p>
<pre><code class='php'>private $imap_config = array(<br />
&nbsp;&nbsp;'imap_user' =&gt; 'test@kiwi-service.de',<br />
&nbsp;&nbsp;'imap_pass' =&gt; 'total-geheimes-supersicheres-passwort',<br />
&nbsp;&nbsp;'imap_flags' =&gt; '',<br />
&nbsp;&nbsp;'imap_mailbox' =&gt; 'test@kiwi-service.de',<br />
&nbsp;&nbsp;'imap_server' =&gt; 'kiwi-service.de',<br />
&nbsp;&nbsp;'imap_port' =&gt; '143'<br />
);</code></pre>
<p>Die einzelnen Keys sind ja selbsterklärend. Nur die Componente muss sie noch wissen. Das geschieht per Übergabe eines Arrays:</p>
<pre><code class='php'>$this-&gt;Imap-&gt;items($this-&gt;imap_config);</code></pre>
<p>Oder einzelnd mit</p>
<pre><code class='php'>$this-&gt;Imap-&gt;item('imap_user', 'test@kiwi-service.de');</code></pre>
<p>Wenn alle Daten gesetzt sind, wird zum Postfach verbunden.</p>
<pre><code class='php'>$this-&gt;Imap-&gt;connect();</code></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><code class='php'>$messages = $this-&gt;Imap-&gt;msg_list();<br />
foreach($messages as $message) {<br />
&nbsp;&nbsp;debug('Titel: '.$message['title']);<br />
}</code></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><code class='php'>debug(utf8_encode(quoted_printable_decode($message['body'])));</code></pre>
<h2>Ende</h2>
<p>Zu guter letzt sollte dann auch noch die Verbindung wieder getrennt werden.</p>
<pre><code class='php'>$this-&gt;Imap-&gt;close();</code></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>2</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 speichern, [...]]]></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><code class='php'>&lt;?<br />
&nbsp;&nbsp;class Tag extends AppModel {<br />
&nbsp;&nbsp;[tab]var $name = 'Tag';<br />
&nbsp;&nbsp;[tab]<br />
&nbsp;&nbsp;[tab]$hasAndBelongsToMany = array('Post');<br />
&nbsp;&nbsp;}<br />
?&gt;</code></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><code class='php'>$hasAndBelongsToMany = array('Tag');</code></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><code class='php'>echo $form-&gt;input('temp_tags', array(<br />
&nbsp;&nbsp;'label' =&gt; 'Tags (mit Komma trennen)',<br />
));</code></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><code class='php'>function beforeSave() {<br />
&nbsp;&nbsp;// hier gehts ab!<br />
}</code></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><code class='php'>if(isset($this-&gt;data[$this-&gt;name]['temp_tags'])) {<br />
&nbsp;&nbsp;$tags = explode(",", $this-&gt;data[$this-&gt;name]['temp_tags']);<br />
&nbsp;&nbsp;unset($this-&gt;data[$this-&gt;name]['temp_tags']);</code></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><code class='php'>foreach($tags as $tag) {<br />
&nbsp;&nbsp;$tag = trim($tag);<br />
}</code></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><code class='php'>$id = $this-&gt;Tag-&gt;findByName($tag);</code></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><code class='php'>if(!$id['Tag']['id']) {<br />
&nbsp;&nbsp;$this-&gt;Tag-&gt;create();<br />
&nbsp;&nbsp;$this-&gt;Tag-&gt;save(array('name' =&gt; $tag));<br />
&nbsp;&nbsp;$id['Tag']['id'] = $this-&gt;Tag-&gt;id;<br />
}</code></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><code class='php'>$this-&gt;data['Tag']['Tag'][] = $id['Tag']['id'];</code></pre>
<p>Fehlt nur noch die ggf. definierte Elternmethode und eine positive Rückmeldung.</p>
<pre><code class='php'>parent::beforeSave();<br />
return true;</code></pre>
<h2>Fertig!</h2>
<pre><code class='php'>}</code></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 der [...]]]></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><code class='php'>class AppController extends Controller {<br />
&nbsp;&nbsp;var $helpers = array('Ajax');<br />
}<br />
</code></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><code class='php'>echo $javascript-&gt;link(<br />
&nbsp;&nbsp;array(<br />
&nbsp;&nbsp;[tab]'jquery.min.js',<br />
&nbsp;&nbsp;[tab]'jquery.action.js',<br />
&nbsp;&nbsp;)<br />
);</code></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><code class='html'>&lt;base href="http://&lt;?=$_SERVER['HTTP_HOST']?&gt;&lt;?=$this-&gt;base?&gt;/" /&gt;</code></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><code class='php'>echo $this-&gt;element("projectform");</code></pre>
<p>eingebunden. Die Datei selbst besteht, gekürzt, aus folgenden Zeilen:</p>
<pre><code class='php'>$c = array('') + $customers;<br />
echo $form-&gt;input('customer_id', array(<br />
&nbsp;&nbsp;'class' =&gt; 'relation',<br />
&nbsp;&nbsp;'rel' =&gt; 'ProjectContact',<br />
&nbsp;&nbsp;'data' =&gt; 'users|customer_id',<br />
&nbsp;&nbsp;'options' =&gt; $c<br />
));<br />
echo $form-&gt;input('contact', array(<br />
&nbsp;&nbsp;'options' =&gt; $contacts<br />
));</code></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><code class='javascript'>if($("base").length == 1)<br />
&nbsp;&nbsp;BASEURL = $("base").attr("href");<br />
else {<br />
&nbsp;&nbsp;alert("Keine BaseUrl gefunden!!!");<br />
}</code></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><code class='php'>class UsersController extends AppController {<br />
&nbsp;&nbsp;function get() {<br />
&nbsp;&nbsp;}<br />
}</code></pre>
<p>Als erstes wird wie gewohnt eine <strong>Liste von Daten</strong> aus dem Model gezogen:</p>
<pre><code class='php'>$list = $this-&gt;User-&gt;find('list', array(<br />
&nbsp;&nbsp;'conditions' =&gt; array(<br />
&nbsp;&nbsp;[tab]'User.'.$this-&gt;params['url']['field'] =&gt; $this-&gt;params['url']['value']<br />
&nbsp;&nbsp;)<br />
));</code></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><code class='php'>var $components = array('RequestHandler');</code></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><code class='php'>if($this-&gt;RequestHandler-&gt;isAjax()) {<br />
&nbsp;&nbsp;Configure::write('debug', 0);<br />
&nbsp;&nbsp;$this-&gt;autoRender = false;<br />
&nbsp;&nbsp;return json_encode($list);<br />
}</code></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>1</slash:comments>
		</item>
		<item>
		<title>CakePHP und die Pfade in CSS Dateien</title>
		<link>http://www.interaktionsdesigner.de/2008/08/08/cakephp-und-die-pfade-in-css-dateien/</link>
		<comments>http://www.interaktionsdesigner.de/2008/08/08/cakephp-und-die-pfade-in-css-dateien/#comments</comments>
		<pubDate>Fri, 08 Aug 2008 12:20:18 +0000</pubDate>
		<dc:creator>Paul</dc:creator>
				<category><![CDATA[CakePHP]]></category>

		<guid isPermaLink="false">http://www.interaktionsdesigner.de/?p=57</guid>
		<description><![CDATA[Ich musste lange über den Titel nachdenken, ungefähr genau so lange wie ich vorher in meiner Cake Applikation rumprobiert habe bis ich endlich rausgefunden habe wie die korrekten Pfadangaben in einer CSS Datei zu Grafiken lauten!
So siehts aus: Meine CSS Datei liegt im Ordner vendors/css/
Die Bilder für die Webausgabe liegen dagegen in app/webroot/img/
Fragt mich bitte [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignright size-full wp-image-58" title="cake-logo" src="http://www.interaktionsdesigner.de/wp-content/uploads/2008/08/cake-logo.png" alt="" width="180" height="180" />Ich musste lange über den Titel nachdenken, ungefähr genau so lange wie ich vorher in meiner Cake Applikation rumprobiert habe bis ich endlich rausgefunden habe wie die korrekten Pfadangaben in einer CSS Datei zu Grafiken lauten!<span id="more-57"></span></p>
<p>So siehts aus: Meine CSS Datei liegt im Ordner <strong>vendors/css/</strong></p>
<p>Die Bilder für die Webausgabe liegen dagegen in <strong>app/webroot/img/</strong></p>
<p>Fragt mich bitte nicht warum; in einem Tutorial oder beim ersten Versuch hat es damit geklappt und so hab ichs erstmal gelassen. Jetzt stellt sich allerdings die Frage wie man das beides miteinander verknüpft.</p>
<p>Zum Glück ist die Lösung mal wieder einfach. Die CSS aus dem Vendor wird für die Webausgabe in den <strong>webroot</strong> Ordner kopiert, oder per URL Rewriting dort hin geschrieben, jedenfalls kann man von diesem Ordner aus die Bilder relativ zur CSS Datei verknüpfen:</p>
<pre><code class="css">background-image:url("../img/edit.png");</code></pre>
<p>Leider fehlt mir die Zeit ausführlich über Cake zu schreiben, aber ich habe große Lust drauf und werde es irgendwann schaffen!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.interaktionsdesigner.de/2008/08/08/cakephp-und-die-pfade-in-css-dateien/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
