NAME

App::ZofCMS::Plugin::CRUD - Generic "Create Read Update Delete List" functionality

SYNOPSIS

In your ZofCMS Template:

plugins => [
    qw/CRUD/,
],

plug_crud => {
    table       => 'information_packages',
    file_dir    => 'files/information-packages/',
    items       => [
        'Item',
        { Description => [ 'textarea', optional => 1 ] },
        { File        => 'file'                        },
        { Time        => sub { time(); }               },
    ],
},

Create a SQL table for the plugin to use:

CREATE TABLE `information_packages` (
    `crud_id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    `item` TEXT,
    `description` TEXT,
    `file` TEXT,
    `time` INT(10) UNSIGNED
);

In your HTML::Template Template:

<h2>Information Packages Admin</h2>

<tmpl_var name='crud_success_message'>
<tmpl_var name='crud_error'>

<h3>Add New Item</h3>

<tmpl_var name='crud_form'>

<h3>Items In The Database</h3>

<tmpl_if name='crud_has_items'>
    <table>
    <thead>
        <tr>
            <td>File</td>
            <td>Description</td>
            <td>Add Time</td>
        </tr>
    </thead>
    <tbody>
        <tmpl_loop name='crud_items'>
            <tr>
                <td>
                <tmpl_var name='crud_d_form'>
                <tmpl_var name='crud_u_form'>
                <a href="<tmpl_var escape='html' name='file'>"
                    target="_blank"><tmpl_var
                    escape='html' name='item'></a></td>
                <td><tmpl_var name='description'></td>
                <td><tmpl_var name='foo1'></td>
                <td><tmpl_var name='foo2'></td>
                <td><tmpl_var escape='html' name='time'></td>
            </tr>
        </tmpl_loop>
    </tbody>
    </table>
<tmpl_else>
    <p>Currently there are no items in the database.</p>
</tmpl_if>

DESCRIPTION

The plugin provides a generic "Create Read Update Delete List" functionality. (Currently, READ is not implemented). In conjunction with this plugin, you might find these plugins useful App::ZofCMS::Plugin::DBIPPT and App::ZofCMS::Plugin::FormChecker.

ZofCMS TEMPLATE/MAIN CONFIG FILE FIRST LEVEL KEYS

The keys can be set either in ZofCMS template or in Main Config file, if same keys are set in both, then the one in ZofCMS template takes precedence.

plugins

plugins => [ qw/CRUD/ ],

You obviously would want to include the plugin in the list of plugins to execute.

plug_crud

### Mandatory fields without defaults: dsn, user, items

plug_crud => {
    dsn             => 'DBI:mysql:database=zofdb;host=localhost',
    user            => 'db_login',
    pass            => 'db_pass',
    opt             => {
        RaiseError        => 1,
        AutoCommit        => 1, 
        mysql_enable_utf8 => 1,
    },
    items       => [
        'Item',
        { Description => [ 'textarea', optional => 1 ] },
        { File        => 'file'                        },
        { Time        => sub { time(); }               },
    ],
    list_sub        => # sub for processing the list of output items
    table           => 'products',
    file_dir        => 'files/',
    can             => 'CRUDL',
    no_time_sort    => 0,
}

Mandatory. Takes either a hashref or a subref as a value. If a subref is specified, its return value will be assigned to plug_crud as if it were already there. If the sub returns an undef, then the plugin will stop further processing. The @_ of the subref will contain (in that order): ZofCMS Template hashref, query parameters hashref, and App::ZofCMS::Config object. Possible keys/values for the hashref are as follows:

dsn

dsn => "DBI:mysql:database=test;host=localhost",

Mandatory. Specifies the "DSN" for DBI module. See DBI's docs for connect_cached() method for more info on this one.

user

user => 'test',

Mandatory. Specifies your username for the SQL database.

pass

pass => 'test',

Optional. Specifies your password for the SQL database. If not specified, behaviour will be akin to not having a set password.

opt

opt => {
    RaiseError => 1,
    AutoCommit => 1, 
    mysql_enable_utf8 => 1,
}

Optional. Takes a hashref as a value. Specifies the additional options for DBI's connect_cached() method. See DBI's docs for connect_cached() method for more info on this one. Defaults to: { RaiseError => 1, AutoCommit => 1, mysql_enable_utf8 => 1, }

table

table => 'products',

Optional. Takes a string as a value that represents the name of the table in which to store the data. Defaults to: products

items

items => [
    'Item',
    { Description => [ 'textarea', optional => 1 ] },
    { File        => 'file'                        },
    { Time        => sub { time(); }               },
],

Mandatory. Takes an arrayref as a value that must contain at least one item in it. Specifies the list of items that comprise a single record that the plugin will create/read/update/delete. Important note: if you're not using time item to store time, see no_time_sort option below; you'll likely want it set to a true value, to avoid the plugin from erroring out. The items in a list can be of these types:

String

items => [
    'Item',
    q|Employee's Performance Record #|,
],

A simple string will be represented as a <input type="text"> in the Create/Update HTML form. The string will become the <label> text for the form element. Then everything in it that doesn't match \w will be converted into underscores, and lc() will be run, and that new string will be used for:

  • SQL column name for that field

  • HTML <input>'s name="" attribute in the Create/Update form

  • HTML id="" attribute in the Create/Update form (prefix crud_ will be added)

  • <tmpl_var> name for the List function of the plugin.

An example:

We have two items in our record that we'll manipulate with this plugin:

items => [
    'Item',
    q|Employee's Performance Record #|,
],

We'll create a table, where field `crud_id` is mandatory and must contain the unique ID of the record (this example shows MySQL taking care of that automatically):

CREATE TABLE `example` (
    `crud_id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    `item` TEXT,
    `employee_s_performance_record__` TEXT,
);

Let's say we have App::ZofCMS::Plugin::FormChecker checking the input given to the Create form. The trigger will be crud_create and the fields it will be checking are item and employee_s_performance_record__:

plug_form_checker => {
    trigger     => 'crud_create',
    fail_code   => sub { my $t = shift; delete $t->{plug_crud} },
    rules       => {
        item                            => { max => 200 },
        employee_s_performance_record__ => 'num',
    },
},

Lastly, when displaying the list of records, we'll use this code in the HTML::Template template:

<tmpl_if name='crud_has_items'>
    <table>
    <thead>
        <tr>
            <td>Item</td>
            <td>Employee's Performance Record #</td>
        </tr>
    </thead>
    <tbody>
        <tmpl_loop name='crud_items'>
            <tr>
                <td><tmpl_var escape='html' name='item'></td>
                <td><tmpl_var escape='html'
                    name='employee_s_performance_record__'></td>
            </tr>
        </tmpl_loop>
    </tbody>
    </table>
<tmpl_else>
    <p>Currently there are no items in the database.</p>
</tmpl_if>

hashref

items => [
    { File        => 'file'                        },
    { Description => [ 'textarea', optional => 1 ] },
    { Time        => sub { time(); }               },
]

The key of the hashref functions the same as described above for String (i.e. it'll be the <label> and the base for the name="", etc.). The value of the hashref can be a string, an arrayref, or a subref:

A string

items => [
    { File => 'file' },
],

When the value is a string, it will specify the type of HTML element to use for this record item in the Create/Update form. Currently supported values are text for <input type="text">, textarea for <textarea>, and file for <input type="file">. File inputs are currently not editable in the Update form.

An arrayref

items => [
    { Description => [ 'textarea', optional => 1 ] },
]

The first item in the arrayref will be the type of HTML element to use for this record item (see A string section right above). The rest of the items are in key/value pairs and specify options for this record. The currently available option is optional that, when set to a true value, will cause the item to be optional: an asterisk will not be prepended to its label and an error will not show if the user leaves this item blank in the Create/Update form.

A subref

items => [
    { Time => sub { time(); } },
]

The key will function the same as described for A string above. The only difference is that this item will not be shown in the Create/Update form. The sub will be executed and its value will be assigned to the item as if it were specified by the user. On Update, this item will not be editable and the sub will NOT be executed. The @_ of the sub will contain (in that order): ZofCMS Template hashref and query parameters hashref where keys are params' names and values are their values.

list_sub

list_sub => sub {
    my ( $d, $t, $q ) = @_;
    
    for ( @$d ) {
        $_->{time} = localtime $_->{time};
        $_->{is_good_employee}
        = $t->{d}{records}{ $_->{employee_s_performance_record__} }
            ? 1 : 0;

        $_->{sort} = $q->{sort};
    }
},

Optional. By default is not specified. Takes a subref as a value. Allows you to make modifications to the list of records in the List output. The @_ of the sub will contain (in that order): the arrayref of hashrefs where each hashref is a record in the database, ZofCMS Template hashref, and query parameters hashref where keys are params' names and values are their values. The hashref for each record will contain all the columns from the database for that specific record, as well as:

  • crud_can_ud => 1 if the user can either update or delete records (see can parameter below),

  • crud_can_u => 1 if the user can update records

  • crud_can_d => 1 if the user can delete records

  • crud_d_form containing HTML of the form used for deleting this record

  • crud_u_form containing HTML of the form used for updating this record

file_dir

file_dir => 'files',

Optional. Defaults to files. Takes a string as a value that specifies the directory (relative to index.pl) where the plugin will store files uploaded by the user (that is for any records for which <input type="file"> was used in the Create form).

can

can => 'CRUDL',

can => 'RL',

can => 'CUL',

Optional. Defaults to CRUDL. Takes a string of letters (in any order) as the value. Each letter specifies what the current user is allowed to do: C => Create, R => Read, U => Update, D => Delete, L => List. Note: if L is specified, plugin will automatically load all records into {t}{crud_list}.

no_time_sort

no_time_sort => 0,

Optional. Takes true or false value. Defaults to false. In 99% of my CRUD pages, I've had a time item in the record that stored the time of when the record was added, and when the records were listed they were sorted by "most recent first." This is exactly what this plugin does automatically and it expects time item to be present and set to a value of Unix time (output of time()). If you don't have such a column or don't want your records sorted by time, set no_time_sort to a true value.

HTML::Template TEMPLATE VARIABLES

<tmpl_var name='crud_success_message'>
<tmpl_var name='crud_error'>

<h3>Add New Item</h3>

<tmpl_var name='crud_form'>

<h3>Items In The Database</h3>

<tmpl_if name='crud_has_items'>
    <table>
    <thead>
        <tr>
            <td>File</td>
            <td>Description</td>
            <td>Add Time</td>
        </tr>
    </thead>
    <tbody>
        <tmpl_loop name='crud_items'>
            <tr>
                <td>
                <tmpl_var name='crud_d_form'>
                <tmpl_var name='crud_u_form'>
                <a href="<tmpl_var escape='html' name='file'>"
                    ><tmpl_var escape='html' name='item'></a></td>
                <td><tmpl_var name='description'></td>
                <td><tmpl_var name='foo1'></td>
                <td><tmpl_var name='foo2'></td>
                <td><tmpl_var escape='html' name='time'></td>
            </tr>
        </tmpl_loop>
    </tbody>
    </table>
<tmpl_else>
    <p>Currently there are no items in the database.</p>
</tmpl_if>

crud_success_message

<tmpl_var name='crud_success_message'>

This variable will contain <p class="success-message">Item was successfully deleted.</p> when Delete action succeeds.

crud_error

<tmpl_var name='crud_error'>

This variable will contain an error message, if any, except for messages generated during submission of the Create/Update forms, as those will be stuffed inside the form, but same HTML code will be wrapping the error message. The code will be <p class="error">$error</p> where $error is the text of the error, which currently will be either Couldn't find the item to update or Couldn't find the item to delete.

crud_form

<tmpl_var name='crud_form'>

This variable will contain either Create or Update form, depending on whether is the user is trying to update a record. See the source code of this module or the output of crud_form to find HTML code for the form. This variable will be empty if the user doesn't have Create or Update permissions (see can configuration variable).

crud_has_items

<tmpl_if name='crud_has_items'>
    ... output the record list here
<tmpl_else>
    <p>Currently there are no items in the database.</p>
</tmpl_if>

Contains true or false values. If true, it means the plugin retrieved at least one record with the List operation. This variable will always be false if the user isn't allowed to List (see can configuration argument).

crud_items

<tmpl_loop name='crud_items'>
     <tr>
         <td>
         <tmpl_var name='crud_d_form'>
         <tmpl_var name='crud_u_form'>
         <a href="<tmpl_var escape='html' name='file'>"
             ><tmpl_var escape='html' name='item'></a></td>
         <td><tmpl_var name='description'></td>
         <td><tmpl_var name='foo1'></td>
         <td><tmpl_var name='foo2'></td>
         <td><tmpl_var escape='html' name='time'></td>
     </tr>
 </tmpl_loop>

A loop containing records returned by the List operation. This variable will be empty if the user isn't allowed to List (see can configuration argument). The variables in the loop are as follows:

All items from items configuration argument

<a href="<tmpl_var escape='html' name='file'>"
    ><tmpl_var escape='html' name='item'></a></td>
<td><tmpl_var name='description'></td>
<td><tmpl_var name='foo1'></td>
<td><tmpl_var name='foo2'></td>
<td><tmpl_var escape='html' name='time'></td>

All the items you specified in the items configuration argument will be present here, even if that item was set as a subref in the items. You can also add extra keys here through list_sub sub specified in the configuration. Note: any file items will contain filename and directory specified in the file_dir argument. You can modify that using list_sub sub.

crud_can_d

<tmpl_if name="crud_can_d">
    Can delete!
</tmpl_if>

True or false value. If true, then the user is allowed to delete records (see can configuration argument).

crud_can_u

<tmpl_if name="crud_can_u">
    Can update!
</tmpl_if>

True or false value. If true, then the user is allowed to update records (see can configuration argument).

crud_can_ud

<tmpl_if name="crud_can_ud">
    Can update or delete!
</tmpl_if>

True or false value. If true, then the user is allowed to delete or update records (see can configuration argument).

crud_u_form

<tmpl_var name='crud_u_form'>

Contains HTML code for the "Update Record" form. This HTML might change in the future or be configurable, as currently it's highly specific to what I use in a specific Web app. You can easily use your own form by including crud_update parameter set to a true value and crud_id paramater set to the ID of the record (that will be in the <tmpl_var name='crud_id>).

crud_d_form

<tmpl_var name='crud_d_form'>

Contains HTML code for the "Delete Record" form. This HTML might change in the future or be configurable, as currently it's highly specific to what I use in a specific Web app. You can easily use your own form by including crud_delete parameter set to a true value and crud_id paramater set to the ID of the record (that will be in the <tmpl_var name='crud_id>).

TODO AND LIMITATIONS

Currently, the module doesn't actually implement the "READ" functionality. Instead, it only does "LIST" (i.e. list all records instead of a chosen one) and it doesn't support pagination. If you expect a whole ton of records in the database, heed the "L" flag in the can option; if it's present, the plugin will always load all records into {t}

Along with the "READ" optional and pagination, this plugin could make use of absorbing some of the HTML for the LIST feature; so you'd have to just type <tmpl_var name="crud_list"> instead of doing HTML by hand, but so far I haven't found a flexible solution that doesn't drown the user of the plugin in settings options.

In addition, the plugin needs an option to add a variable amount of files, all stored in a single database field, which in turn would allow updating the file field during record editing.

Lastly, the plugin currently doesn't support selects, checkboxes, or radio boxes in the Create/Update form.

A NOTE ON FORM INPUT ERROR CHECKING

This plugin only checks for whether or not a mandatory field is present when creating/updating records. If you need more advanced error checking, see App::ZofCMS::Plugin::FormChecker that can (read "should") work together with this plugin (run FormChecker first).

AUTHOR

Zoffix Znet, <zoffix at cpan.org>

BUGS

Please report any bugs or feature requests to bug-app-zofcms-plugin-crud at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=App-ZofCMS-Plugin-CRUD. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc App::ZofCMS::Plugin::CRUD

You can also look for information at:

LICENSE AND COPYRIGHT

Copyright 2013 Zoffix Znet.

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

http://www.perlfoundation.org/artistic_license_2_0

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.