Hi guys, I thought I'd give you a little example of what the new template layer system can do.
We participated in the PHP Throwdown (http://www.phpthrowdown.com) and built an IRC bot using Agavi. It's called "Chuckwalla", contains a fully fledged IRC library that doesn't suck, has live logs, cool web interfaces etc and will be open sourced soon. One feature is the live logs. You click a channel, and it uses Ajax to refresh the contents, so you can follow the discussion in a channel. So for the first load, we need a full document, and for subsequent XMLHttpRequest, we just want the new messages. Here is the layout's configuration for output type "html": <layout name="standard"> <layers> <layer name="content" class="AgaviFileTemplateLayer" /> <layer name="decorator" class="AgaviFileTemplateLayer"> <parameter name="template">Master</parameter> </layer> </layers> </layout> Let's assume this is our decorator template: <html> <head><title>logs!</title></head> <body> <?php echo $slots['content']; ?> </body> </html> And here is the content template (LiveSuccess.php): <div id="roomTitle"><?php echo $t['topic']; ?></div> <table border="0" cellspacing="0" cellpadding="0"> <tbody> <?php foreach($t['messages'] as $message): ?> <tr> <td class="name"><?php echo $message->getNick()->getNick(); ?></td> <td class="message"><?php echo $message->getMessage(); ?></td> <td class="time"><?php echo $message->getMessageDate(); ?></td> </tr> <?php endforeach; ?> </tbody> </table> Now we also have an output type for the AJAX calls, called "json". It's configured like this: <layout name="standard"> <layers> <layer name="content" class="AgaviFileTemplateLayer" /> </layers> </layout> But now we have a problem: If an AJAX call is made, we get back the entire inner content template, complete with the <divs> and the <table>, but we only want the <tbody> to append to the current table (remember, tables can have multiple <tbody> elements). One approach would be to simply have two templates. The other approach would be to split them up and include() the inner portion in the non-ajax version, and set the templates differently per output type. However, there also is a third approach. Right now, we have these layers: +------------------------+ | <html> decorator | | +--------------------+ | | | <table> content | | | | with <tbody>, <tr> | | | +--------------------+ | | </html> | +------------------------+ What we can do now is split up the template like we would with the include() solution: +------------------------+ | <html> decorator | | +--------------------+ | | | <table> wrapper | | | | +----------------+ | | | | | <tbody> inner | | | | | | content | | | | | +----------------+ | | | | </table> | | | +--------------------+ | | </html> | +------------------------+ First, the "wrapper" template (LiveSuccess.wrapper.php): <div id="roomTitle"><?php echo $t['topic']; ?></div> <table border="0" cellspacing="0" cellpadding="0"> <?php echo $slots['inner']; ?> </table> And the actual "inner" content template (LiveSuccess.php): <tbody> <?php foreach($t['messages'] as $message): ?> <tr> <td class="name"><?php echo $message->getNick()->getNick(); ?></td> <td class="message"><?php echo $message->getMessage(); ?></td> <td class="time"><?php echo $message->getMessageDate(); ?></td> </tr> <?php endforeach; ?> </tbody> For Ajax, everything is fine now. We'll get the <tbody> content. But for output type "html", the result will be this: <html> <head><title>logs!</title></head> <body> <tbody> <tr> <td class="name">John McClane</td> <td class="message">Yippie-kay-yay motherfucker</td> <td class="time">03:27</td> </tr> </tbody> </body> </html> What we need now is insert the wrapper IN BETWEEN "decorator" and "content" layer. Remember that the decorator template expects to output $slots ['content']. Hence, we actually have to modify the "content" layer to display the wrapper template (LiveSuccess.wrapper.php), and then prepend another "inner" layer (LiveSuccess.php) to the layer list. We do it like this, in the view: public function executeHtml(AgaviRequestDataHolder $rd) { // remember: first, you should have a base view and call a method on the parent to load the layout instead of doing it here. // second, loadLayout() would be enough since "standard" is the default layer $this->loadLayout('standard'); // we get the "content" layer and change the template to the wrapper $c = $this->getLayer('content'); $c->setTemplate('LiveSuccess.wrapper'); // and then prepend the actual "inner" content template to the list $i = $this->prependLayer($this->createLayer ('AgaviFileTemplateLayer', 'inner')); $i->setTemplate('LiveSuccess'); } And that's it! Now we have the desired result: <html> <head><title>logs!</title></head> <body> <div id="roomTitle"><?php echo $t['topic']; ?></div> <table border="0" cellspacing="0" cellpadding="0"> <tbody> <tr> <td class="name">John McClane</td> <td class="message">Yippie-kay-yay motherfucker</td> <td class="time">03:27</td> </tr> </tbody> </table> </body> </html> IMPORTANT: Some advice regarding base views. In the original email, I recommended that you always have a base view you extend from where execute() throws an exception. This view would also have base methods you can call that load layers, maybe set dynamic slots, or do stuff like: $this->setAttribute('_contentType', $this->container->getOutputType()- >getParameter('Content-Type')); However, there is a problem here. The whole point of having execute() throw an exception is that if there is a request using, say, ajax, that sets the output type to, say, "json" because of a routing rule like this: <route pattern="^XMLHttpRequest$" source="_SERVER [HTTP_X_REQUESTED_WITH]" stop="false" output_type="json" /> And you then implement a base executeJson() in the base view... all of your views DO serve the json output type, and the normal execute() is never called, not even for actions/views that don't implement ajax features and should thus get an exception! To solve that problem, name these base methods differently. I suggest you call them "setupHtml", "setupJson" and so on, and then call them using $this->setupHtml($rd); etc inside your concrete view's executeHtml() method. Again, if there are any questions, let me know. Cheers, David _______________________________________________ Agavi Dev Mailing List [email protected] http://lists.agavi.org/mailman/listinfo/dev
