[Agavi-Users] IMPORTANT: Breaking changes in 0.11 branch: tons of new features!

18 views
Skip to first unread message

David Zülke

unread,
Jan 25, 2007, 7:28:30 AM1/25/07
to d...@lists.agavi.org, Agavi Users Mailing List
Hi guys,

revision 1568 (http://trac.agavi.org/changeset/1568) saw the merge of
the david-execution_flow branch to the 0.11 branch. This means some
things will break, and it means many things have improved.

Before I begin, let me apologize for promising that there wouldn't be
any more breaking changes after 0.11RC1. It was found out that the
way things were, we couldn't implement caching (http://trac.agavi.org/
ticket/78, coming to SVN today!), so I figured I should just throw
out rendering filters (http://trac.agavi.org/ticket/377) which were
the primary reason caching wouldn't be possible, and while doing
that, it became clear that I could remodel the execution flow
entirely, with minimal to no BC breaks, away from the hacky Action
Stack implementation to a system where we have a container that fully
encapsulates the execution of an action, without any possibility of
the "outer world" breaking things inside this container (http://
trac.agavi.org/ticket/373, and http://trac.agavi.org/ticket/290 which
was originally scheduled for 2.0).

That all went very well, and it even became possible to use
decorators within slots, and slots inside the content template,
without decorators. We now had a very modern, very advanced, and were
forward compatible execution flow model, but the templating system
all of a sudden seemed horribly archaic in comparison. After some
pondering, I finally had the idea to re-do the templating system so
it works just like the decorators, but with an infinite number of
layers, like an onion or a russian doll (http://trac.agavi.org/ticket/
287, also originally scheduled for 2.0). As a result, existing
templates would work without modification, only the API in the views
would change.

We also discovered that while our validation system worked very well
for protecting code from malicious or non-validating request
parameters, files, cookies, http headers etc didn't get filtered that
way. Thus, we introduced request data holders that are now passed to
the actions and hold all request data: parameters, files, cookies,
headers etc (http://trac.agavi.org/ticket/389). While doing that, it
became clear how awfully crappy the current file handling was. getFile
() will now return an object on which you can call getSize() to get
the size, or move() to move the uploaded file (http://trac.agavi.org/
ticket/391). These two changes should result in only minimal breaking
changes and can mostly be adapted to by some search and replace work.

Also, let me stress that that's it for now. No more planned features,
everyone's happy. There are some issues and minor bugs to clean up,
but besides that, I'm sure we won't see any more breakage from now
on. Also because neither I nor anyone else wants that. Really. Again,
my apologies.


Now let's move on to the actual changes.


First, the execution flow.

Until now, execution was done using Controller::forward(). A forward
put the information about the action to run on the global action
stack and then started running through the filters etc. Each time
some code wanted to get, say, the action name, it had to pull the
last item from the stack and read the information from that. As you
can probably imagine, that was very prone to errors and totally
messed things up when forwarding, for instance, since the information
about what was going on (the action name, module name, action
instance, request data etc) was on the stack, not encapsulated within
the execution. This decoupling and indirect way of accessing the
current state of execution was rather ugly.

Enter execution containers.
Controller::forward() is gone now, and each container encapsulates
all information necessary to run an action: module name, action name,
request information, the output type (yes, the output type is _not_
global anymore!), the response, information about the view etc. When
execute() is called, the entire execution happens fully isolated,
like a black box. This also brings a slight performance improvement
for the general execution process.

As a consequence, you obviously cannot use $actionStack->getLastEntry
()->getActionName() etc anymore. Instead, Views and Actions are given
the container instance as the first argument to the initialize()
method. It is then available via $this->getContainer(). Note that in
Views, $this->getResponse() is a shortcut for $this->getContainer()-
>getResponse(). So for example to grab the micro timestamp at which
the current action started execution, do $this->getContainer()-
>getMicrotime().

Also, filters have changed due to this. Filter execute() methods now
get the filter chain as the first argument, and the container as the
second. To proceed to the next filter in the chain, you must call
$filterChain->execute($container), i.e. hand on the container you got
as the second argument. Again, the response for the container is
available from $container->getResponse().

Also, for forwarding and for setting slots, you use an execution
container instance now, so to forward to EggAction in module Spam,
you do return $this->container->createExecutionContainer('Spam',
'Egg'); Likewise, to set a slot, you do setSlot('name', $container);
(I'll get to slots later)
An important note here. As I mentioned, each execution container also
has it's own output type. This is rather useful since you can run a
slot with a different output type and have it produce, say, JSON data
you use in a script tag in the page header. Unfortunately, the
standard way to create an execution container is the
createExecutionContainer method in the Controller, which will use the
default output type. That's why each container has such a method too,
which calls the controller's method, but with the same output type as
that controller - basically, the container creates a "brother" of
itself.
createExecutionContainer() optionally accepts a RequestDataHolder
object as the third argument to pass additional information such as
parameters, files, cookies to the forwarded action or the slot, and
the name of an output type as the fourth argument.

When you define a forward, this information is stored in the current
execution container. Once that finished running, it will execute this
next container, and return THAT CONTAINER's RESPONSE. That means if
you set a cookie or a redirect in the container and then forward,
that information gets lost. This is usually the desired behavior, but
there might be situations where you don't want that to happen.
Because of that, there's also a global response in $controller-
>getGlobalResponse(). There are circumstances when you should use
that one, like when setting a cookie from a routing callback or so.

Also, redirection is now done via the response by calling $response-
>redirect('targeturl'); Since you don't want & there, remember
to pass array('separator' => '&') as the third argument to
Routing::gen(). WebResponse::redirect also accepts a second parameter
for an HTTP status code other than the default 302.

Again, you have to make a wise decision between local container's
response and the global response. Remember, only a local container's
response will be included in caches. Use it whenever possible.

Other than that, this change shouldn't affect actions and/or views.
Yes, rendering filters were removed, but I don't think anyone ever
uesed these anyway. Likely since there was absolutely no use case for
them, all they did was cause unnecessary overhead.


Next: the new template architecture.

This is a rather big change.
Until now, we had a content template, and a decorator template. A
decorator template could also have slots, but the content template
couldn't, and a slot couldn't use a decorator template itself. Per-
locale templates were only possible for the content template, not for
the decorator. You could only have these two layers. Both content
templates any layers had to use the same renderer with the same
configuration. Templates couldn't come from a database. And so on.
Clearly, that was poorly implemented.

Now, all of the above works. And it's not even poorly implemented ;)

First, the layering.
Instead of setTemplate and setDecoratorTemplate, layers are now used.
To mimic the old behavior, a view would have a layer called "content"
with the content template (e.g. "IndexSuccess"), and another layer
who's name doesn't really matter (let's call it "decorator") with the
template "Master".
Now what happens is that the first layer is rendered, and it's result
is available to the next layer as a slot. The name of the slot is the
name of that layer. Here, we used "content" because it doesn't only
make sense, but actually produces results similar to the old system,
where the content template was always available as "content" in the
decorator.
As you probably have guessed, layers are executed in the order they
are defined. Of course, you can re-arrange them at runtime, remove
them, add new ones at arbitrary locations and so on. So let's have a
look at the code:

$l1 = $this->createLayer('AgaviFileTemplateLayer', 'content');
$l1->setTemplate('IndexSuccess');
$this->appendLayer($l1);
$l2 = $this->createLayer('AgaviFileTemplateLayer', 'decorator');
$l2->setTemplate('Master');
$this->appendLayer($l2);

Yes, you are absolutely right. This code is HIDEOUS. You don't want
to do that in every view, and even with a base view (more on that
later), it's awfully ugly.

BUT!

You can configure these layouts very easily, and as a result, you
don't have to write that code anymore, instead you just do

$this->loadLayout();

to load a layout (no name passed here, so it will use the default
one). I'll explain this in more detail in a minute.

But first, let's look at slots and renderers.

First, slots. You can now set slots on _any_ of these layers:
$l2->setSlot('latestproductbox', $this->container-
>createExecutionContainer('ZeModule', 'LatestProductWidget'));
will set the slot on the decorator layer, and is then available there
via $slots['latestproductbox'] (yes, $slots, not $template, more on
that later).

Second, renderers. Let's say you wanted to use Smarty instead of the
default renderer for the decorator layer:
$l2 = $this->createLayer('AgaviFileTemplateLayer', 'decorator',
'nameofthesmartyrendererasconfiguredinoutput_types.xml');

Of course, you can also pass in a renderer instance:
$r = new AgaviSmartyRenderer();
$r->initialize($this->context);
$l2 = $this->createLayer('AgaviFileTemplateLayer', 'decorator',
$r);

!!! This means you can easily use renderers everywhere now, let's say
in a model to render an email. What's more, you can even use layers,
slots etc, by just creating them and rendering each of them. This is
a bit of an advanced task, and there might be a helper to assist you
with that, but even without it, it's only a couple of lines of code.
Let me know if you need any assistance.

You probably noticed AgaviFileTemplateLayer. That's the name of the
layer implementation we want to use. AgaviFileTemplateLayer is a
special implementation of AgaviStreamTemplateLayer that allows the
use of a directory etc, and is designed for the file system.
AgaviStreamTemplateLayer is a generic implementation for PHP streams
that allows you to load a template via HTTP, for example, or any
other (built-in or userspace) stream wrapper registered with PHP:
$l = $this->createLayer('AgaviStreamTemplateLayer', 'asdf');
$l->setTemplate('www.myhost.com/thetemplate.php');
$l->setScheme('http');
to load http://www.myhost.com/thetemplate.php, or, even cooler, using
an RFC 2397 data stream (http://www.faqs.org/rfcs/rfc2397, http://
de.php.net/manual/en/wrappers.data.php):
$l = $this->createLayer('AgaviStreamTemplateLayer', 'blah');
$l->setTemplate('text/plain;base64,SGVsbG8gVGVzdA==');
$l->setScheme('data');
That will result in "Hello Test".

Note: you can also use setParameter('name', 'value') or setParameters
(array(...)) instead of setWhatever('value').

Back to our original example:
$l1 = $this->createLayer('AgaviFileTemplateLayer', 'content');
$l1->setTemplate('IndexSuccess');
$this->appendLayer($l1);

setTemplate() isn't necessary here, because createLayer() sets the
following parameters on a layer by default:
- "template" => the name of the current view
- "module" => the name of the current view's module
- "output_type" => the name of the output type
- "name" => the name of the layer ("content", "decorator" and so on).

Now it's getting interesting: there's a parameter called "targets",
which holds a list of formatting patterns used for looking up the
template resource name. For AgaviFileTemplateLayer, the default
target pattern is:
${directory}/${template}${extension}
Where ${extension} is the extension you have set, or, if none was
set, the default extension of the renderer (e.g. ".php").
${directory} is a pattern itself:
%core.module_dir%/${module}/templates
It's a bit complicated, but I hope you get the idea - the effect is
that the default directory is app/modues/${module}/templates, and $
{module} is resolved to the current module, because that is set by
createLayer(). Now you can easily set a different directory for the
template of a layer:
$l->setDirectory('/the/template/dir'); (and yes, you guessed it,
you could also do $l->setParameter('directory', '/the/template/dir');)

Of course, you can again use variables that will be expanded. This
variable can be _any_ parameter set on the layer:
$l->setDirectory(AgaviConfig::get('core.template_dir') . '/$
{module}/${output_type});
would look for the template in app/templates/Spam/html provided that
the current module is "Spam" and the output type is "html".

IMPORTANT: Variables are expanded just in time, not as you set them,
which means you can set a string containing a variable, and change
that variable after that. Also, as an alternative to ${foo} you can
also use {$foo} and $foo, the syntax works just like with PHP vars.

NOTE: if i18n is enabled, there are two more default targets for
FileTemplateLayer:
${directory}/${locale}/${template}${extension}
${directory}/${template}.${locale}${extension}

You can now easily implement your own template layer class that loads
the template from a database, and then caches it into a file and
returns that file as the resource name. Or, alternatively, you can of
course write a PHP stream wrapper that interfaces with your database
and simply use the StreamTemplateLayer. Or you read from memcached.
The possibilities are endless.

But we already established that doing all this in code is rather
annoying. That's why you can define layers into layouts, and then
load a layout in your view. This is done in output_types.xml, per
<output_type>:

<layouts default="standard">
<layout name="standard">
<layers>
<layer name="content" class="AgaviFileTemplateLayer" />
<layer name="decorator" class="AgaviFileTemplateLayer">
<parameters>
<parameter name="template">Master</parameter>
</parameters>
</layer>
</layers>
</layout>
</layouts>

$this->loadLayout() in a view will then load the default layout
"standard" unless you give it the name of a layout to load as the
first argument. The method returns an array of parameters set for the
layout, that might come in handy if you need to carry any further
information from the definition into the view.

Each <layer> accepts the following attributes:
- "name": name of the layer
- "class": the name of the AgaviTemplateLayer implementation to use
- "renderer": the optional name of a renderer to use if you don't
want to use the default renderer

IMPORTANT: you can now define multiple renderers per output type.
Each renderer must now have a name, and the <renderers> element must
define a default renderer.

Of course, you can do $this->getLayer('zename'); to grab a layer
after calling loadLayout()!

Also, you can configure slots for a layer by putting them into a
<slots> element which (obviously) goes into the <layer> element:

<slot name="loginbox" module="Default" action="LoginBox" />

Each <slot> accepts the following attributes:
- "name": the name of the slot
- "module": the module of the action
- "action": the name of the action
- "output_type": an optional output type to use for the slot, else
the current container's output type will be used.

You can also place <parameters> into a <slot> to pass these
parameters to the action as additional request information.

To manually set a slot in the code, use $layer->setSlot(). Of course,
again, you can modify slots, for instance:

$this->loadLayout();
$this->getLayer('decorator')->getSlot('loginbox')->getRequestData
()->setParameter('foo', 'bar');

and so on.


A general word on views, base views and base actions

It is strongly recommended that you _always_ use a CustomBaseAction
that all your actions extend, even if it is empty. Same goes for the
view, use a CustomBaseView. You can have these autoloaded per-module
using a module autoload.xml, that keeps things tidy. The sample app
does this, look at it for an example.

The main reason why you should do this is because you can easily
inject code into all actions and all views at a later time. Do it.
Even if the classes are empty. Do it. Always. The manual has
instructions on how to use custom build templates so "agavi action"
always generates actions and views that extend from your base classes.

Also, you should NEVER use execute() in views! Always, always, always
use executeHtml(), executeRss() and so on. Then, in your base view,
implement a "public final function execute()" that forwards to the
404 action or so. There is a very simple reason for this: if the
output type that is set isn't handled by your view, nothing breaks!
For instance, you might have this route at the top of your routing.xml:

<route pattern="text/javascript" source="_SERVER[HTTP_ACCEPT]"
output_type="json" />
that would set the output type to "json" when an ajax request comes
in (prototype sends this Accept: header, for example), or
<route name="rss" pattern="/rss$" cut="true" stop="false"
output_type="rss" />
to set the output type to "rss" when a URL ends on /rss.

But imagine what happens if the view doesn't implement the "rss"
output type! Things break horribly. Therefor, always use a specific
execute method, and in your execute(), handle the situation. Instead
of forwarding to 404, you could also check the output type and set a
response accordingly, like the HTTP 407 Not Implemented code. Or
throw an exception and handle it in the exception template (remember,
these can be per output type, too). But do it right!


And finally: request data holders.

You already know the parameter holder passed to action and view
execute() methods. This is now an AgaviRequestDataHolder (typically
AgaviWebRequestDataHolder) object instead which also holds
information other than parameters, like cookies, files and headers.
they all pass validation too inthe exactly same manner as parameters:

$rd->getHeader('User-Agent'); // or 'USER_AGENT' or 'user-agent' etc,
doesn't matter

$rd->getCookie('cookiename');

Also, there's no getFileName, getFileError etc anymore. Instead,
getFile() returns an AgaviUploadedFile object, which holds all this
information:

$file = $rd->getFile('foo[bar]');
$file->getSize();
$file->hasError();
$file->move('/path/to/destination');

The global request data holder is copied into each execution
container when that container is executed. You can access it via
$context->getRequest->getRequestData(). As before, this cannot be
done while inside an action's or view's execute() method to encourage
people to use validated request data only.


Hope that helps. Let me know if there are any questions. Comments and
feedback welcome :)

Yours,

David

_______________________________________________
users mailing list
us...@lists.agavi.org
http://lists.agavi.org/mailman/listinfo/users

David Zülke

unread,
Jan 25, 2007, 8:26:58 AM1/25/07
to Agavi Dev Mailing List, Agavi Users Mailing List
It is Response::setRedirect(), not redirect(), sorry.

Also, I forgot to mention that you cannot extract slots anymore, and
that slots and template attributes cannot share a variable. That
means slots won't be available in $template anymore, the default
variable name is now $slots. All still configurable using "var_name",
"slot_var_name" and "extract_vars" parameters of the renderer

> Agavi Dev Mailing List
> d...@lists.agavi.org
> http://lists.agavi.org/mailman/listinfo/dev

David Zülke

unread,
Jan 25, 2007, 8:28:17 AM1/25/07
to Agavi Dev Mailing List, Agavi Users Mailing List
Argh... "slots_var_name"

Van Daele, Koen

unread,
Jan 26, 2007, 4:01:27 AM1/26/07
to Agavi Users Mailing List
Hello David (and others),

Thanks for all the hard work. Seems like there are some new cool things to play with. When I have a chance, I'll update to the latest revision and give it a try. Escecially the view/templating improvements seem to be just what I need.

Keep up the good work.

Greetings,
Koen

> -----Oorspronkelijk bericht-----
> Van: users-...@lists.agavi.org
> [mailto:users-...@lists.agavi.org] Namens David Zülke
> Verzonden: donderdag 25 januari 2007 13:29
> Aan: d...@lists.agavi.org
> CC: Agavi Users Mailing List
> Onderwerp: [Agavi-Users] IMPORTANT: Breaking changes in 0.11
> branch: tons ofnew features!

David Zülke

unread,
Jan 29, 2007, 8:02:21 AM1/29/07
to Agavi Dev Mailing List, Agavi Users Mailing List
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 Zülke

unread,
Jan 30, 2007, 4:32:08 AM1/30/07
to Agavi Users Mailing List, Agavi Dev Mailing List
As of http://trac.agavi.org/changeset/1609, the output of the inner
layer is available via the variable $inner in templates. Also
available are $container for the current execution container and
$view for the template's view. Like with factories, the names of
these assigns ("inner", "container" and "view") can be configured
using the "assigns" parameter of a renderer.

Since the wrapping template now does not have to know the inner
layer's name anymore, the example can be simplified quite a bit.

Master.php:


<html>
<head><title>logs!</title></head>
<body>

<?php echo $inner; ?>
</body>
</html>

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>

LiveSuccess.wrapper.php:


<div id="roomTitle"><?php echo $t['topic']; ?></div>
<table border="0" cellspacing="0" cellpadding="0">

<?php echo $inner; ?>
</table>

And the code that makes it happen:


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 layout
$this->loadLayout('standard');

// now we insert the wrapper template between the decorator and the
actual content layer
$i = $this->appendLayer($this->createLayer
('AgaviFileTemplateLayer', 'wrapper'), $this->getLayer('content'));
$i->setTemplate('LiveSuccess.wrapper');
}


Cheers,

David

Van Daele, Koen

unread,
Jan 30, 2007, 11:18:15 AM1/30/07
to Agavi Users Mailing List
Hi,

The new layout stuff works really well. I've just converted my app to using the layouts, took a bit of getting used to it, but it wasn't to difficult. And now I don't need to set up the decorator manually in the base view anymore. I've made the default view have a method like this:

public function executeHtml(AgaviRequestDataHolder $rd)
{
$this->setupHtml($rd);
}

For simple views that just serve up a template without doing anyting else this means I don't even need to override the executeHtml() method anymore.

I have set up the setupHtml method so that it takes as an optional parameter the name of a layout, that way I can do setupHtml($rd, 'notDecorated') to have a view that renders without decoration (e.g. a view that needs to be inserted in some other slot) while the setupHtml($rd) automatically decorates with the standard site template. And I have a third layout that just wraps some simple layout around a template for pages like Unavailable.php.

The setting of slots for an inner layer is also very helpful. Something I've been waiting for a long time.

I haven't encountered any problems so far. One thing I do keep on running into is agavi telling me that a config file is not valid, but only giving back a very general error-message (Element Content is invalid) that doesn't really help in debugging. Maybe this is only a problem now because the config-file format has changed a few times. Is it possible to have more specific errors? It might also be useful to add the different parameters to xsd for validation since right now you don't get a warning if you've used a wrong parameter.

Anyway, thanks again for all the hard work,
Koen

> -----Oorspronkelijk bericht-----
> Van: users-...@lists.agavi.org
> [mailto:users-...@lists.agavi.org] Namens David Zülke

> Verzonden: dinsdag 30 januari 2007 10:32
> Aan: Agavi Users Mailing List
> CC: Agavi Dev Mailing List
> Onderwerp: Re: [Agavi-Users] Advanced Layout/Layers example
> (was: Re:IMPORTANT: Breaking changes in 0.11 branch: tons of
> new features!)

Reply all
Reply to author
Forward
0 new messages