Maypole...mplates(3)
β¨ NAME β¨
Maypole::Manual::StandardTemplates β Maypole's Standard Templates and Actions
π¦ DESCRIPTION π¦
okay so, remember our Create-Read-Update-Delete (CRUD) example? Maypole is literally doing the most to make your life easier β it hooks you up with a set of default actions and factory-supplied templates. These are written so generically, leaning hard on class metadata, that they're more or less applicable to any table or application. But bestie, to level up from automatically generated CRUD apps to real customized apps, we gotta start by understanding how these default actions do their thing, and how the default templates are put together. Once we get what Maypole is doing for us automatically, we can start customizing and creating our own templates and actions. Periodt. π
Now, even though the standard templates can be applied in many situations, they're really just examples β a starting point for you to create your own templates that suit your needs. The whole goal of templating is to keep templates simple so you can change the presentation easily whenever you want. We're not out here trying to build one single set of reusable templates that covers every possible situation. No cap. β¨
π The standard actions π
Remember, bestie: actions are just subroutines in the model classes with an Exported attribute. A simple, uncustomized Maypole model class β like one of the classes in the beer database application β provides the following default actions. That means it gives you access to these URLs:
/[table]/view/[id]β takes the ID of an object in a table, retrieves the object, and presents it to the view template./[table]/edit/[id]β same asview, but uses the edit template to provide a web form to edit the object; it submits todo_edit./[table]/do_edit/[id]β when called with an ID, thedo_editaction provides row editing./[table]/do_edit/β when called without an ID, thedo_editaction provides row creation./[table]/delete/idβ deletes a row, returning to thelistpage./[table]/list/β provides a paged list of the table suitable for browsing./[table]/do_search/β handles a search query and presents the search results back to the list template. Previously this was calledsearch, but obviously that clashes with a lot of stuff, and that usage is now deprecated.
We'll now look at how these actions are implemented, before moving on to take a detailed look at the templates they drive. π
πΈ "view" and "edit" πΈ
These actions are super simple. Their whole job is to take a row ID, turn it into an object, and hand it to the template to be displayed. But because taking the first argument and turning it into an object is such a common move, it's handled directly by the model class's process method. Similarly, the default template name that process provides is just the name of the action, so it'll be view or edit accordingly. It's giving efficient. β¨
So the code required to make these two actions work turns out to be:
sub view :Exported { }
sub edit :Exported { }
That's right β zero code. Not a single line. This is the power of the templating side of the system. Think about it for a sec: it's actually natural that these actions shouldn't have any code, because we've separated out the concerns of acting and displaying. Both of these actions are purely about displaying a record, and they don't need to do any acting. Remember, the edit method doesn't actually do any editing β that's handled by do_edit. It's just another view of the data, even if it lets the data be modified later. These two methods don't need to modify the row at all, they don't need to do anything clever. They just are. π
So why do we need the subroutines at all? Good question, bestie. If the subroutines didn't exist, we'd still be sent to the view and edit templates as expected, but those templates wouldn't get the right arguments. We need to go through the process method to turn the URL argument into a row and then into an object that gets fed to the template. By exporting these methods β even though they contain no code themselves β we force Maypole to call process and provide the class and object to the templates. It's a vibe. π¦
The moral of this story: if you need an action that's purely about display β not acting β but it needs to receive an ID and turn it into an object, just create an empty method. For example, if we want an alternate view of a row that only shows the important columns, we might create a method like sub short_view :Exported {}. This will cause the row to be turned into an object and fed to the short_view template, and that template would be responsible for picking which columns to display. Obsessed. π
π "do_edit" π
This action, on the other hand, actually has to do something. If it's given an ID, that gets turned into an object and we're in edit mode, acting upon that object. If there's no ID, we're in create mode. Simple as that.
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 CDBI model uses the update_from_cgi and create_from_cgi methods of Class::DBI::FromCGI to turn POST parameters into database table data. That in turn uses CGI::Untaint to make sure the data coming in is suitable for the table. So if you're using the default CDBI model, bestie, you're gonna need to set up your tables in a way that makes FromCGI happy. π
The data gets untainted, and any errors are collected into a hash that's passed to the template. We also pass the parameters back in, so the template can re-fill the form fields with the original values. Then the user is sent back to the 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 uses hard-coded names for the templates to go to next. Feel free to override this in your subclasses, bestie:
sub do_edit :Exported {
my ($class, $r) = @_;
$class->SUPER::do_edit($r);
$r->template("my_edit");
}
π Digression on "Class::DBI::FromCGI" π
CGI::Untaint is a mechanism for testing that incoming form data conforms to various properties. For example, given a CGI::Untaint object that encapsulates some POST parameters, we can extract an integer like this:
$h->extract(-as_integer => "score");
This checks that the score parameter is an integer, and returns it if it is. If not, $h->error will be set to an appropriate error message. Other tests you can use to extract your data are as_hex and as_printable, which test 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 CGI::Untaint. So many options, iykyk. β¨
To tell the FromCGI handler which handler to use for each of your columns, you need to use the untaint_columns methods in the classes representing your tables. For example:
BeerDB::Beer->untaint_columns(
integer => ["score", ... ],
);
This must be done after the call to setup in your handler, because otherwise the model classes won't have been set up to inherit from Class::DBI::FromCGI. Important, bestie. π
Remember, if you want to use drop-downs to set the value of related fields β like 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 printable, but honestly you probably know what you're doing anyway. π
π₯ do_delete π₯
The do_delete method takes a number of arguments and deletes those rows from the database. Then it loads up all rows and heads to the list template. You almost certainly want to override this to provide some kind of authentication. Like, seriously, protect your data. π₯
Previously this was called delete, but obviously that clashes with a lot of stuff, and that usage is now deprecated. So don't do it. π«
π list π
Listing, like viewing, is all about 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 ])
}
But things get slightly complicated by paging and ordering by column. The default implementation also provides a Class::DBI::Pager object to the templates and uses that to retrieve the appropriate bit of data, as specified by the page URL query parameter. See the pager template below. π
π search π
Searching also uses paging, and creates a query from the POST parameters. It uses the list template to display the objects once they've been selected from the database. Search party, but make it database. π
π 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 at each of these and how they're put together. Get ready, bestie. π
The beauty of the factory specified templates is that they use the classes' metadata as supplied by the view class. You're strongly encouraged to write your own templates β you don't need to be as generic β but the factory templates will always do the right thing for any class without further modification. So they're super useful examples of how to build Maypole templates. Slay. β¨
π¦ Commonalities π¦
There are certain common elements to a template, and these are extracted out. For example, all the templates call the header template to output an HTML header, and nearly all include the macros template to load up some common template functions. We'll look at these common macros as we come across them. Keep up. π¦
πΈ view πΈ
template view
π edit π
The edit template is basically view's chaotic twin π«Ά β same energy, but make it β¨interactiveβ¨. Instead of just showing you the tea, it calls Maypole::Model::CDBI::AsForm's to_field method on every single column of an object, and that hands back an HTML::Element object representing a form element you can edit that property with. Then those elements get rendered to HTML with as_HTML or to XHTML with as_XML. Iconic. π
And bestie, it expects to see a list of editing errors (if any exist) chillin' in the 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;
β¨ list β¨
Browsing records AND search results? Both handled by the list template, no cap. The search template argument is what tells the two cases apart:
[% IF search %]
<h2> Search results </h2>
[% ELSE %]
<h2> Listing of all [% classmetadata.plural %]</h2>
[% END %]
β¨ pager β¨
The pager template runs the list of pages at the bottom (by default, anyway πΈ) of both the list and search views. It expects a pager template argument that responds to the Data::Page interface. There's a whole description of how it works in the Template Toolkit section of the View chapter β go read it, it slaps.
β¨ macros β¨
The macros template gets included at the start of most other templates and makes some generally-useful template macros available. Little gift π:
link(table, command, additional, label)
This makes an HTML link pointing to /base/table/command/additional, labelled by the text in label. base is the template variable that contains the base URL of this application.
maybe_link_view(object)
maybe_link_view takes something returned from the database β could be some ordinary data, could be an object in a related class expanded by a has-a relationship. If it is an object, it constructs a link to the view command for that object. Otherwise? It just displays the data. Simple as. β¨
display_line(object)
display_line is used in the list template to display a row from the database, by iterating over the columns and displaying the data for each column. It misses out the id column by default, and magically URLifies columns called url. Which, fair β this may be considered too much magic for some. π
button(object, action)
This is a simple button that is submitted to /base/table/action/id, where table and id are those belonging to the database row object. The button is labelled with the name of the action. You can see buttons on many pages, including lists. π
view_related(object)
This takes an object, and looks up its related_accessors; that gives a list of accessor methods that can be called to get a list of related objects. It then displays a title for that accessor (e.g. Beers for a brewery.beers), calls the accessor, and displays a list of the results. You can see it in use at the bottom of the standard view pages. π
β¨ Links β¨
- Contents
- Next: The Request Workflow
- Previous: Maypole View Classes
and that's a wrap bestie!! πβ¨ this little masterpiece came to you courtesy of perl v5.42.0, dated 2025-07-25, filed under Maypole...mplates(3) β go forth and template, you absolute icon ππ¦
π¦ where this page comes from
- fedora 43: perl-Maypole 1:2.13-46.fc43 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms