There are numerous frameworks that will help you do this sort of thing. Many of them are a little over the top. One that may fit the job (without getting in your way too much) is CGI::Ex::App which is part of the CGI::Ex suite. The perldoc for CGI::Ex::App has a recipe book example that is fairly basic.
The sample from the perldoc normally puts all of the html in separate files but CGI::Ex::App allows you to rapid prototype by storing the html in the same file as your script using subs called my_step_file_print which is what I have done below.
Just copy the code below into a file in a cgi directory (or mod_perl registry). Change the datebase connection settings returned by the db_conn method. Chmod it 755. Then go to it in a browser. It has all of the parts of a CRUD (create read update delete) interface for the simple recipe records.
The long debug that prints out after the html shows what methods the CGI::Ex::App used to run each step and can be removed by commenting out the post_navigate method.
For more information as to what is going on, you should read the CGI::Ex::App perldoc.
#!/usr/bin/perl -w
package Recipe;
use strict;
use base qw(CGI::Ex::App);
use CGI::Ex::Dump qw(debug);
use DBI;
Recipe->navigate;
###------------------------------------------###
sub handle_error {
my ($self, $error) = @_;
debug $error, $self->dump_history;
die $error;
}
sub post_navigate {
# show what happened during development
debug shift->dump_history;
}
sub base_dir_abs { '/var/www/templates' }
sub base_dir_rel { 'content' }
sub db_conn { ['dbi:mysql:my_db', 'my_user', 'my_pass'] }
sub dbh {
my $self = shift;
if (! $self->{'dbh'}) {
my $conn = $self->db_conn;
$self->{'dbh'} = DBI->connect(@$conn, {RaiseError => 1});
$self->create_tables if ! grep {/^`?recipe`?$/} ($self->{'dbh'
+}->tables);
}
return $self->{'dbh'};
}
sub create_tables {
my $self = shift;
$self->dbh->do("CREATE TABLE recipe (
id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(50) NOT NULL,
ingredients VARCHAR(255) NOT NULL,
directions VARCHAR(255) NOT NULL,
date_added DATETIME NOT NULL
)");
}
###----------------------------------------------------------------###
sub main_info_complete { 0 }
sub main_hash_swap {
my $self = shift;
my $s = "SELECT id, title, date_added
FROM recipe
ORDER BY date_added";
my $data = $self->dbh->selectall_arrayref($s);
my @data = map {my %h; @h{qw(id title date_added)} = @$_; \%h} @$d
+ata;
return {
recipies => \@data,
};
}
###----------------------------------------------------------------###
sub add_file_print { shift->edit_file_print('edit') }
sub add_hash_validation {
return {
'group order' => [qw(title ingredients directions)],
title => {
required => 1,
max_len => 30,
},
ingredients => {
required => 1,
max_len => 255,
},
directions => {
required => 1,
max_len => 255,
},
};
}
sub add_finalize {
my $self = shift;
my $form = $self->form;
my $s = "SELECT COUNT(*) FROM recipe WHERE title = ?";
my ($count) = $self->dbh->selectrow_array($s, {}, $form->{'title'}
+);
if ($count) {
$self->add_errors(title => 'A recipe by this title already exi
+sts');
return 0;
}
$s = "INSERT INTO recipe (title, ingredients, directions, date_add
+ed)
VALUES (?, ?, ?, NOW())";
$self->dbh->do($s, {}, $form->{'title'},
$form->{'ingredients'},
$form->{'directions'});
$self->add_to_form(success => "Recipe added to the database");
return 1;
}
###----------------------------------------------------------------###
sub edit_skip { shift->form->{'id'} ? 0 : 1 }
sub edit_hash_common {
my $self = shift;
return {} if $self->ready_validate;
my $sth = $self->dbh->prepare("SELECT * FROM recipe WHERE id = ?"
+);
$sth->execute($self->form->{'id'});
my $hash = $sth->fetchrow_hashref;
return $hash;
}
sub edit_hash_validation {
my $hash = shift->add_hash_validation(@_);
$hash->{'id'} = {required => 1, match => 'm/^\d+$/'}; # check to m
+ake sure id is valid
return $hash;
}
sub edit_finalize {
my $self = shift;
my $form = $self->form;
my $s = "SELECT COUNT(*) FROM recipe WHERE title = ? AND id != ?";
my ($count) = $self->dbh->selectrow_array($s, {}, $form->{'title'}
+, $form->{'id'});
if ($count) {
$self->add_errors(title => 'A recipe by this title already exi
+sts');
return 0;
}
$s = "UPDATE recipe SET title = ?, ingredients = ?, directions = ?
+ WHERE id = ?";
$self->dbh->do($s, {}, $form->{'title'},
$form->{'ingredients'},
$form->{'directions'},
$form->{'id'});
$self->add_to_form(success => "Recipe updated in the database");
return 1;
}
###----------------------------------------------------------------###
sub view_skip { shift->edit_skip(@_) }
sub view_hash_common { shift->edit_hash_common(@_) }
###----------------------------------------------------------------###
sub delete_skip { shift->edit_skip(@_) }
sub delete_info_complete { 1 }
sub delete_finalize {
my $self = shift;
$self->dbh->do("DELETE FROM recipe WHERE id = ?", {}, $self->form-
+>{'id'});
$self->add_to_form(success => "Recipe deleted from the database");
return 1;
}
#File: /var/www/templates/content/recipe/main.html
### --------------------------------------------
sub main_file_print {
return \ q{
<html>
<head>
<title>Recipe DB</title>
</head>
<h1>Recipe DB</h1>
[% IF success %]<span style="color:darkgreen"><h2>[% success %]</h2></
+span>[% END %]
<table style="border:1px solid blue">
<tr><th>#</th><th>Title</th><th>Date Added</th></tr>
[% FOR row IN recipies %]
<tr>
<td>[% loop.count %].</td>
<td><a href="[% script_name %]/view?id=[% row.id %]">[% row.title %]
+</a>
(<a href="[% script_name %]/edit?id=[% row.id %]">Edit</a>)
</td>
<td>[% row.date_added %]</td>
</tr>
[% END %]
<tr><td colspan=2 align=right><a href="[% script_name %]/add">Add new
+recipe</a></td></tr>
</table>
</html>
};
}
#File: /var/www/templates/content/recipe/edit.html
### --------------------------------------------
sub edit_file_print {
return \ q{
<html>
<head>
<title>[% step == 'add' ? "Add" : "Edit" %] Recipe</title>
</head>
<h1>[% step == 'add' ? "Add" : "Edit" %] Recipe</h1>
<form method=post name=[% form_name %]>
<input type=hidden name=step>
<table>
[% IF step != 'add' ~%]
<tr>
<td><b>Id:</b></td><td>[% id %]</td></tr>
<input type=hidden name=id>
</tr>
<tr>
<td><b>Date Added:</b></td><td>[% date_added %]</td></tr>
</tr>
[% END ~%]
<tr>
<td valign=top><b>Title:</b></td>
<td><input type=text name=title>
<span style='color:red' id=title_error>[% title_error %]</span><
+/td>
</tr>
<tr>
<td valign=top><b>Ingredients:</b></td>
<td><textarea name=ingredients rows=10 cols=40 wrap=physical></texta
+rea>
<span style='color:red' id=ingredients_error>[% ingredients_erro
+r %]</span></td>
</tr>
<tr>
<td valign=top><b>Directions:</b></td>
<td><textarea name=directions rows=10 cols=40 wrap=virtual></textare
+a>
<span style='color:red' id=directions_error>[% directions_error
+%]</span></td>
</tr>
<tr>
<td colspan=2 align=right>
<input type=submit value="[% step == 'add' ? 'Add' : 'Update' %]
+"></td>
</tr>
</table>
</form>
(<a href="[% script_name %]">Main Menu</a>)
[% IF step != 'add' ~%]
(<a href="[% script_name %]/delete?id=[% id %]">Delete this recipe</
+a>)
[%~ END %]
[% js_validation %]
</html>
};
}
#File: /var/www/templates/content/recipe/view.html
### --------------------------------------------
sub view_file_print {
return \ q{
<html>
<head>
<title>[% title %] - Recipe DB</title>
</head>
<h1>[% title %]</h1>
<h3>Date Added: [% date_added %]</h3>
<h2>Ingredients</h2>
[% ingredients %]
<h2>Directions</h2>
[% directions %]
<hr>
(<a href="[% script_name %]">Main Menu</a>)
(<a href="[% script_name %]/edit?id=[% id %]">Edit this recipe</a>)
</html>
};
}