Author image Andrew Payne
and 1 contributors


CatalystX::ListFramework - foundations for displaying and editing lists (CRUD) in a Catalyst application


    package MyApp::Controller::Foo;
    use base 'Catalyst::Controller';
    use CatalystX::ListFramework;
    sub listandsearch :Local {
        my ($self, $c, $kind) = @_;
        my $lf = CatalystX::ListFramework->new($kind, $c);
        my $restrict = {};
        $lf->stash_listing('myview', 'myprefix', $restrict);
        $c->stash->{template} = '';

    sub get :Local {
        my ($self, $c, $kind, $id) = @_;
        my $lf = CatalystX::ListFramework->new($kind, $c);
        $lf->stash_infoboxes({'' => $id}); 
        $c->stash->{kind} = $kind;
        $c->stash->{id} = $id;  # the update form adds this to the URL
        $c->stash->{template} = '';
    sub update :Local {
        my ($self, $c, $kind, $id) = @_;
        my $lf = CatalystX::ListFramework->new($kind, $c);
        $lf->update_from_query({'' => $id}); 
    sub create :Local {
        my ($self, $c, $kind) = @_;
        my $lf = CatalystX::ListFramework->new($kind, $c);
        my $id = $lf->create_new; 


Displaying tabulated lists of database records, updating those records and creating new ones is a common task in Catalyst applications. This class supplies such lists, and forms to edit such records, to a set of templates, using simple definition files and your DBIx::Class Catalyst model. A search form is also supplied, which can include JSON-powered ExtJS comboboxes (see

To run the included demo application, grab a copy of ExtJS, then

    cd t/
    ln -s /path/to/extjs/ static/extjs-1.1


    firefox http://localhost:3000/start

Please see BUGS about some SQLite issues with the demo app. The noninteractive test suite is



ListFramework is driven by a set of definition files, found under formdef/, one pair per schema class (table). These are divided into 'master' files and 'site' files and are named kind.form. Files under master/ describe a kind's source, what fields it has available, and how it is associated with other schema classes. Files under site/ describe how the data is displayed on the page. This division, and the naming, implies that a vendor (you) could supply the master files, while a particular installation could use customised site files to suit their needs.

These are best understood by looking at the example files.

Files under /master

The sections in these files are:


A title, displayed on various screens.


The DBIx::Class model to use.


This is a hashref linking this schema to others, in the form field => 'kind'. In site files, you can then use field. (or several ones nested) to access the foreign schema, for example "". field must be listed in the schema as a column and have a matching belongs_to relationship.


This hashref species what columns the schema makes available and provides some metadata, such as column headings, default values and types.

The 'field' property may be an arrayref, all elements of which are concatenated for display. Static text can be specified using a scalar ref, and you can call functions from the Helper class by specifying function(field). For example,

    field => [ \'(', uc(surname), \')' ]

If a 'type' field is specified', then a coresponding filter function in ::Helper::Types is called.

There should be a special entry, OBJECT => {primary_key => 'id'}, which specifies the table's primary key (and has other uses). Referencing OBJECT in a site file gives you the serialisation of the object, which is useful if you've overloaded "", as in:

    package TestApp::Model::TestModel::Artist;
    use overload '""' => sub {
        my $self = shift;
        return $self->artist_forename . ' ' . $self->artist_surname;

These are simply read by the template. If they're specified, then links are generated on the page.


This is a hashref of searches which are made available by this schema, e.g.

    albtitle => {heading=>'Album title', field=>'title', op=>'like'}

If 'op' is set to 'like', the user's input is automatically surrounded by '%'s. The other usual choice is '='.

Files under /site

The sections in these files are:


This is a hashref of views, each of which is an arrayref of columns to show in a list.

    default => [
                  {id=>'tid', heading => 'Track Code', uri => '/get/track/'},

'id' can refer to foreign fields through the dot notation. If 'uri' is specified, then the value of the entry's primary key is appended and the column is shown as a link.

This is an arrayref of fields you want the search form to present (in order).

    {id=>'fromalbum.albid', autocomplete=>['' => 'fromalbum.title'], minchars=>'0'}

The 'autocomplete' parameter takes 2 arguments in an arrayref: a hidden field (sent when a search form is submitted) and a field to be displayed to the user in a dropdown list. Here, album titles are shown, but album IDs are sent by the form. The user can find an album by typing a substring.

You can also override the 'heading' parameter from the 'site' .form file.


The detail view for an entry is split into 'boxes', which are rendered as ExtJS tabs in the demo app. You can specify local or foreign schema fields by the 'id' property, and headings etc can be overridden as usual. All fields are editable, unless 'not_editable' is true or the field is more complicated than just a table column. If '.OBJECT' is specified, a dropdown list of choices is presented.

        track => [
            {id => 'ttitle', not_editable => 1},
            {id => 'fromalbum.artist.OBJECT', heading=>'Who by'},


Documentation TODO


Documentation TODO


Probably many, and many areas are in need of further work. See all the TODO tags within the code. Please feel free to email me with comments or patches.

Something somewhere between SQLite and DBIx::Class is buggy when it comes to row updates. You will see TT error screens on the demo app about "too many rows updated". This goes away if you refresh and is fine with a real DB like MySQL.


Andrew Payne <>.


This module is Copyright (C) 2007 Dragonstaff Ltd and is licensed under the same terms as Perl itself.