App-ZofCMS

 view release on metacpan or  search on metacpan

lib/App/ZofCMS/Plugin/CRUD.pm  view on Meta::CPAN

        id          => $self->{Q}{crud_id},
        page        => $self->{Q}{dir} . $self->{Q}{page},
        has_files   => scalar( grep $_->{el_file}, @items ),
        elements    => [ grep !$_->{el_auto_set}, @items ],
        create_success  => $self->{CREATE_SUCCESS},
        update_success  => $self->{UPDATE_SUCCESS},
        hide_form       => ($self->{CREATE_SUCCESS} || $self->{UPDATE_SUCCESS}),
        (
            @{ $self->{FORM_ERRORS} || [] }
            ? ( errors      => $self->{FORM_ERRORS}, )
            : (),
        ),
    );

    return $ht->output;
}

sub _prepare_items {
    my $self = shift;

    $self->{ITEMS} = $self->{CONF}{items};

    for ( @{ $self->{ITEMS} || [] } ) {
        unless ( ref ) {
            $_ = +{ $_ => 'text' },
        }

        my ( $text, $type ) = %$_;
        ( my $name = lc $text ) =~ s/\W/_/g;

        my %opts;
        if ( ref $type eq 'ARRAY' ) {
            ( $type, %opts ) = ( @$type );
        }
        elsif ( ref $type eq 'CODE' ) {
            $self->{Q}{ $name } = $type->( @{ $self }{ qw/T  Q/ } );
            $type = 'auto_set';
        }
        my $id = "crud_$name";
        $_ = +{
            text        => $text,
            "el_$type"  => 1,
            name        => $name,
            id          => $id,
            value       => $self->{Q}{ $name },
            is_create   => !$self->{Q}{crud_update}
                            && !$self->{Q}{crud_update_save},
            %opts,
        };
    }

    return;
}

sub _get_D_form_template {
    return <<'END_HTML';
<form class="delete_button_form delete_form" method="POST" action=""
><div
    ><input type="hidden"
        name="page"
        value="<tmpl_var escape='html' name='page'>"
    ><input type="hidden"
        name="crud_delete"
        value="1"
    ><input type="hidden"
        name="crud_id"
        value="[<<ITEM:ID>>]"
    ><input type="image"
        alt="Delete"
        class="delete_button_no_style"
        src="/pics/delete-button.png"
></div></form>
END_HTML
}

sub _get_U_form_template {
    return <<'END_HTML';
<form class="update_button_form update_form" method="POST" action=""
><div
    ><input type="hidden"
        name="page"
        value="<tmpl_var escape='html' name='page'>"
    ><input type="hidden"
        name="crud_update"
        value="1"
    ><input type="hidden"
        name="crud_id"
        value="[<<ITEM:ID>>]"
    ><input type="image"
        alt="Update"
        class="update_button"
        src="/pics/update-button.png"
></div></form>
END_HTML
}

sub _get_CU_form_template {
    return <<'END_HTML';
<tmpl_if name='create_success'>
    <p class="success-message">Item has been successfully added. <a href="<tmpl_var escape='html' name='page'>">Add another one</a></p>
</tmpl_if>
<tmpl_if name='update_success'>
    <p class="success-message">Item has been successfully updated. <a href="<tmpl_var escape='html' name='page'>">Back to the form</a></p>
</tmpl_if>
<tmpl_unless name='hide_form'>
    <form action="" method="POST" id="crud_<tmpl_if name='is_create'>c<tmpl_else>u</tmpl_if>form"<tmpl_if name='has_files'> enctype="multipart/form-data"</tmpl_if>>
    <div>
        <input type="hidden" name="page"
            value="<tmpl_var escape='html' name='page'>">
        <input type="hidden" name="crud_<tmpl_if name='is_create'>create<tmpl_else>update_save</tmpl_if>" value="1">

        <tmpl_unless name='is_create'>
            <input type="hidden" name="crud_id"
                value="<tmpl_var escape='html' name='id'>">
        </tmpl_unless>

        <tmpl_loop name='errors'>
            <p class="error"><tmpl_var escape='html' name='error'></p>
        </tmpl_loop>

        <p class="crud_form_note">Fields marked with
            an asterisk(*) are mandatory.</p>
        <ul>
            <tmpl_loop name='elements'>
            <li>
                <tmpl_if name='el_text'>
                    <label for="<tmpl_var escape='html' name='id'>">
                        <tmpl_unless name='optional'>
                        *</tmpl_unless><tmpl_var
                            escape='html' name='text'>:</label>
                    <input type="text"
                        class="input_text"
                        name="<tmpl_var escape='html' name='name'>"
                        value="<tmpl_var escape='html' name='value'>"
                        id="<tmpl_var escape='html' name='id'>">
                </tmpl_if>
                <tmpl_if name='el_textarea'>
                    <label for="<tmpl_var escape='html' name='id'>"
                        class="textarea_label"
                        ><tmpl_unless name='optional'>
                        *</tmpl_unless><tmpl_var
                            escape='html' name='text'>:</label>
                    <textarea cols="60" rows="5"
                        name="<tmpl_var escape='html' name='name'>"
                        id="<tmpl_var escape='html' name='id'>"
                        ><tmpl_var escape='html' name='value'></textarea>
                </tmpl_if>
                <tmpl_if name='el_file'>
                    <tmpl_if name='is_create'>
                        <label for="<tmpl_var escape='html' name='id'>"
                            ><tmpl_unless name='optional'>
                        *</tmpl_unless><tmpl_var
                            escape='html' name='text'>:</label>
                        <input type="file"
                            class="input_file"
                            name="<tmpl_var escape='html' name='name'>"
                            id="<tmpl_var escape='html' name='id'>">
                    <tmpl_else>
                        Can't change files.
                    </tmpl_if>
                </tmpl_if>
            </li>
            </tmpl_loop>
        </ul>

        <input type="submit" value="Submit">
    </div>
    </form>
</tmpl_unless>
END_HTML
}

1;
__END__

=encoding utf8

=head1 NAME

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

=head1 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>

=head1 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 L<App::ZofCMS::Plugin::DBIPPT>
and L<App::ZofCMS::Plugin::FormChecker>.

=head1 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.

=head2 C<plugins>

    plugins => [ qw/CRUD/ ],

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

=head2 C<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,
    }

B<Mandatory>. Takes either a hashref or a subref as a value. If a subref is
specified, its return value will be assigned to C<plug_crud> as if
it were already there. If the sub returns an C<undef>, then the plugin
will stop
further processing. The C<@_> of the subref will contain (in that order):
ZofCMS Template hashref, query parameters hashref, and

lib/App/ZofCMS/Plugin/CRUD.pm  view on Meta::CPAN

=over 10

=item * SQL column name for that field

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

=item * HTML C<id=""> attribute in the Create/Update form (prefix
C<crud_> will be added)

=item * C<< <tmpl_var> >> name for the List function of the plugin.

=back

=head4 B<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 C<< `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 L<App::ZofCMS::Plugin::FormChecker> checking the
input given to the Create form. The trigger will be
C<crud_create> and the fields it will be checking are
C<item> and C<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>

=head4 hashref

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

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

=head4 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. B<Currently supported
values> are C<text> for C<< <input type="text"> >>, C<textarea> for
C<< <textarea> >>, and C<file> for C<< <input type="file"> >>.
File inputs are currently not editable in the Update form.

=head4 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 C<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 C<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.

=head4 A subref

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

The key will function the same as described for C<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 B<will NOT be executed>. The C<@_> of the sub will
contain (in that order): ZofCMS Template hashref and query parameters

lib/App/ZofCMS/Plugin/CRUD.pm  view on Meta::CPAN

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

=head3 C<can>

    can => 'CRUDL',

    can => 'RL',

    can => 'CUL',

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

=head3 C<no_time_sort>

    no_time_sort => 0,

B<Optional. Takes> true or false value. B<Defaults to> false. In 99%
of my CRUD pages, I've had a C<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 C<time> item to be present and set to a value of Unix time
(output of C<time()>). If you don't have such a column or don't want your
records sorted by time, set C<no_time_sort> to a true value.

=head1 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>

=head2 C<crud_success_message>

    <tmpl_var name='crud_success_message'>

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

=head2 C<crud_error>

    <tmpl_var name='crud_error'>

This variable will contain an error message, if any, B<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 C<< <p class="error">$error</p> >> where C<$error> is
the text of the error, which currently will be either
C<Couldn't find the item to update> or C<Couldn't find the item to delete>.

=head2 C<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 C<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 C<can> configuration variable).

=head2 C<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 I<List> (see C<can> configuration
argument).

=head2 C<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 I<List> (see C<can> configuration
argument). The variables in the loop are as follows:

=head3 All items from C<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 C<items> configuration argument will be
present here, even if that item was set as a subref in the C<items>.
You can also add extra keys here through C<list_sub> sub specified in the
configuration. B<Note:> any C<file> items will contain filename
I<and> directory specified in the C<file_dir> argument. You can modify that
using C<list_sub> sub.

=head3 C<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 C<can> configuration argument).

=head3 C<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 C<can> configuration argument).

=head3 C<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
B<or> update records (see C<can> configuration argument).

=head3 C<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 C<crud_update> parameter set to a true value and
C<crud_id> paramater set to the ID of the record (that will be in the
C<< <tmpl_var name='crud_id> >>).

=head3 C<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 C<crud_delete> parameter set to a true value and
C<crud_id> paramater set to the ID of the record (that will be in the
C<< <tmpl_var name='crud_id> >>).

=head1 TODO AND LIMITATIONS

Currently, the module doesn't actually implement the "READ" functionality.



( run in 0.910 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )