Sebastian Riedel wrote:
> the current Manual should be quite up to date,
It doesn't seem to have changed much from the 1.7 version. There are
still complete empty sections at the end of chapters where it should
explain enough about how it works to let people customise it. The list
of "What Maypole provides to a template" still has errors that I've
previously reported. The whole subsection describing the view template
has disappeared from the Standard Templates chapter (I've attached the
1.7 version of the chapter that includes the missing text). BTW where it
says 'as_HTML' it should now say 'as_XML'.
> but we need feedback and patches to improve it!
I submit patches whenever I see anything and I'm writing a section about
initialisation. But it's very slow to write docs for something you don't
understand! It's much more effective if those people who already know
what it does write the core docs.
>>> - support for multiple models (we definately need this)
> Simple, you can currently only have one model.
> If you want LDAP and CDBI it doesn't work.
>
> And Maypole::Model::Base is much too CDBI oriented.
OK, that's clear now, thanks! And I agree :)
>>In general, provision of hooks to let people more easily customise
>>things. For example, I posted some code to generate correct singular and
>>plural text for tables and columns. The new release broke my hack that
>>runs this code so I've had to spend time making a new hack to run the
>>code, but I haven't had any feedback on whether there are better hooks
>>to use or better ways to do it, or whether people believe text
>>generation belongs in th emodel or the view.
>>Also, of course, I'd like to see proper text generation in Maypole :)
> Dunno what you're talking about, sorry.
I described it in my mail "table names, column names & other text for
display"
<http://lists.netthink.co.uk/pipermail/maypole/2004-October/000895.html>
Cheers, Dave
=head1 Maypole's Standard Templates and Actions
As we saw in our CRUD example, Maypole does all it can to make your life
easier; this inclues providing a set of default actions and
factory-supplied templates. These are written in such a generic way,
making extensive use of class metadata, that they are more or less
applicable to any table or application. However, in order to progress
from automatically generated CRUD applications to real customized
applications, we need to begin by understanding how these default
actions do their stuff, and how the default templates are put together.
Once we have an understanding of what Maypole does for us automatically,
we can begin to customize and create our own templates and actions.
=head2 The standard actions
A simple, uncustomized Maypole model class, such as one of the classes
in the beer database application, provides the following default actions
- that is, provides access to the following URLs:
=over 3
=item C</[table]/view/[id]>
This takes the ID of an object in a table, retrieves the object, and
presents it to the F<view> template.
=item C</[table]/edit/[id]>
This is the same as C<view>, but uses the F<edit> template to provide a
web form to edit the object; it submits to C<do_edit>.
=item C</[table]/do_edit/[id]>
=item C</[table]/do_edit/>
This provides both editing and row creation facilities.
=item C</[table]/delete/id>
This deletes a row, returning to the C<list> page.
=item C</[table]/list/>
This provides a paged list of the table suitable for browsing.
=item C</[table]/search/>
This handles a search query and presents the search results back to the
F<list> template.
=back
We'll now look at how these actions are implemented, before moving on to
take a detailed look at the templates they drive.
=head3 C<view> and C<edit>
These actions are very simple; their job is to take a row ID, turn it
into an object, and hand it to the template to be displayed. However, as
taking the first argument and turning it into an object is such a common
action, it is handled directly by the model class's C<process> method.
Similarly, the default template name provided by the C<process> method
is the name of the acction, and so will be C<view> or C<edit>
accordingly.
So the code required to make these two actions work turns out to be:
sub view :Exported { }
sub edit :Exported { }
That's right - no code at all. This shows the power of the templating
side of the system. If you think about it for a moment, it is natural
that these actions should not have any code - after all, we have
separated out the concerns of "acting" and displaying. Both of these
"actions" are purely concerned with displaying a record, and don't need
to do any "acting". Remember that the "edit" method doesn't actually do
any editing - this is provided by C<do_edit>; it is just another view of
the data, albeit once which allows the data to be modified later. These
two methods don't need to modify the row in any way, they don't need to
do anything clever. They just are.
So why do we need the subroutines at all? If the subroutines did not exist,
we would be sent to the C<view> and C<edit> templates as would be
expected, but these templates would not be provided with the right
arguments; we need to go through the C<process> method in order to turn
the URL argument into a row and thence into an object to be fed to the
template. By exporting these methods, even though they contain no code
themselves, we force Maypole to call C<process> and provide the class
and object to the templates.
The moral of this story is that if you need to have an action which is
purely concerned with display, not acting, but needs to receive an ID
and turn it into an object, then create an empty method. For instance,
if we want to make an alternate view of a row which only showed the
important columns, we might create a method
sub short_view :Exported {}
This will cause the row to be turned into an object and fed to the
C<short_view> template, and that template would be responsible for
selecting the particular columns to be displayed.
=head3 C<do_edit>
This action, on the other hand, actually has to do something. If it's
provided with an ID, this is turned into an object and we're in edit
mode, acting upon that object. If not, we're in create mode.
sub do_edit :Exported {
my ($self, $r) = @_;
my $h = CGI::Untaint->new(%{$r->{params}});
my ($obj) = @{$r->objects || []};
if ($obj) {
# We have something to edit
$obj->update_from_cgi($h);
} else {
$obj = $self->create_from_cgi($h);
}
The C<CDBI> model uses L<Class::DBI::FromCGI> to turn C<POST> parameters
into database table data. This in turn uses C<CGI::Untaint> to ensure
that the data coming in is suitable for the table. If you're using the
default C<CDBI> model, then, you're going to need to set up your tables
in a way that makes C<FromCGI> happy.
=over
=item Digression on C<Class::DBI::FromCGI>
C<CGI::Untaint> is a mechanism for testing that incoming form data
conforms to various properties. For instance, given a C<CGI::Untaint>
object that encapsulates some C<POST> parameters, we can extract an
integer like so:
$h->extract(-as_integer => "score");
This checks that the C<score> parameter is an integer, and returns it if
it is; if not, C<< $h->error >> will be set to an appropriate error
message. Other tests by which you can extract your data are C<as_hex>
and C<as_printable>, which tests for a valid hex number and an ordinary
printable string respectively; there are other handlers available on
CPAN, and you can make your own, as documented in L<CGI::Untaint>.
To tell the C<FromCGI> handler what handler to use for each of your
columns, you need to use the C<untaint_columns> methods in the classes
representing your tables. For instance:
BeerDB::Beer->untaint_columns(
integer => ["score", ... ],
);
This must be done after the call to C<setup> in your handler, because
otherwise the model classes won't have been set up to inherit from
C<Class::DBI::FromCGI>.
Remember that if you want to use drop-downs to set the value of related
fields, such as the brewery for a beer, you need to untaint these as
something acceptable for the primary key of that table:
BeerDB::Beer->untaint_columns(
integer => ["score", "brewery", "style" ],
...
);
This is usually integer, if you're using numeric IDs for your primary
key. If not, you probably want C<printable>, but you probably know what
you're doing anyway.
=back
The data is untainted, and any errors are collected into a hash which is
passed to the template. We also pass back in the parameters, so that the
template can re-fill the form fields with the original values. The user
is then sent back to the C<edit> template.
if (my %errors = $obj->cgi_update_errors) {
# Set it up as it was:
$r->{template_args}{cgi_params} = $r->{params};
$r->{template_args}{errors} = \%errors;
$r->{template} = "edit";
}
Otherwise, the user is taken back to viewing the new object:
} else {
$r->{template} = "view";
}
$r->objects([ $obj ]);
Notice that this does use hard-coded names for the templates to go to next.
Feel free to override this in your subclasses:
sub do_edit :Exported {
my ($class, $r) = @_;
$class->SUPER::do_edit($r);
$r->template("my_edit");
}
=head3 delete
The delete method takes a number of arguments and deletes those rows from the
database; it then loads up all rows and heads to the F<list> template.
You almost certainly want to override this to provide some kind of
authentication.
=head3 list
Listing, like viewing, is a matter of selecting objects for
presentation. This time, instead of a single object specified in the
URL, we want, by default, all the records in the table:
sub list :Exported {
my ($class, $r) = @_;
$r->objects([ $self->retrieve_all ])
}
However, things are slightly complicated by paging and ordering by
column; the default implementation also provides a C<Class::DBI::Pager>
object to the templates and uses that to retrieve the appropriate bit of
the data, as specified by the C<page> URL query parameter. See the F<pager>
template below.
=head3 search
Searching also uses paging, and creates a query from the C<POST>
parameters. It uses the F<list> template to display the objects once
they've been selected from the database.
=head2 The templates and macros
Once these actions have done their work, they hand a set of objects to
the templates; if you haven't specified your own custom template
globally or for a given class, you'll be using the factory specified
template. Let's take a look now at each of these and how they're put
together.
The beauty of the factory specified templates is that they make use of
the classes' metadata as supplied by the view class. Although you're
strongly encouraged to write your own templates, in which you don't need
to necessarily be as generic, the factory templates will always do the
right thing for any class without further modification, and as such are
useful examples of how to build Maypole templates.
=head3 Commonalities
There are certain common elements to a template, and these are extracted
out. For instance, all the templates call the F<header> template to
output a HTML header, and nearly all include the F<macros> template to
load up some common template functions. We'll look at these common
macros as we come across them.
=head3 F<view>
The C<view> template takes some objects (usually just one) from
C<DEFANGED_objects> and displays the object's properties in a table.
It gets the displayable form of a column's name from the hash returned
from the C<column_names> method:
<TR>
<TD class="field"> [% classmetadata.colnames.$col; %] </TD>
One interesting macro used in this template is C<maybe_link_view>:
maybe_link_view(item.$col);
%]
This tests whether or not the returned value is an object, and if so,
creates a link to a page viewing that object; if not, it just displays
the text as normal. The object is linked using its stringified name;
by default this calls the C<name> method, or returns the object's ID
if there is no C<name> method or other stringification method defined.
The C<view> template also displays a list of other objects related to the first
one via C<has_many> style relationships; this is done by calling the
C<related_accessors> method - see L<Model/related_accessors> - to return
a list of has-many accessors. Next it calls each of those accessors, and
displays the results in a table.
view_related(item);
=head3 F<edit>
The F<edit> template is pretty much the same as F<view>, but it uses the
C<to_field> method on each column of an object to return a C<HTML::Element>
object representing a form element to edit that property. These elements
are then rendered to HTML with C<as_HTML>. It expects to see a list of
editing errors, if any, in the C<errors> template variable:
FOR col = classmetadata.columns;
NEXT IF col == "id";
"<P>";
"<B>"; classmetadata.colnames.$col; "</B>";
": ";
item.to_field(col).as_HTML;
"</P>";
IF errors.$col;
"<FONT COLOR=\"#ff0000\">"; errors.$col; "</FONT>";
END;
END;
=head3 F<list>
Browsing records and search results are both handled by the F<list> template.
The C<search> template argument is used to distinguish between the two cases:
[% IF search %]
<h2> Search results </h2>
[% ELSE %]
<h2> Listing of all [% classmetadata.plural %]</h2>
[% END %]
=head1 Customizing Generic CRUD Applications
_______________________________________________
maypole mailing list
maypole at lists.netthink.co.uk
http://lists.netthink.co.uk/listinfo/maypole
This archive was generated by hypermail 2.1.3 : Thu Feb 24 2005 - 22:25:56 GMT