For information about current developments and future releases, see:
http://maypole.perl.org/?TheRoadmap
-2.11
+2.11 Mon 31 July 2006
+
+SVN revision 519
Deprecated:
Directly accessing the attributes of the request object, or the parameters
Changes
-ex/BeerDB.pm
-ex/BeerDB/Base.pm
-ex/BeerDB/Beer.pm
-ex/beerdb.sql
-ex/fancy_example/BeerDB.pm
-ex/fancy_example/beerdb.sql
-ex/fancy_example/BeerDB/Base.pm
-ex/fancy_example/BeerDB/Beer.pm
-ex/fancy_example/BeerDB/Brewery.pm
-ex/fancy_example/BeerDB/Drinker.pm
-ex/fancy_example/templates/custom/addnew
-ex/fancy_example/templates/custom/display_inputs
-ex/fancy_example/templates/custom/display_search_inputs
-ex/fancy_example/templates/custom/edit
-ex/fancy_example/templates/custom/header
-ex/fancy_example/templates/custom/maypole.css
-ex/fancy_example/templates/custom/metadata
-ex/fancy_example/templates/custom/search_form
+examples/BeerDB.pm
+examples/BeerDB/Base.pm
+examples/BeerDB/Beer.pm
+examples/beerdb.sql
+examples/fancy_example/BeerDB.pm
+examples/fancy_example/beerdb.sql
+examples/fancy_example/BeerDB/Base.pm
+examples/fancy_example/BeerDB/Beer.pm
+examples/fancy_example/BeerDB/Brewery.pm
+examples/fancy_example/BeerDB/Drinker.pm
+examples/fancy_example/templates/custom/addnew
+examples/fancy_example/templates/custom/display_inputs
+examples/fancy_example/templates/custom/display_search_inputs
+examples/fancy_example/templates/custom/edit
+examples/fancy_example/templates/custom/header
+examples/fancy_example/templates/custom/maypole.css
+examples/fancy_example/templates/custom/metadata
+examples/fancy_example/templates/custom/search_form
lib/Apache/MVC.pm
lib/CGI/Maypole.pm
lib/CGI/Untaint/Maypole.pm
+++ /dev/null
-package BeerDB;
-use Maypole::Application;
-use Class::DBI::Loader::Relationship;
-
-sub debug { $ENV{BEERDB_DEBUG} || 0 }
-# This is the sample application. Change this to the path to your
-# database. (or use mysql or something)
-use constant DBI_DRIVER => 'SQLite';
-use constant DATASOURCE => $ENV{BEERDB_DATASOURCE} || 't/beerdb.db';
-
-
-BEGIN {
- my $dbi_driver = DBI_DRIVER;
- if ($dbi_driver =~ /^SQLite/) {
- die sprintf "SQLite datasource '%s' not found, correct the path or "
- . "recreate the database by running Makefile.PL", DATASOURCE
- unless -e DATASOURCE;
- eval "require DBD::SQLite";
- if ($@) {
- eval "require DBD::SQLite2" and $dbi_driver = 'SQLite2';
- }
- }
- BeerDB->setup(join ':', "dbi", $dbi_driver, DATASOURCE);
-}
-
-# Give it a name.
-BeerDB->config->application_name('The Beer Database');
-
-# Change this to the root of the web site for your maypole application.
-BeerDB->config->uri_base( $ENV{BEERDB_BASE} || "http://localhost/beerdb/" );
-
-# Change this to the htdoc root for your maypole application.
-
-my @root= ('t/templates');
-push @root,$ENV{BEERDB_TEMPLATE_ROOT} if ($ENV{BEERDB_TEMPLATE_ROOT});
-BeerDB->config->template_root( [@root] );
-# Specify the rows per page in search results, lists, etc : 10 is a nice round number
-BeerDB->config->rows_per_page(10);
-
-# Handpumps should not show up.
-BeerDB->config->display_tables([qw[beer brewery pub style]]);
-BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
-BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
-BeerDB::Beer->untaint_columns(
- printable => [qw/abv name price notes url/],
- integer => [qw/style brewery score/],
- date =>[ qw/tasted/],
-);
-BeerDB::Pub->untaint_columns(printable => [qw/name notes url/]);
-
-# Required Fields
-BeerDB->config->{brewery}{required_cols} = [qw/name/];
-BeerDB->config->{style}{required_cols} = [qw/name/];
-BeerDB->config->{beer}{required_cols} = [qw/brewery name price/];
-BeerDB->config->{pub}{required_cols} = [qw/name/];
-
-BeerDB->config->{loader}->relationship($_) for (
- "a brewery produces beers",
- "a style defines beers",
- "a pub has beers on handpumps");
-
-# For testing classmetadata
-sub BeerDB::Beer::classdata :Exported {};
-sub BeerDB::Beer::list_columns { return qw/score name price style brewery url/};
-
-1;
+++ /dev/null
-package BeerDB::Base;
-use strict;
-use warnings;
-
-sub floob {}
-
-1;
+++ /dev/null
-package BeerDB::Beer;
-use strict;
-use warnings;
-
-# do this to test we get the expected @ISA after setup_model()
-use base 'BeerDB::Base';
-
-sub fooey : Exported {}
-
-1;
+++ /dev/null
-CREATE TABLE style (
- id integer primary key auto_increment,
- name varchar(60),
- notes text
-);
-
-CREATE TABLE pub (
- id integer primary key auto_increment,
- name varchar(60),
- url varchar(120),
- notes text
-);
-
-CREATE TABLE handpump (
- id integer primary key auto_increment,
- beer integer,
- pub integer
-);
-
-CREATE TABLE beer (
- id integer primary key auto_increment,
- brewery integer,
- style integer,
- name varchar(30),
- url varchar(120),
- score integer(2),
- price varchar(12),
- abv varchar(10),
- notes text,
- tasted date
-);
-
-CREATE TABLE brewery (
- id integer primary key auto_increment,
- name varchar(30),
- url varchar(50),
- notes text
-);
+++ /dev/null
-package BeerDB;
-use Maypole::Application;
-use Class::DBI::Loader::Relationship;
-
-sub debug { $ENV{BEERDB_DEBUG} || 0 }
-# This is the sample application. Change this to the path to your
-# database. (or use mysql or something)
-use constant DBI_DRIVER => 'SQLite';
-use constant DATASOURCE => '/home/peter/Desktop/maypolebeer/beerdb';
-
-BeerDB->config->model('BeerDB::Base');
-
-BeerDB->setup("dbi:mysql:beerdb",'root', '');
-
-# Give it a name.
-BeerDB->config->application_name('The Beer Database');
-
-# Change this to the root of the web site for your maypole application.
-BeerDB->config->uri_base( $ENV{BEERDB_BASE} || "http://localhost/beerdb/" );
-
-# Change this to the htdoc root for your maypole application.
-
-my @root= ('/home/peter/Desktop/maypolebeer/templates');
-push @root,$ENV{BEERDB_TEMPLATE_ROOT} if ($ENV{BEERDB_TEMPLATE_ROOT});
-BeerDB->config->template_root( [@root] );
-# Specify the rows per page in search results, lists, etc : 10 is a nice round number
-BeerDB->config->rows_per_page(10);
-
-# Let TT templates recursively include themselves
-BeerDB->config->{view_options} = { RECURSION => 1, };
-
-# Handpumps should not show up.
-BeerDB->config->display_tables([qw[beer brewery pub style drinker pint person]]);
-# Access handpumps if want
-BeerDB->config->ok_tables([ @{BeerDB->config->display_tables}, qw[handpump]]);
-
-BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
-BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
-BeerDB::Beer->untaint_columns(
- printable => [qw/abv name price notes/],
- integer => [qw/style brewery score/],
- date =>[ qw/tasted/],
-);
-BeerDB::Pub->untaint_columns(printable => [qw/name notes url/]);
-BeerDB::Drinker->untaint_columns( printable => [qw/handle created/] );
-BeerDB::Pint->untaint_columns( printable => [qw/date_and_time/]);
-
-
-# Required Fields
-BeerDB->config->{brewery}{required_cols} = [qw/name url/];
-BeerDB->config->{style}{required_cols} = [qw/name/];
-BeerDB->config->{beer}{required_cols} = [qw/brewery name price/];
-BeerDB->config->{pub}{required_cols} = [qw/name/];
-BeerDB->config->{drinker}{required_cols} = [qw/handle person/];
-BeerDB->config->{pint}{required_cols} = [qw/drinker handpump/];
-BeerDB->config->{person}{required_cols} = [qw/first_name sur_name dob email/];
-
-# Columns to display
-sub BeerDB::Handpump::display_columns { qw/pub beer/ }
-
-BeerDB->config->{loader}->relationship($_) for (
- "a brewery produces beers",
- "a style defines beers",
- "a pub has beers on handpumps",
- "a handpump defines pints",
- "a drinker drinks pints",);
-
-# For testing classmetadata
-#sub BeerDB::Beer::classdata :Exported {};
-sub BeerDB::Beer::list_columns { return qw/score name price style brewery/};
-
-sub BeerDB::Handpump::stringify_self {
- my $self = shift;
- return $self->beer . " @ " . $self->pub;
-}
-
-
-1;
+++ /dev/null
-package BeerDB::Base;
-use base qw/Maypole::Model::CDBI/;
-use strict;
-use warnings;
-use Data::Dumper;
-
-# Overide list to add display_columns to cgi
-# Perhaps do this in AsForm?
-sub list : Exported {
- use Data::Dumper;
- my ($self, $r) = @_;
- $self->SUPER::list($r);
- my %cols = map { $_ => 1 } $self->columns, $self->display_columns;
- my @cols = keys %cols;
- $r->template_args->{classmetadata}{cgi} = { $self->to_cgi(@cols) };
-}
-
-# Override view to make inputs and process form to add to related
-sub view : Exported {
- my ($self, $r, $obj) = @_;
- $self->_croak( "Object method only") unless $obj;
-
- if ($r->params->{submit}) {
- my @related = $obj->add_to_from_cgi($r, { required => [$self->related ]});
- if (my $errs = $obj->cgi_update_errors) {
- $r->template_args->{errors} = $errs;
- }
- }
-
- # Inputs to add to related on the view page
- # Now done on the view template
- # my %cgi = $self->to_cgi($self->related);
- #$r->template_args->{classmetadata}{cgi} = \%cgi ;
-}
-
-
-# Template switcheroo bug bit me -- was seeing view page but the view action was never
-# being executed after an edit.
-sub do_edit : Exported {
- my ($self, $r) = (shift, shift);
- $self->SUPER::do_edit($r, @_);
- if (my $obj = $r->object) {
- my $url = $r->config->uri_base . "/" . $r->table . "/view/" . $obj->id;
- $r->redirect_request(url => $url);
- }
-}
-
-sub metadata: Exported {}
-
-
-1;
+++ /dev/null
-package BeerDB::Beer;
-use strict;
-use warnings;
-
-# do this to test we get the expected @ISA after setup_model()
-use base 'BeerDB::Base';
-
-sub fooey : Exported {}
-
-1;
+++ /dev/null
-package BeerDB::Brewery;
-use strict;
-use warnings;
-
-use Data::Dumper;
-
-sub display_columns { qw/name url beers/ } # note has_man beers
-sub list_columns { qw/name url/ }
-
-1;
+++ /dev/null
-package BeerDB::Drinker;
-use strict;
-use warnings;
-
-use Data::Dumper;
-
-__PACKAGE__->columns('Stringify' => qw/handle/);
-
-# A drinker is a person but we do not want to select who that person is
-# from a list because this is a 1:1 relationship rather than a M:1.
-# The no_select option tells AsForm not to bother making a select box
-
-__PACKAGE__->has_a(person => 'BeerDB::Person', no_select => 1);
-
-# Drinker drinks many beers at pubs if they are lucky. I like to specify the
-# name of the foreign key unless i can control the order that the
-# cdbi classes are created. CDBI does not guess very well the fk column.
-
-#__PACKAGE__->has_many(pints => 'BeerDB::Pint', 'drinker');
-
-# When we create a drinker we want to create a person as well
-# So tell AsForm to display the person inputs too.
-
-sub display_columns { qw/person handle/ }
-sub list_columns { qw/person handle/ }
-# AsForm and templates may check for search_colums when making
-#sub search_columns { qw/person handle/ }
-
-# We need to tweak the cgi inputs a little.
-# Since list is where addnew is, override that.
-# Person is a has_a rel and AsForm wont make foreign inputs automatically so
-# we manually do it.
-
-sub list : Exported {
- my ($self, $r) = @_;
- $self->SUPER::list($r);
- my %cgi = $self->to_cgi;
- $cgi{person} = $self->to_field('person', 'foreign_inputs');
- $r->template_args->{classmetadata}{cgi} = \%cgi;
- #$r->template_args->{classmetadata}{search_cgi} = $self->search_inputs;
-}
-
-
-
-
-#sub foreign_input_delimiter { '__IMODDD__'}
-
-1;
+++ /dev/null
-CREATE TABLE style (
- id integer primary key auto_increment,
- name varchar(60),
- notes text
-);
-
-CREATE TABLE pub (
- id integer primary key auto_increment,
- name varchar(60),
- url varchar(120),
- notes text
-);
-
-CREATE TABLE handpump (
- id integer primary key auto_increment,
- beer integer,
- pub integer
-);
-
-CREATE TABLE beer (
- id integer primary key auto_increment,
- brewery integer,
- style integer,
- name varchar(30),
- score integer(2),
- price varchar(12),
- abv varchar(10),
- notes text,
- tasted date
-);
-
-CREATE TABLE brewery (
- id integer primary key auto_increment,
- name varchar(30),
- url varchar(50),
- notes text
-);
-
-CREATE TABLE drinker (
- id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- person INTEGER UNSIGNED NOT NULL,
- handle VARCHAR(20) NOT NULL,
- created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY(id),
- INDEX drinker_FKIndex1(person)
-);
-
-CREATE TABLE person (
- id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- first_name VARCHAR(50) NULL,
- sur_name VARCHAR(50) NULL,
- dob DATE NULL,
- username VARCHAR(20) NULL,
- password VARCHAR(20) NULL,
- email VARCHAR(255) NULL,
- PRIMARY KEY(id)
-);
-
-CREATE TABLE pint (
- id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- drinker INTEGER UNSIGNED NOT NULL,
- handpump INTEGER UNSIGNED NOT NULL,
- date_and_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY(id)
-);
-
-
+++ /dev/null
-[%#
-
-=head1 addnew
-
-This is the interface to adding a new instance of an object. (or a new
-row in the database, if you want to look at it that way) It displays a
-form containing a list of HTML components for each of the columns in the
-table.
-
-=cut
-
-#%]
-[% tbl = classmetadata.table; %]
-
-<div id="addnew">
-<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
-<fieldset>
-<legend>Add a new [% config.TABLES.$tbl.singular || tbl | ucfirst | replace('_',' '); %] </legend>
- [% INCLUDE display_inputs; %]
- <input type="submit" name="create" value="create" />
- <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
-</fieldset>
-</form>
-</div>
+++ /dev/null
-[%#
-
-=head1 display_inputs
-
-This *RECURSIVELY* displays inputs for a hash of html elements
-
-Vars it needs:
- classmetadata-- the hash of bunch of data:
- cgi -- inputs keyed on column names
- table -- table inputs are for
- columns -- list in order want displayed inputs
- colnames -- hash of what to label inputs
-
-errors -- hash of errors keyed on columns
-
-
-TODO -- make it recognize a general submit button for redisplaying
-values on errors
-
-=cut
-
-#
-%]
-
-[% # some variables
- foreign = [];
- names = [];
- # get hash of related classes keyed on accessor for Foreign Inputs
- USE this = Class(classmetadata.name);
- tbl = classmetadata.table;
- required = { };
- FOR c IN request.config.$tbl.required_cols;
- required.$c = 1;
- END;
-
-%]
-
-[%
-SET heading_shown = 0;
-FOR col = classmetadata.columns;
- NEXT IF !classmetadata.cgi.$col;
- NEXT IF col == "id" OR col == classmetadata.table _ "_id";
- # Display foreign inputs last
- IF (mykeys = classmetadata.cgi.$col.keys);
- foreign.push(col);
- names.push(classmetadata.colnames.$col);
- NEXT;
- END;
- IF ! heading_shown;
- heading = classmetadata.moniker | ucfirst;
- "<h4> $heading </h4>";
- SET heading_shown = 1;
- END;
-%]
-
-[% # Base case starts here
-
- SET elem = classmetadata.cgi.$col; #.clone; # not sure why clone
- IF elem.type == 'hidden';
- elem.as_XML;
- NEXT;
- ELSIF cgi_params;
- param_col = col_prefix _ col;
- IF elem.tag == "textarea";
- elem = elem.push_content(cgi_params.$param_col);
- ELSIF elem.tag == "select";
- oldval = set_selected(elem, cgi_params.$col);
- ELSE;
- oldval = elem.attr("value", cgi_params.$param_col);
- END;
- END;
-%]
-
- <label>
- [% indicator = '';
- SET indicator = '*' IF (required.$col);
- %]
- <span class="field">
- [% indicator _ classmetadata.colnames.$col ||
- col | replace('_',' ') | ucfirst %]
- </span>
- [% elem.as_XML; %]
- </label>
-
- [% IF errors.$col %]
- <span class="error">[% errors.$col | html %]</span>
- [% END %]
-[% END; %]
-
-<!-- Display the differnt component inputs -->
-
-[% USE this = Class(classmetadata.name);
- FOR col IN foreign;
- # has_many mapping throws a stick in our spokes because related_class returns the mapped
- # class. Sometimes we just want the has_many class.
-
- # In case of Pub Handpumps maps to Beer and we want to add Handpump to Pub, we dont
- # want the mapped data .
- # In case of "Create New Handpump" -- we want the mapped data probably so we get
- # Beer inputs and Pub select box.
-
- fclass_rel_meta = this.related_meta(request, col);
- fclass = fclass_rel_meta.foreign_class; # ignor args.mapping
- fclass_meta = this.get_classmetadata(fclass);
- fclass_meta.cgi = classmetadata.cgi.$col;
- # USE Dumper; Dumper.dump(fclass_meta);
- INCLUDE display_inputs
- col_prefix = col _ "__AF__" _ col_prefix
- errors = errors.$col
- heading = names.shift
- classmetadata = fclass_meta; # localize
- END;
-%]
-
+++ /dev/null
-[%#
-
-=head1 display_search_inputs
-
-This displays inputs for search page. Override in individual class template
-directories as needed.
-
-Vars it needs:
-classmetadata-- the hash of inputs keyed on column names
-errors -- hash of errors keyed on columns
-=cut
-
-#%]
-
-[% IF errors.FATAL; "FATAL ERROR: "; errors.FATAL; "<br>"; END %]
-
-[% USE this = Class(classmetadata.name);
- SET srch_fields = classmetadata.search_columns ||
- classmetadata.columns;
- SET cgi = classmetadata.cgi;
- SET delimiter = this.foreign_input_delimiter;
- FOR field IN srch_fields;
- NEXT IF !cgi.$field;
- # Recursivly call this tmeplate if we have foreign field
- # (hash of foreign inputs should come with it)
- IF ( cgi.$field.keys );
- fclass = this.related_class(request, field);
- fclass_meta = this.get_classmetadata(fclass);
- fclass_meta.cgi = cgi.$field;
- tbl = fclass_meta.table;
- INCLUDE display_search_inputs
- col_prefix = col _ delimiter _ col_prefix
- classmetadata = fclass_meta;
- NEXT;
- END;
-
- NEXT IF field == 'id' OR field == classmetadata.table _ 'id';
- SET element = cgi.$field;
-%]
-
-<label>
- <span class="field">
- [%
- classmetadata.colnames.$field || field | ucfirst | replace('_',' '); %]
- </span>
- [% IF element.tag == "select";
- # set the previous value
- IF cgi_params.exists(field);
- set_selected(element, cgi_params.$field);
- END;
-
- END;
- IF element.tag == "input"; # wipe out any default value
- old_val = element.attr('value', '');
- END;
-
-
- element.as_XML;
- %]
-</label>
-[% END; %]
-
-
+++ /dev/null
-[%#
-
-=head1 edit
-
-This is the edit page. It edits the passed-in object, by displaying a
-form similar to L<addnew> but with the current values filled in.
-
-=cut
-
-#%]
-[% PROCESS macros %]
-[% INCLUDE header %]
-[% INCLUDE title %]
-
-[% IF request.action == 'edit' %]
-[% INCLUDE navbar %]
-[% END %]
-
-[% IF objects.size %]
-<div id="title">Edit a [% classmetadata.moniker %]</div>
-[% FOR item = objects; %]
-<form action="[% base %]/[% item.table %]/do_edit/[% item.id %]" method="post">
-<fieldset>
-<legend>Edit [% item.name %]</legend>
-[% FOR col = classmetadata.columns;
- NEXT IF col == "id" OR col == classmetadata.table _ "_id";
- '<label><span class="field">';
- classmetadata.colnames.$col || col | ucfirst | replace('_',' '); ":</span>";
- item.to_field(col).as_XML;
- "</label>";
- IF errors.$col;
- '<span class="error">'; errors.$col;'</span>';
- END;
- END %]
- <input type="submit" name="edit" value="edit"/>
- <input type="hidden" name="__form_id" value="[% request.make_random_id %]">
- </fieldset></form>
-
- [% END %]
-[% ELSE %]
-
-<div id="addnew">
-<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
-<fieldset>
-<legend>Add a new [% classmetadata.moniker %]</legend>
- [% FOR col = classmetadata.columns %]
- [% NEXT IF col == "id" %]
- <label><span class="field">[% classmetadata.colnames.$col %]</span>
- [%
- SET elem = classmetadata.cgi.$col.clone;
- IF request.action == 'do_edit';
- IF elem.tag == "textarea";
- elem = elem.push_content(request.param(col));
- ELSE;
- elem.attr("value", request.param(col));
- END;
- END;
- elem.as_XML; %]
- </label>
- [% IF errors.$col %]
- <span class="error">[% errors.$col | html %]</span>
- [% END %]
-
- [% END; %]
- <input type="submit" name="create" value="create" />
- <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
-</fieldset>
-</form>
-</div>
-
-[% END %]
-[% INCLUDE footer %]
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>
- [%
- title || config.application_name ||
- "A poorly configured Maypole application"
- %]
- </title>
- <meta http-equiv="Content-Type" content="text/html; charset=[% request.document_encoding %]" />
- <base href="[% config.uri_base%]"/>
- <link title="Maypole" href="maypole.css" type="text/css" rel="stylesheet" />
- </head>
- <body>
- <div class="content">
+++ /dev/null
-html {
- padding-right: 0px;
- padding-left: 0px;
- padding-bottom: 0px;
- margin: 0px;
- padding-top: 0px
-}
-body {
- font-family: sans-serif;
- padding-right: 0px;
- padding-left: 0px;
- padding-bottom: 0px;
- margin: 0px; padding-top: 0px;
- background-color: #fff;
-}
-#frontpage_list {
- position: absolute;
- z-index: 5;
- padding: 0px 100px 0px 0px;
- margin:0 0.5%;
- margin-bottom:1em;
- margin-top: 1em;
- background-color: #fff;
-}
-
-#frontpage_list a:hover {
- background-color: #d0d8e4;
-}
-
-#frontpage_list ul {
- list-style-type: square;
-}
-
-.content {
- padding: 12px;
- margin-top: 1px;
- margin-bottom:0px;
- margin-left: 15px;
- margin-right: 15px;
- border-color: #000000;
- border-top: 0px;
- border-bottom: 0px;
- border-left: 1px;
- border-right: 1px;
-}
-
-A {
- text-decoration: none;
- color:#225
-}
-A:hover {
- text-decoration: underline;
- color:#222
-}
-
-#title {
- z-index: 6;
- width: 100%;
- height: 18px;
- margin-top: 10px;
- font-size: 90%;
- border-bottom: 1px solid #ddf;
- text-align: left;
-}
-
-#subtitle {
- postion: absolute;
- z-index: 6;
- padding: 10px;
- margin-top: 2em;
- height: 18px;
- text-align: left;
- background-color: #fff;
-}
-
-input[type=text] {
- height: 16px;
- width: 136px;
- font-family: sans-serif;
- font-size: 11px;
- color: #2E415A;
- padding: 0px;
- margin-bottom: 5px;
-}
-
-input[type=submit] {
- height: 18px;
- width: 60px;
- font-family: sans-serif;
- font-size: 11px;
- border: 1px outset;
- background-color: #fff;
- padding: 0px 0px 2px 0px;
- margin-bottom: 5px;
-}
-
-input:hover[type=submit] {
- color: #fff;
- background-color: #7d95b5;
-}
-
-textarea {
- width: 136px;
- font-family: sans-serif;
- font-size: 11px;
- color: #2E415A;
- padding: 0px;
- margin-bottom: 5px;
-}
-
-select {
- height: 16px;
- width: 140px;
- font-family: sans-serif;
- font-size: 12px;
- color: #202020;
- padding: 0px;
- margin-bottom: 5px;
-}
-
-.deco1 {
- font-size: 0px;
- z-index:1;
- border:0px;
- border-style:solid;
- border-color:#4d6d99;
- background-color:#4d6d99;
-}
-
-.deco2 {
- z-index:2;
- border:0px;
- border-style:solid;
- border-color:#627ea5;
- background-color:#627ea5;
-}
-
-
-.deco3 {
- z-index:3;
- border:0px;
- border-style:solid;
- border-color:#7d95b5;
- background-color:#7d95b5;
-}
-
-.deco4 {
- z-index:4;
- border:0px;
- border-style:solid;
- border-color:#d0d8e4;
- background-color:#d0d8e4;
-}
-
-
-table {
- border: 0px solid;
- background-color: #ffffff;
-}
-
-#matrix { width: 100%; }
-
-#matrix th {
- background-color: #b5cadc;
- border: 1px solid #778;
- font: bold 12px Verdana, sans-serif;
-}
-
-#matrix #actionth {
- width: 1px;
- padding: 0em 1em 0em 1em;
-}
-
-#matrix tr.alternate { background-color:#e3eaf0; }
-#matrix tr:hover { background-color: #b5cadc; }
-#matrix td { font: 12px Verdana, sans-serif; }
-
-#navlist {
- padding: 3px 0;
- margin-left: 0;
- margin-top:3em;
- border-bottom: 1px solid #778;
- font: bold 12px Verdana, sans-serif;
-}
-
-#navlist li {
- list-style: none;
- margin: 0;
- display: inline;
-}
-
-#navlist li a {
- padding: 3px 0.5em;
- margin-left: 3px;
- border: 1px solid #778;
- border-bottom: none;
- background: #b5cadc;
- text-decoration: none;
-}
-
-#navlist li a:link { color: #448; }
-#navlist li a:visited { color: #667; }
-
-#navlist li a:hover {
- color: #000;
- background: #eef;
- border-top: 4px solid #7d95b5;
- border-color: #227;
-}
-
-#navlist #active a {
- background: white;
- border-bottom: 1px solid white;
- border-top: 4px solid;
-}
-
-td { font: 12px Verdana, sans-serif; }
-
-
-fieldset {
- margin-top: 1em;
- padding: 1em;
- background-color: #f3f6f8;
- font:80%/1 sans-serif;
- border:1px solid #ddd;
-}
-
-legend {
- padding: 0.2em 0.5em;
- background-color: #fff;
- border:1px solid #aaa;
- font-size:90%;
- text-align:right;
-}
-
-label {
- display:block;
-}
-
-label.error {
- display:block;
- border-color: red;
- border-width: 1px;
-}
-
-label .field {
- float:left;
- width:25%;
- margin-right:0.5em;
- padding-top:0.2em;
- text-align:right;
- font-weight:bold;
-}
-
-#vlist {
- padding: 0 1px 1px;
- margin-left: 0;
- font: bold 12px Verdana, sans-serif;
- background: gray;
- width: 13em;
-}
-
-#vlist li {
- list-style: none;
- margin: 0;
- border-top: 1px solid gray;
- text-align: left;
-}
-
-#vlist li a {
- display: block;
- padding: 0.25em 0.5em 0.25em 0.75em;
- border-left: 1em solid #7d95b5;
- background: #d0d8e4;
- text-decoration: none;
-}
-
-#vlist li a:hover {
- border-color: #227;
-}
-
-.view .field {
- background-color: #f3f6f8;
- border-left: 1px solid #7695b5;
- border-top: 1px solid #7695b5;
- padding: 1px 10px 0px 2px;
-}
-
-#addnew {
- width: 50%;
- float: left;
-}
-
-#search {
- width: 50%;
- float:right;
-}
-
-.error { color: #d00; }
-
-.action {
- border: 1px outset #7d95b5;
- style:block;
-}
-
-.action:hover {
- color: #fff;
- text-decoration: none;
- background-color: #7d95b5;
-}
-
-.actionform {
- display: inline;
-}
-
-.actionbutton {
- height: 16px;
- width: 40px;
- font-family: sans-serif;
- font-size: 10px;
- border: 1px outset;
- background-color: #fff;
- margin-bottom: 0px;
-}
-
-.actionbutton:hover {
- color: #fff;
- background-color: #7d95b5;
-}
-
-.actions {
- white-space: nowrap;
-}
-
-.field {
- display:inline;
-}
-
-#login { width: 400px; }
-
-#login input[type=text] { width: 150px; }
-#login input[type=password] { width: 150px; }
-
-.pager {
- font: 11px Arial, Helvetica, sans-serif;
- text-align: center;
- border: solid 1px #e2e2e2;
- border-left: 0;
- border-right: 0;
- padding-top: 10px;
- padding-bottom: 10px;
- margin: 0px;
- background-color: #f3f6f8;
-}
-
-.pager a {
- padding: 2px 6px;
- border: solid 1px #ddd;
- background: #fff;
- text-decoration: none;
-}
-
-.pager a:visited {
- padding: 2px 6px;
- border: solid 1px #ddd;
- background: #fff;
- text-decoration: none;
-}
-
-.pager .current-page {
- padding: 2px 6px;
- font-weight: bold;
- vertical-align: top;
-}
-
-.pager a:hover {
- color: #fff;
- background: #7d95b5;
- border-color: #036;
- text-decoration: none;
-}
-
+++ /dev/null
-<h3> Class::DBI meta info for [% classmetadata.name %] </h3>
-[%
- USE this = Class(classmetadata.name);
- USE Dumper; Dumper.dump(this.meta_info);
-%]
+++ /dev/null
-<div id="search">
-<form method="get" action="[% base %]/[% classmetadata.table %]/search/">
-<fieldset>
-<legend>Search</legend>
- [% INCLUDE display_search_inputs; %]
- <input type="submit" name="search" value="search"/>
-</fieldset>
-</form>
-</div>
+++ /dev/null
-[%#
-
-=head1 addnew
-
-This is the interface to adding a new instance of an object. (or a new
-row in the database, if you want to look at it that way) It displays a
-form containing a list of HTML components for each of the columns in the
-table.
-
-=cut
-
-#%]
-
-<div id="addnew">
-<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
- <fieldset>
-<legend>Add a new [% classmetadata.moniker %]</legend>
- [% FOR col = classmetadata.columns %]
- [% NEXT IF col == "id" %]
- <label><span class="field">[% classmetadata.colnames.$col %]</span>
- [%
- SET elem = classmetadata.cgi.$col.clone;
- IF request.action == 'do_edit';
- IF elem.tag == "textarea";
- elem = elem.push_content(request.param(col));
- ELSE;
- elem.attr("value", request.param(col));
- END;
- END;
- elem.as_XML; %]
- </label>
- [% IF errors.$col %]
- <span class="error">[% errors.$col | html %]</span>
- [% END %]
-
- [% END; %]
- <input type="submit" name="create" value="create" />
- <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
-</fieldset>
-</form>
-</div>
+++ /dev/null
-[%#
-
-=head1 edit
-
-This is the edit page. It edits the passed-in object, by displaying a
-form similar to L<addnew> but with the current values filled in.
-
-=cut
-
-#%]
-[% PROCESS macros %]
-[% INCLUDE header %]
-[% INCLUDE title %]
-
-[% IF request.action == 'edit' %]
-[% INCLUDE navbar %]
-[% END %]
-
-[% IF object %]
-<div id="title">Edit a [% classmetadata.moniker %]</div>
-<form action="[% base %]/[% item.table %]/do_edit/[% item.id %]" method="post">
-<fieldset>
-<legend>Edit [% object.name %]</legend>
- [% FOR col = classmetadata.columns;
- NEXT IF col == "id" OR col == classmetadata.table _ "_id";
- '<label><span class="field">';
- classmetadata.colnames.$col || col | ucfirst | replace('_',' '); ":</span>";
- object.to_field(col).as_XML;
- "</label>";
- IF errors.$col;
- '<span class="error">'; errors.$col;'</span>';
- END;
- END %]
- <input type="submit" name="edit" value="edit"/>
- <input type="hidden" name="__form_id" value="[% request.make_random_id %]">
- </fieldset></form>
-
-[% ELSE %]
-
-<div id="addnew">
-<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
-<fieldset>
-<legend>Add a new [% classmetadata.moniker %]</legend>
- [% FOR col = classmetadata.columns %]
- [% NEXT IF col == "id" %]
- <label><span class="field">[% classmetadata.colnames.$col %]</span>
- [%
- SET elem = classmetadata.cgi.$col.clone;
- IF request.action == 'do_edit';
- IF elem.tag == "textarea";
- elem = elem.push_content(request.param(col));
- ELSE;
- elem.attr("value", request.param(col));
- END;
- END;
- elem.as_XML; %]
- </label>
- [% IF errors.$col %]
- <span class="error">[% errors.$col | html %]</span>
- [% END %]
-
- [% END; %]
- <input type="submit" name="create" value="create" />
- <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
-</fieldset>
-</form>
-</div>
-
-[% END %]
-[% INCLUDE footer %]
+++ /dev/null
- </div>
- </body>
-</html>
+++ /dev/null
-[%#
-
-=head1 frontpage
-
-This is the frontpage for your Maypole application.
-It shows a list of all tables it is allowed to display.
-
-=cut
-
-#%]
-[% INCLUDE header %]
-<div id="title">
- [% config.application_name || "A poorly configured Maypole application" %]
-</div>
-<div id="frontpage_list">
-<ul>
-[% FOR table = config.display_tables %]
- <li>
- <a href="[% base %]/[%table%]/list">List by [%table %]</a>
- </li>
-[% END %]
-</ul>
-</div>
-
-[% INCLUDE maypole %]
-
-[% INCLUDE footer %]
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>
- [%
- title || config.application_name ||
- "A poorly configured Maypole application"
- %]
- </title>
- <meta http-equiv="Content-Type" content="text/html; charset=[% request.document_encoding %]" />
- <base href="[% config.uri_base%]"/>
- <link title="Maypole" href="[% config.uri_base %]/maypole.css" type="text/css" rel="stylesheet" />
- </head>
- <body>
- <div class="content">
+++ /dev/null
-[% PROCESS macros %]
-[% INCLUDE header %]
-[% INCLUDE title %]
-[% IF search %]
- <div id="title">Search results</div>
-[% ELSE %]
- <div id="title">Listing of all [% classmetadata.plural %]</div>
-[% END %]
-[% INCLUDE navbar %]
-<div class="list">
- <table id="matrix">
- <tr>
- [% FOR col = classmetadata.list_columns.list;
- NEXT IF col == "id" OR col == classmetadata.table _ "_id";
- "<th>";
- SET additional = "?order=" _ col;
- SET additional = additional _ "&page=" _ pager.current_page
- IF pager;
- SET additional = additional _ "&o2=desc"
- IF col == request.params.order and request.params.o2 != "desc";
- SET action = "list";
- FOR name = classmetadata.columns.list;
- IF request.query.$name;
- SET additional =
- additional _ "&" _ name _ "=" _
- request.params.$name;
- SET action = "search";
- END;
- END;
- USE model_obj = Class request.model_class;
- IF model_obj.find_column(col);
- link(classmetadata.table, action, additional,
- classmetadata.colnames.$col);
- IF col == request.params.order;
- IF request.params.o2 != "desc";
- "↓";
- ELSE;
- "↑";
- END;
- END;
- ELSE;
- classmetadata.colnames.$col || col FILTER ucfirst;
- END;
- "</th>";
- END %]
- <th id="actionth">Actions</th>
- </tr>
- [% SET count = 0;
- FOR item = objects;
- SET count = count + 1;
- "<tr";
- ' class="alternate"' IF count % 2;
- ">";
- display_line(item);
- "</tr>";
- END %]
- </table>
-
-[% INCLUDE pager %]
-[% INCLUDE addnew %]
-[% INCLUDE search_form %]
-</div>
-[% INCLUDE footer %]
+++ /dev/null
-[% PROCESS macros %]
-[% INCLUDE header %]
-[% INCLUDE title %]
-[% user_field = config.auth.user_field || "user" %]
-
- <div id="title">You need to log in</div>
-
- <div id="login">
- [% IF login_error %]
- <div class="error"> [% login_error | html %] </div>
- [% END %]
- <form method="post" action="[% base %]/[% request.path %]">
- <fieldset>
- <legend>Login</legend>
- <label>
- <span class="field">Username:</span>
- <input name="[% user_field %]" type="text" value="[% cgi_params.$user_field | html %]" />
- </label>
- <label>
- <span class="field">Password:</span>
- <input name="password" type="password" value="[% cgi_params.passwrd | html %]"/>
- </label>
- <input type="submit" name="login" value="Submit"/>
- </fieldset>
- </form>
- </div>
-
+++ /dev/null
-[%#
-
-=head1 MACROS
-
-These are some default macros which are used by various templates in the
-system.
-
-=head2 link
-
-This creates an <A HREF="..."> to a command in the Apache::MVC system by
-catenating the base URL, table, command, and any arguments.
-
-#%]
-[%
-MACRO link(table, command, additional, label) BLOCK;
- SET lnk = base _ "/" _ table _ "/" _ command _ "/" _ additional;
- lnk = lnk | uri | html;
- '<a href="' _ lnk _ '">';
- label | html;
- "</a>";
-END;
-%]
-
-[%#
-
-=head2 maybe_link_view
-
-C<maybe_link_view> takes something returned from the database - either
-some ordinary data, or 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.
-
-#%]
-
-[%
-MACRO maybe_link_view(object) BLOCK;
- IF object.isa('Maypole::Model::Base');
- link(object.table, "view", object.id.join('/'), object);
- ELSE;
- object | html ;
- END;
-END;
-%]
-
-[%#
-
-=head2 display_line
-
-C<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 C<id> column by default, and magically
-URLifies columns called C<url>. This may be considered too much magic
-for some.
-
-#%]
-[% MACRO display_line(item) BLOCK;
- FOR col = classmetadata.list_columns;
- NEXT IF col == "id" OR col == classmetadata.table _ "_id";
- col_obj = item.find_column(col);
- "<td>";
- IF col == "url" AND item.url;
- '<a href="'; item.url | html ; '"> '; item.url; '</a>';
- ELSIF col == classmetadata.stringify_column;
- maybe_link_view(item);
- ELSIF col_obj; # its a real column
- accessor = item.accessor_name_for(col_obj) ||
- item.accessor_name(col_obj); # deprecated in cdbi
- maybe_link_view(item.$accessor);
- ELSE;
- item.$col;
- END;
-
- "</td>";
- END;
- '<td class="actions">';
- button(item, "edit");
- button(item, "delete");
- "</td>";
-END %]
-[%#
-
-=head2 button
-
-This is a generic button, which performs an action on an object.
-
-=cut
-
-#%]
-[% MACRO button(obj, action) BLOCK; %]
-[% IF obj.is_public(action) %]
-<form class="actionform" action="[% base %]/[% obj.table %]/[% action %]/[% obj.id.join('/') %]" method="post">
-<div class="field"><input class="actionbutton" type="submit" value="[% action %]" /></div></form>
-[% END %]
-[% END %]
-[%#
-
-=head2 view_related
-
-This takes an object, and looks up the C<related_accessors>; this should
-give a list of accessors that can be called to get a list of related
-objects. It then displays a title for that accessor, (i.e. "Beers" for a
-brewery) calls the accesor, and displays a list of the results.
-
-=cut
-
-#%]
-[%
-MACRO view_related(object) BLOCK;
- FOR accessor = classmetadata.related_accessors.list;
- "<div id=\"subtitle\">"; accessor | ucfirst; "</div>\n";
- "<ul id=\"vlist\">";
- FOR thing = object.$accessor;
- "<li>"; maybe_link_view(thing); "</li>\n";
- END;
- "</ul>";
- END;
-END;
-
-MACRO test_xxxx(myblock) BLOCK;
- FOR col = classmetadata.columns;
- NEXT IF col == "id";
- myblock;
- END;
-END;
-%]
-[%#
-
-=head2 view_item
-
-This takes an object and and displays its properties in a table.
-
-=cut
-
-#%]
-[% MACRO view_item(item) BLOCK; %]
- [% SET string = classmetadata.stringify_column %]
- <div id="title"> [% item.$string | html %]</div>
- [% INCLUDE navbar %]
- <table class="view">
- <tr>
- <td class="field">[% classmetadata.colnames.$string %]</td>
- <td>[% item.$string | html %]</td>
- </tr>
- [% FOR col = classmetadata.columns.list;
- NEXT IF col == "id" OR col == string OR col == classmetadata.table _ "_id";;
- NEXT UNLESS item.$col;
- %]
-[%#
-
-=for doc
-
-It gets the displayable form of a column's name from the hash returned
-from the C<column_names> method:
-
-#%]
- <tr>
- <td class="field">[% classmetadata.colnames.$col ||
- col | ucfirst | replace('_',' '); %]</td>
- <td>
- [% IF col == "url" && item.url; # Possibly too much magic.
- '<a href="'; item.url | html ; '"> '; item.url; '</a>';
- ELSIF item.$col.size > 1; # has_many column
- FOR thing IN item.$col;
- maybe_link_view(thing);", ";
- END;
-
- ELSE;
- maybe_link_view(item.$col);
- END; %]
-[%#
-
-This tests whether or not the returned value is an object, and if so,
-creates a link to a page viewing that object; if not, it just displays
-the text as normal. The object is linked using its stringified name;
-by default this calls the C<name> method, or returns the object's ID
-if there is no C<name> method or other stringification method defined.
-
-=cut
-
-#%]
- </td>
- </tr>
- [% END; %]
- </table>
-[% END %]
+++ /dev/null
-<!-- boxes -->
-<div style='position:absolute;top:220px;left:130px;border-bottom-width:260px;border-right-width:370px;' class='deco1'> </div>
-<div style='position:absolute;top:260px;left:190px;border-bottom-width:170px;border-right-width:530px;' class='deco2'> </div>
-<div style='position:absolute;top:240px;left:220px;border-bottom-width:340px;border-right-width:440px;' class='deco4'> </div>
-<div style='position:absolute;top:160px;left:330px;border-bottom-width:160px;border-right-width:280px;' class='deco1'> </div>
-<div style='position:absolute;top:190px;left:290px;border-bottom-width:430px;border-right-width:130px;' class='deco2'> </div>
-<!-- end of boxes -->
+++ /dev/null
-html {
- padding-right: 0px;
- padding-left: 0px;
- padding-bottom: 0px;
- margin: 0px;
- padding-top: 0px
-}
-body {
- font-family: sans-serif;
- padding-right: 0px;
- padding-left: 0px;
- padding-bottom: 0px;
- margin: 0px; padding-top: 0px;
- background-color: #fff;
-}
-#frontpage_list {
- position: absolute;
- z-index: 5;
- padding: 0px 100px 0px 0px;
- margin:0 0.5%;
- margin-bottom:1em;
- margin-top: 1em;
- background-color: #fff;
-}
-
-#frontpage_list a:hover {
- background-color: #d0d8e4;
-}
-
-#frontpage_list ul {
- list-style-type: square;
-}
-
-.content {
- padding: 12px;
- margin-top: 1px;
- margin-bottom:0px;
- margin-left: 15px;
- margin-right: 15px;
- border-color: #000000;
- border-top: 0px;
- border-bottom: 0px;
- border-left: 1px;
- border-right: 1px;
-}
-
-A {
- text-decoration: none;
- color:#225
-}
-A:hover {
- text-decoration: underline;
- color:#222
-}
-
-#title {
- z-index: 6;
- width: 100%;
- height: 18px;
- margin-top: 10px;
- font-size: 90%;
- border-bottom: 1px solid #ddf;
- text-align: left;
-}
-
-#subtitle {
- postion: absolute;
- z-index: 6;
- padding: 10px;
- margin-top: 2em;
- height: 18px;
- text-align: left;
- background-color: #fff;
-}
-
-input[type=text] {
- height: 16px;
- width: 136px;
- font-family: sans-serif;
- font-size: 11px;
- color: #2E415A;
- padding: 0px;
- margin-bottom: 5px;
-}
-
-input[type=submit] {
- height: 18px;
- width: 60px;
- font-family: sans-serif;
- font-size: 11px;
- border: 1px outset;
- background-color: #fff;
- padding: 0px 0px 2px 0px;
- margin-bottom: 5px;
-}
-
-input:hover[type=submit] {
- color: #fff;
- background-color: #7d95b5;
-}
-
-textarea {
- width: 136px;
- font-family: sans-serif;
- font-size: 11px;
- color: #2E415A;
- padding: 0px;
- margin-bottom: 5px;
-}
-
-select {
- height: 16px;
- width: 140px;
- font-family: sans-serif;
- font-size: 12px;
- color: #202020;
- padding: 0px;
- margin-bottom: 5px;
-}
-
-.deco1 {
- font-size: 0px;
- z-index:1;
- border:0px;
- border-style:solid;
- border-color:#4d6d99;
- background-color:#4d6d99;
-}
-
-.deco2 {
- z-index:2;
- border:0px;
- border-style:solid;
- border-color:#627ea5;
- background-color:#627ea5;
-}
-
-
-.deco3 {
- z-index:3;
- border:0px;
- border-style:solid;
- border-color:#7d95b5;
- background-color:#7d95b5;
-}
-
-.deco4 {
- z-index:4;
- border:0px;
- border-style:solid;
- border-color:#d0d8e4;
- background-color:#d0d8e4;
-}
-
-
-table {
- border: 0px solid;
- background-color: #ffffff;
-}
-
-#matrix { width: 100%; }
-
-#matrix th {
- background-color: #b5cadc;
- border: 1px solid #778;
- font: bold 12px Verdana, sans-serif;
-}
-
-#matrix #actionth {
- width: 1px;
- padding: 0em 1em 0em 1em;
-}
-
-#matrix tr.alternate { background-color:#e3eaf0; }
-#matrix tr:hover { background-color: #b5cadc; }
-#matrix td { font: 12px Verdana, sans-serif; }
-
-#navlist {
- padding: 3px 0;
- margin-left: 0;
- margin-top:3em;
- border-bottom: 1px solid #778;
- font: bold 12px Verdana, sans-serif;
-}
-
-#navlist li {
- list-style: none;
- margin: 0;
- display: inline;
-}
-
-#navlist li a {
- padding: 3px 0.5em;
- margin-left: 3px;
- border: 1px solid #778;
- border-bottom: none;
- background: #b5cadc;
- text-decoration: none;
-}
-
-#navlist li a:link { color: #448; }
-#navlist li a:visited { color: #667; }
-
-#navlist li a:hover {
- color: #000;
- background: #eef;
- border-top: 4px solid #7d95b5;
- border-color: #227;
-}
-
-#navlist #active a {
- background: white;
- border-bottom: 1px solid white;
- border-top: 4px solid;
-}
-
-td { font: 12px Verdana, sans-serif; }
-
-
-fieldset {
- margin-top: 1em;
- padding: 1em;
- background-color: #f3f6f8;
- font:80%/1 sans-serif;
- border:1px solid #ddd;
-}
-
-legend {
- padding: 0.2em 0.5em;
- background-color: #fff;
- border:1px solid #aaa;
- font-size:90%;
- text-align:right;
-}
-
-label {
- display:block;
-}
-
-label.error {
- border-color: red;
- border-width: 1px;
-}
-
-label .field {
- float:left;
- width:25%;
- margin-right:0.5em;
- padding-top:0.2em;
- text-align:right;
- font-weight:bold;
-}
-
-#vlist {
- padding: 0 1px 1px;
- margin-left: 0;
- font: bold 12px Verdana, sans-serif;
- background: gray;
- width: 13em;
-}
-
-#vlist li {
- list-style: none;
- margin: 0;
- border-top: 1px solid gray;
- text-align: left;
-}
-
-#vlist li a {
- display: block;
- padding: 0.25em 0.5em 0.25em 0.75em;
- border-left: 1em solid #7d95b5;
- background: #d0d8e4;
- text-decoration: none;
-}
-
-#vlist li a:hover {
- border-color: #227;
-}
-
-.view .field {
- background-color: #f3f6f8;
- border-left: 1px solid #7695b5;
- border-top: 1px solid #7695b5;
- padding: 1px 10px 0px 2px;
-}
-
-#addnew {
- width: 50%;
- float: left;
-}
-
-#search {
- width: 50%;
- float:right;
-}
-
-.error { color: #d00; }
-
-.action {
- border: 1px outset #7d95b5;
- style:block;
-}
-
-.action:hover {
- color: #fff;
- text-decoration: none;
- background-color: #7d95b5;
-}
-
-.actionform {
- display: inline;
-}
-
-.actionbutton {
- height: 16px;
- width: 40px;
- font-family: sans-serif;
- font-size: 10px;
- border: 1px outset;
- background-color: #fff;
- margin-bottom: 0px;
-}
-
-.actionbutton:hover {
- color: #fff;
- background-color: #7d95b5;
-}
-
-.actions {
- white-space: nowrap;
-}
-
-.field {
- display:inline;
-}
-
-#login { width: 400px; }
-
-#login input[type=text] { width: 150px; }
-#login input[type=password] { width: 150px; }
-
-.pager {
- font: 11px Arial, Helvetica, sans-serif;
- text-align: center;
- border: solid 1px #e2e2e2;
- border-left: 0;
- border-right: 0;
- padding-top: 10px;
- padding-bottom: 10px;
- margin: 0px;
- background-color: #f3f6f8;
-}
-
-.pager a {
- padding: 2px 6px;
- border: solid 1px #ddd;
- background: #fff;
- text-decoration: none;
-}
-
-.pager a:visited {
- padding: 2px 6px;
- border: solid 1px #ddd;
- background: #fff;
- text-decoration: none;
-}
-
-.pager .current-page {
- padding: 2px 6px;
- font-weight: bold;
- vertical-align: top;
-}
-
-.pager a:hover {
- color: #fff;
- background: #7d95b5;
- border-color: #036;
- text-decoration: none;
-}
-
+++ /dev/null
-[%#
-
-=head1 navbar
-
-This is a navigation bar to go across the page. (Or down the side, or
-whatetver you want to do with it.) It displays all the tables which are
-accessible, with a link to the list page for each one.
-
-#%]
-[% PROCESS macros %]
-<div id="navcontainer">
-<ul id="navlist">
-[%
- FOR table = config.display_tables;
- '<li '; 'id="active"' IF table == classmetadata.table; '>';
- # Hack
- link(table, "list", "", table);
- '</li>';
- END;
-%]
-</ul>
-</div>
+++ /dev/null
-[%#
-
-=head1 pager
-
-This controls the pager display at the bottom (by default) of the list
-and search views. It expects a C<pager> template argument which responds
-to the L<Data::Page> interface.
-
-#%]
-[%
-IF pager AND pager.first_page != pager.last_page;
-%]
-<p class="pager">Pages:
-[%
- UNLESS pager_action;
- SET pager_action = request.action;
- END;
-
- SET begin_page = pager.current_page - 10;
- IF begin_page < 1;
- SET begin_page = pager.first_page;
- END;
- SET end_page = pager.current_page + 10;
- IF pager.last_page < end_page;
- SET end_page = pager.last_page;
- END;
- FOREACH num = [begin_page .. end_page];
- IF num == pager.current_page;
- "<span class='current-page'>"; num; "</span>";
- ELSE;
- SET label = num;
- SET args = "?page=" _ num;
- SET args = args _ "&order=" _ request.params.order
- IF request.params.order;
- SET args = args _ "&o2=desc"
- IF request.params.o2 == "desc";
- FOR col = classmetadata.columns.list;
- IF request.params.$col;
- SET args = args _ "&" _ col _ "=" _ request.params.$col;
- SET action = "search";
- END;
- END;
- link(classmetadata.table, pager_action, args, label);
- END;
- END;
-%]
-</p>
-[% END %]
+++ /dev/null
-<div id="search">
-<form method="get" action="[% base %]/[% classmetadata.moniker %]/search/">
-<fieldset>
-<legend>Search</legend>
- [% FOR col = classmetadata.columns;
- NEXT IF col == "id" OR col == classmetadata.table _ "_id";
- %]
- <label>
- <span class="field">[% classmetadata.colnames.$col; %]</span>
- [% SET element = classmetadata.cgi.$col;
- IF element.tag == "select";
- USE element_maker = Class("HTML::Element");
- SET element = element.unshift_content(
- element_maker.new("option", value," "));
- END;
- element.as_XML; %]
- </label>
- [% END; %]
- <input type="submit" name="search" value="search"/>
- </fieldset>
-</form>
-</div>
+++ /dev/null
-<div id="search">
-<form method="get" action="[% base %]/[% classmetadata.table %]/search/">
-<fieldset>
-<legend>Search</legend>
- [% INCLUDE display_search_inputs; %]
- <input type="submit" name="search" value="search"/>
-</fieldset>
-</form>
-</div>
+++ /dev/null
- <a href="[% base %]/frontpage">[% config.application_name %]</a>
+++ /dev/null
-[%#
-
-=for doc
-
-The C<view> template takes some objects (usually just one) from
-C<objects> and displays the object's properties in a table.
-
-=cut
-
-#%]
-[% PROCESS macros %]
-[% INCLUDE header %]
-[% view_item(object); %]
-[%#
-
-=for doc
-
-The C<view> template also displays a list of other objects related to the first
-one via C<has_many> style relationships; this is done by calling the
-C<related_accessors> method - see L<Model/related_accessors> - to return
-a list of has-many accessors. Next it calls each of those accessors, and
-displays the results in a table.
-
-#%]
- <br /><a href="[%base%]/[%object.table%]/list">Back to listing</a>
-[% view_related(object); %]
-
-[%
- button(object, "edit");
- button(object, "delete");
-%]
-[% INCLUDE footer %]
--- /dev/null
+package BeerDB;
+use Maypole::Application;
+use Class::DBI::Loader::Relationship;
+
+sub debug { $ENV{BEERDB_DEBUG} || 0 }
+# This is the sample application. Change this to the path to your
+# database. (or use mysql or something)
+use constant DBI_DRIVER => 'SQLite';
+use constant DATASOURCE => $ENV{BEERDB_DATASOURCE} || 't/beerdb.db';
+
+
+BEGIN {
+ my $dbi_driver = DBI_DRIVER;
+ if ($dbi_driver =~ /^SQLite/) {
+ die sprintf "SQLite datasource '%s' not found, correct the path or "
+ . "recreate the database by running Makefile.PL", DATASOURCE
+ unless -e DATASOURCE;
+ eval "require DBD::SQLite";
+ if ($@) {
+ eval "require DBD::SQLite2" and $dbi_driver = 'SQLite2';
+ }
+ }
+ BeerDB->setup(join ':', "dbi", $dbi_driver, DATASOURCE);
+}
+
+# Give it a name.
+BeerDB->config->application_name('The Beer Database');
+
+# Change this to the root of the web site for your maypole application.
+BeerDB->config->uri_base( $ENV{BEERDB_BASE} || "http://localhost/beerdb/" );
+
+# Change this to the htdoc root for your maypole application.
+
+my @root= ('t/templates');
+push @root,$ENV{BEERDB_TEMPLATE_ROOT} if ($ENV{BEERDB_TEMPLATE_ROOT});
+BeerDB->config->template_root( [@root] );
+# Specify the rows per page in search results, lists, etc : 10 is a nice round number
+BeerDB->config->rows_per_page(10);
+
+# Handpumps should not show up.
+BeerDB->config->display_tables([qw[beer brewery pub style]]);
+BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
+BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
+BeerDB::Beer->untaint_columns(
+ printable => [qw/abv name price notes url/],
+ integer => [qw/style brewery score/],
+ date =>[ qw/tasted/],
+);
+BeerDB::Pub->untaint_columns(printable => [qw/name notes url/]);
+
+# Required Fields
+BeerDB->config->{brewery}{required_cols} = [qw/name/];
+BeerDB->config->{style}{required_cols} = [qw/name/];
+BeerDB->config->{beer}{required_cols} = [qw/brewery name price/];
+BeerDB->config->{pub}{required_cols} = [qw/name/];
+
+BeerDB->config->{loader}->relationship($_) for (
+ "a brewery produces beers",
+ "a style defines beers",
+ "a pub has beers on handpumps");
+
+# For testing classmetadata
+sub BeerDB::Beer::classdata :Exported {};
+sub BeerDB::Beer::list_columns { return qw/score name price style brewery url/};
+
+1;
--- /dev/null
+package BeerDB::Base;
+use strict;
+use warnings;
+
+sub floob {}
+
+1;
--- /dev/null
+package BeerDB::Beer;
+use strict;
+use warnings;
+
+# do this to test we get the expected @ISA after setup_model()
+use base 'BeerDB::Base';
+
+sub fooey : Exported {}
+
+1;
--- /dev/null
+CREATE TABLE style (
+ id integer primary key auto_increment,
+ name varchar(60),
+ notes text
+);
+
+CREATE TABLE pub (
+ id integer primary key auto_increment,
+ name varchar(60),
+ url varchar(120),
+ notes text
+);
+
+CREATE TABLE handpump (
+ id integer primary key auto_increment,
+ beer integer,
+ pub integer
+);
+
+CREATE TABLE beer (
+ id integer primary key auto_increment,
+ brewery integer,
+ style integer,
+ name varchar(30),
+ url varchar(120),
+ score integer(2),
+ price varchar(12),
+ abv varchar(10),
+ notes text,
+ tasted date
+);
+
+CREATE TABLE brewery (
+ id integer primary key auto_increment,
+ name varchar(30),
+ url varchar(50),
+ notes text
+);
--- /dev/null
+package BeerDB;
+use Maypole::Application;
+use Class::DBI::Loader::Relationship;
+
+sub debug { $ENV{BEERDB_DEBUG} || 0 }
+# This is the sample application. Change this to the path to your
+# database. (or use mysql or something)
+use constant DBI_DRIVER => 'SQLite';
+use constant DATASOURCE => '/home/peter/Desktop/maypolebeer/beerdb';
+
+BeerDB->config->model('BeerDB::Base');
+
+BeerDB->setup("dbi:mysql:beerdb",'root', '');
+
+# Give it a name.
+BeerDB->config->application_name('The Beer Database');
+
+# Change this to the root of the web site for your maypole application.
+BeerDB->config->uri_base( $ENV{BEERDB_BASE} || "http://localhost/beerdb/" );
+
+# Change this to the htdoc root for your maypole application.
+
+my @root= ('/home/peter/Desktop/maypolebeer/templates');
+push @root,$ENV{BEERDB_TEMPLATE_ROOT} if ($ENV{BEERDB_TEMPLATE_ROOT});
+BeerDB->config->template_root( [@root] );
+# Specify the rows per page in search results, lists, etc : 10 is a nice round number
+BeerDB->config->rows_per_page(10);
+
+# Let TT templates recursively include themselves
+BeerDB->config->{view_options} = { RECURSION => 1, };
+
+# Handpumps should not show up.
+BeerDB->config->display_tables([qw[beer brewery pub style drinker pint person]]);
+# Access handpumps if want
+BeerDB->config->ok_tables([ @{BeerDB->config->display_tables}, qw[handpump]]);
+
+BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
+BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
+BeerDB::Beer->untaint_columns(
+ printable => [qw/abv name price notes/],
+ integer => [qw/style brewery score/],
+ date =>[ qw/tasted/],
+);
+BeerDB::Pub->untaint_columns(printable => [qw/name notes url/]);
+BeerDB::Drinker->untaint_columns( printable => [qw/handle created/] );
+BeerDB::Pint->untaint_columns( printable => [qw/date_and_time/]);
+
+
+# Required Fields
+BeerDB->config->{brewery}{required_cols} = [qw/name url/];
+BeerDB->config->{style}{required_cols} = [qw/name/];
+BeerDB->config->{beer}{required_cols} = [qw/brewery name price/];
+BeerDB->config->{pub}{required_cols} = [qw/name/];
+BeerDB->config->{drinker}{required_cols} = [qw/handle person/];
+BeerDB->config->{pint}{required_cols} = [qw/drinker handpump/];
+BeerDB->config->{person}{required_cols} = [qw/first_name sur_name dob email/];
+
+# Columns to display
+sub BeerDB::Handpump::display_columns { qw/pub beer/ }
+
+BeerDB->config->{loader}->relationship($_) for (
+ "a brewery produces beers",
+ "a style defines beers",
+ "a pub has beers on handpumps",
+ "a handpump defines pints",
+ "a drinker drinks pints",);
+
+# For testing classmetadata
+#sub BeerDB::Beer::classdata :Exported {};
+sub BeerDB::Beer::list_columns { return qw/score name price style brewery/};
+
+sub BeerDB::Handpump::stringify_self {
+ my $self = shift;
+ return $self->beer . " @ " . $self->pub;
+}
+
+
+1;
--- /dev/null
+package BeerDB::Base;
+use base qw/Maypole::Model::CDBI/;
+use strict;
+use warnings;
+use Data::Dumper;
+
+# Overide list to add display_columns to cgi
+# Perhaps do this in AsForm?
+sub list : Exported {
+ use Data::Dumper;
+ my ($self, $r) = @_;
+ $self->SUPER::list($r);
+ my %cols = map { $_ => 1 } $self->columns, $self->display_columns;
+ my @cols = keys %cols;
+ $r->template_args->{classmetadata}{cgi} = { $self->to_cgi(@cols) };
+}
+
+# Override view to make inputs and process form to add to related
+sub view : Exported {
+ my ($self, $r, $obj) = @_;
+ $self->_croak( "Object method only") unless $obj;
+
+ if ($r->params->{submit}) {
+ my @related = $obj->add_to_from_cgi($r, { required => [$self->related ]});
+ if (my $errs = $obj->cgi_update_errors) {
+ $r->template_args->{errors} = $errs;
+ }
+ }
+
+ # Inputs to add to related on the view page
+ # Now done on the view template
+ # my %cgi = $self->to_cgi($self->related);
+ #$r->template_args->{classmetadata}{cgi} = \%cgi ;
+}
+
+
+# Template switcheroo bug bit me -- was seeing view page but the view action was never
+# being executed after an edit.
+sub do_edit : Exported {
+ my ($self, $r) = (shift, shift);
+ $self->SUPER::do_edit($r, @_);
+ if (my $obj = $r->object) {
+ my $url = $r->config->uri_base . "/" . $r->table . "/view/" . $obj->id;
+ $r->redirect_request(url => $url);
+ }
+}
+
+sub metadata: Exported {}
+
+
+1;
--- /dev/null
+package BeerDB::Beer;
+use strict;
+use warnings;
+
+# do this to test we get the expected @ISA after setup_model()
+use base 'BeerDB::Base';
+
+sub fooey : Exported {}
+
+1;
--- /dev/null
+package BeerDB::Brewery;
+use strict;
+use warnings;
+
+use Data::Dumper;
+
+sub display_columns { qw/name url beers/ } # note has_man beers
+sub list_columns { qw/name url/ }
+
+1;
--- /dev/null
+package BeerDB::Drinker;
+use strict;
+use warnings;
+
+use Data::Dumper;
+
+__PACKAGE__->columns('Stringify' => qw/handle/);
+
+# A drinker is a person but we do not want to select who that person is
+# from a list because this is a 1:1 relationship rather than a M:1.
+# The no_select option tells AsForm not to bother making a select box
+
+__PACKAGE__->has_a(person => 'BeerDB::Person', no_select => 1);
+
+# Drinker drinks many beers at pubs if they are lucky. I like to specify the
+# name of the foreign key unless i can control the order that the
+# cdbi classes are created. CDBI does not guess very well the fk column.
+
+#__PACKAGE__->has_many(pints => 'BeerDB::Pint', 'drinker');
+
+# When we create a drinker we want to create a person as well
+# So tell AsForm to display the person inputs too.
+
+sub display_columns { qw/person handle/ }
+sub list_columns { qw/person handle/ }
+# AsForm and templates may check for search_colums when making
+#sub search_columns { qw/person handle/ }
+
+# We need to tweak the cgi inputs a little.
+# Since list is where addnew is, override that.
+# Person is a has_a rel and AsForm wont make foreign inputs automatically so
+# we manually do it.
+
+sub list : Exported {
+ my ($self, $r) = @_;
+ $self->SUPER::list($r);
+ my %cgi = $self->to_cgi;
+ $cgi{person} = $self->to_field('person', 'foreign_inputs');
+ $r->template_args->{classmetadata}{cgi} = \%cgi;
+ #$r->template_args->{classmetadata}{search_cgi} = $self->search_inputs;
+}
+
+
+
+
+#sub foreign_input_delimiter { '__IMODDD__'}
+
+1;
--- /dev/null
+CREATE TABLE style (
+ id integer primary key auto_increment,
+ name varchar(60),
+ notes text
+);
+
+CREATE TABLE pub (
+ id integer primary key auto_increment,
+ name varchar(60),
+ url varchar(120),
+ notes text
+);
+
+CREATE TABLE handpump (
+ id integer primary key auto_increment,
+ beer integer,
+ pub integer
+);
+
+CREATE TABLE beer (
+ id integer primary key auto_increment,
+ brewery integer,
+ style integer,
+ name varchar(30),
+ score integer(2),
+ price varchar(12),
+ abv varchar(10),
+ notes text,
+ tasted date
+);
+
+CREATE TABLE brewery (
+ id integer primary key auto_increment,
+ name varchar(30),
+ url varchar(50),
+ notes text
+);
+
+CREATE TABLE drinker (
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+ person INTEGER UNSIGNED NOT NULL,
+ handle VARCHAR(20) NOT NULL,
+ created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY(id),
+ INDEX drinker_FKIndex1(person)
+);
+
+CREATE TABLE person (
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+ first_name VARCHAR(50) NULL,
+ sur_name VARCHAR(50) NULL,
+ dob DATE NULL,
+ username VARCHAR(20) NULL,
+ password VARCHAR(20) NULL,
+ email VARCHAR(255) NULL,
+ PRIMARY KEY(id)
+);
+
+CREATE TABLE pint (
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+ drinker INTEGER UNSIGNED NOT NULL,
+ handpump INTEGER UNSIGNED NOT NULL,
+ date_and_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY(id)
+);
+
+
--- /dev/null
+[%#
+
+=head1 addnew
+
+This is the interface to adding a new instance of an object. (or a new
+row in the database, if you want to look at it that way) It displays a
+form containing a list of HTML components for each of the columns in the
+table.
+
+=cut
+
+#%]
+[% tbl = classmetadata.table; %]
+
+<div id="addnew">
+<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
+<fieldset>
+<legend>Add a new [% config.TABLES.$tbl.singular || tbl | ucfirst | replace('_',' '); %] </legend>
+ [% INCLUDE display_inputs; %]
+ <input type="submit" name="create" value="create" />
+ <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
+</fieldset>
+</form>
+</div>
--- /dev/null
+[%#
+
+=head1 display_inputs
+
+This *RECURSIVELY* displays inputs for a hash of html elements
+
+Vars it needs:
+ classmetadata-- the hash of bunch of data:
+ cgi -- inputs keyed on column names
+ table -- table inputs are for
+ columns -- list in order want displayed inputs
+ colnames -- hash of what to label inputs
+
+errors -- hash of errors keyed on columns
+
+
+TODO -- make it recognize a general submit button for redisplaying
+values on errors
+
+=cut
+
+#
+%]
+
+[% # some variables
+ foreign = [];
+ names = [];
+ # get hash of related classes keyed on accessor for Foreign Inputs
+ USE this = Class(classmetadata.name);
+ tbl = classmetadata.table;
+ required = { };
+ FOR c IN request.config.$tbl.required_cols;
+ required.$c = 1;
+ END;
+
+%]
+
+[%
+SET heading_shown = 0;
+FOR col = classmetadata.columns;
+ NEXT IF !classmetadata.cgi.$col;
+ NEXT IF col == "id" OR col == classmetadata.table _ "_id";
+ # Display foreign inputs last
+ IF (mykeys = classmetadata.cgi.$col.keys);
+ foreign.push(col);
+ names.push(classmetadata.colnames.$col);
+ NEXT;
+ END;
+ IF ! heading_shown;
+ heading = classmetadata.moniker | ucfirst;
+ "<h4> $heading </h4>";
+ SET heading_shown = 1;
+ END;
+%]
+
+[% # Base case starts here
+
+ SET elem = classmetadata.cgi.$col; #.clone; # not sure why clone
+ IF elem.type == 'hidden';
+ elem.as_XML;
+ NEXT;
+ ELSIF cgi_params;
+ param_col = col_prefix _ col;
+ IF elem.tag == "textarea";
+ elem = elem.push_content(cgi_params.$param_col);
+ ELSIF elem.tag == "select";
+ oldval = set_selected(elem, cgi_params.$col);
+ ELSE;
+ oldval = elem.attr("value", cgi_params.$param_col);
+ END;
+ END;
+%]
+
+ <label>
+ [% indicator = '';
+ SET indicator = '*' IF (required.$col);
+ %]
+ <span class="field">
+ [% indicator _ classmetadata.colnames.$col ||
+ col | replace('_',' ') | ucfirst %]
+ </span>
+ [% elem.as_XML; %]
+ </label>
+
+ [% IF errors.$col %]
+ <span class="error">[% errors.$col | html %]</span>
+ [% END %]
+[% END; %]
+
+<!-- Display the differnt component inputs -->
+
+[% USE this = Class(classmetadata.name);
+ FOR col IN foreign;
+ # has_many mapping throws a stick in our spokes because related_class returns the mapped
+ # class. Sometimes we just want the has_many class.
+
+ # In case of Pub Handpumps maps to Beer and we want to add Handpump to Pub, we dont
+ # want the mapped data .
+ # In case of "Create New Handpump" -- we want the mapped data probably so we get
+ # Beer inputs and Pub select box.
+
+ fclass_rel_meta = this.related_meta(request, col);
+ fclass = fclass_rel_meta.foreign_class; # ignor args.mapping
+ fclass_meta = this.get_classmetadata(fclass);
+ fclass_meta.cgi = classmetadata.cgi.$col;
+ # USE Dumper; Dumper.dump(fclass_meta);
+ INCLUDE display_inputs
+ col_prefix = col _ "__AF__" _ col_prefix
+ errors = errors.$col
+ heading = names.shift
+ classmetadata = fclass_meta; # localize
+ END;
+%]
+
--- /dev/null
+[%#
+
+=head1 display_search_inputs
+
+This displays inputs for search page. Override in individual class template
+directories as needed.
+
+Vars it needs:
+classmetadata-- the hash of inputs keyed on column names
+errors -- hash of errors keyed on columns
+=cut
+
+#%]
+
+[% IF errors.FATAL; "FATAL ERROR: "; errors.FATAL; "<br>"; END %]
+
+[% USE this = Class(classmetadata.name);
+ SET srch_fields = classmetadata.search_columns ||
+ classmetadata.columns;
+ SET cgi = classmetadata.cgi;
+ SET delimiter = this.foreign_input_delimiter;
+ FOR field IN srch_fields;
+ NEXT IF !cgi.$field;
+ # Recursivly call this tmeplate if we have foreign field
+ # (hash of foreign inputs should come with it)
+ IF ( cgi.$field.keys );
+ fclass = this.related_class(request, field);
+ fclass_meta = this.get_classmetadata(fclass);
+ fclass_meta.cgi = cgi.$field;
+ tbl = fclass_meta.table;
+ INCLUDE display_search_inputs
+ col_prefix = col _ delimiter _ col_prefix
+ classmetadata = fclass_meta;
+ NEXT;
+ END;
+
+ NEXT IF field == 'id' OR field == classmetadata.table _ 'id';
+ SET element = cgi.$field;
+%]
+
+<label>
+ <span class="field">
+ [%
+ classmetadata.colnames.$field || field | ucfirst | replace('_',' '); %]
+ </span>
+ [% IF element.tag == "select";
+ # set the previous value
+ IF cgi_params.exists(field);
+ set_selected(element, cgi_params.$field);
+ END;
+
+ END;
+ IF element.tag == "input"; # wipe out any default value
+ old_val = element.attr('value', '');
+ END;
+
+
+ element.as_XML;
+ %]
+</label>
+[% END; %]
+
+
--- /dev/null
+[%#
+
+=head1 edit
+
+This is the edit page. It edits the passed-in object, by displaying a
+form similar to L<addnew> but with the current values filled in.
+
+=cut
+
+#%]
+[% PROCESS macros %]
+[% INCLUDE header %]
+[% INCLUDE title %]
+
+[% IF request.action == 'edit' %]
+[% INCLUDE navbar %]
+[% END %]
+
+[% IF objects.size %]
+<div id="title">Edit a [% classmetadata.moniker %]</div>
+[% FOR item = objects; %]
+<form action="[% base %]/[% item.table %]/do_edit/[% item.id %]" method="post">
+<fieldset>
+<legend>Edit [% item.name %]</legend>
+[% FOR col = classmetadata.columns;
+ NEXT IF col == "id" OR col == classmetadata.table _ "_id";
+ '<label><span class="field">';
+ classmetadata.colnames.$col || col | ucfirst | replace('_',' '); ":</span>";
+ item.to_field(col).as_XML;
+ "</label>";
+ IF errors.$col;
+ '<span class="error">'; errors.$col;'</span>';
+ END;
+ END %]
+ <input type="submit" name="edit" value="edit"/>
+ <input type="hidden" name="__form_id" value="[% request.make_random_id %]">
+ </fieldset></form>
+
+ [% END %]
+[% ELSE %]
+
+<div id="addnew">
+<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
+<fieldset>
+<legend>Add a new [% classmetadata.moniker %]</legend>
+ [% FOR col = classmetadata.columns %]
+ [% NEXT IF col == "id" %]
+ <label><span class="field">[% classmetadata.colnames.$col %]</span>
+ [%
+ SET elem = classmetadata.cgi.$col.clone;
+ IF request.action == 'do_edit';
+ IF elem.tag == "textarea";
+ elem = elem.push_content(request.param(col));
+ ELSE;
+ elem.attr("value", request.param(col));
+ END;
+ END;
+ elem.as_XML; %]
+ </label>
+ [% IF errors.$col %]
+ <span class="error">[% errors.$col | html %]</span>
+ [% END %]
+
+ [% END; %]
+ <input type="submit" name="create" value="create" />
+ <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
+</fieldset>
+</form>
+</div>
+
+[% END %]
+[% INCLUDE footer %]
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>
+ [%
+ title || config.application_name ||
+ "A poorly configured Maypole application"
+ %]
+ </title>
+ <meta http-equiv="Content-Type" content="text/html; charset=[% request.document_encoding %]" />
+ <base href="[% config.uri_base%]"/>
+ <link title="Maypole" href="maypole.css" type="text/css" rel="stylesheet" />
+ </head>
+ <body>
+ <div class="content">
--- /dev/null
+html {
+ padding-right: 0px;
+ padding-left: 0px;
+ padding-bottom: 0px;
+ margin: 0px;
+ padding-top: 0px
+}
+body {
+ font-family: sans-serif;
+ padding-right: 0px;
+ padding-left: 0px;
+ padding-bottom: 0px;
+ margin: 0px; padding-top: 0px;
+ background-color: #fff;
+}
+#frontpage_list {
+ position: absolute;
+ z-index: 5;
+ padding: 0px 100px 0px 0px;
+ margin:0 0.5%;
+ margin-bottom:1em;
+ margin-top: 1em;
+ background-color: #fff;
+}
+
+#frontpage_list a:hover {
+ background-color: #d0d8e4;
+}
+
+#frontpage_list ul {
+ list-style-type: square;
+}
+
+.content {
+ padding: 12px;
+ margin-top: 1px;
+ margin-bottom:0px;
+ margin-left: 15px;
+ margin-right: 15px;
+ border-color: #000000;
+ border-top: 0px;
+ border-bottom: 0px;
+ border-left: 1px;
+ border-right: 1px;
+}
+
+A {
+ text-decoration: none;
+ color:#225
+}
+A:hover {
+ text-decoration: underline;
+ color:#222
+}
+
+#title {
+ z-index: 6;
+ width: 100%;
+ height: 18px;
+ margin-top: 10px;
+ font-size: 90%;
+ border-bottom: 1px solid #ddf;
+ text-align: left;
+}
+
+#subtitle {
+ postion: absolute;
+ z-index: 6;
+ padding: 10px;
+ margin-top: 2em;
+ height: 18px;
+ text-align: left;
+ background-color: #fff;
+}
+
+input[type=text] {
+ height: 16px;
+ width: 136px;
+ font-family: sans-serif;
+ font-size: 11px;
+ color: #2E415A;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+input[type=submit] {
+ height: 18px;
+ width: 60px;
+ font-family: sans-serif;
+ font-size: 11px;
+ border: 1px outset;
+ background-color: #fff;
+ padding: 0px 0px 2px 0px;
+ margin-bottom: 5px;
+}
+
+input:hover[type=submit] {
+ color: #fff;
+ background-color: #7d95b5;
+}
+
+textarea {
+ width: 136px;
+ font-family: sans-serif;
+ font-size: 11px;
+ color: #2E415A;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+select {
+ height: 16px;
+ width: 140px;
+ font-family: sans-serif;
+ font-size: 12px;
+ color: #202020;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+.deco1 {
+ font-size: 0px;
+ z-index:1;
+ border:0px;
+ border-style:solid;
+ border-color:#4d6d99;
+ background-color:#4d6d99;
+}
+
+.deco2 {
+ z-index:2;
+ border:0px;
+ border-style:solid;
+ border-color:#627ea5;
+ background-color:#627ea5;
+}
+
+
+.deco3 {
+ z-index:3;
+ border:0px;
+ border-style:solid;
+ border-color:#7d95b5;
+ background-color:#7d95b5;
+}
+
+.deco4 {
+ z-index:4;
+ border:0px;
+ border-style:solid;
+ border-color:#d0d8e4;
+ background-color:#d0d8e4;
+}
+
+
+table {
+ border: 0px solid;
+ background-color: #ffffff;
+}
+
+#matrix { width: 100%; }
+
+#matrix th {
+ background-color: #b5cadc;
+ border: 1px solid #778;
+ font: bold 12px Verdana, sans-serif;
+}
+
+#matrix #actionth {
+ width: 1px;
+ padding: 0em 1em 0em 1em;
+}
+
+#matrix tr.alternate { background-color:#e3eaf0; }
+#matrix tr:hover { background-color: #b5cadc; }
+#matrix td { font: 12px Verdana, sans-serif; }
+
+#navlist {
+ padding: 3px 0;
+ margin-left: 0;
+ margin-top:3em;
+ border-bottom: 1px solid #778;
+ font: bold 12px Verdana, sans-serif;
+}
+
+#navlist li {
+ list-style: none;
+ margin: 0;
+ display: inline;
+}
+
+#navlist li a {
+ padding: 3px 0.5em;
+ margin-left: 3px;
+ border: 1px solid #778;
+ border-bottom: none;
+ background: #b5cadc;
+ text-decoration: none;
+}
+
+#navlist li a:link { color: #448; }
+#navlist li a:visited { color: #667; }
+
+#navlist li a:hover {
+ color: #000;
+ background: #eef;
+ border-top: 4px solid #7d95b5;
+ border-color: #227;
+}
+
+#navlist #active a {
+ background: white;
+ border-bottom: 1px solid white;
+ border-top: 4px solid;
+}
+
+td { font: 12px Verdana, sans-serif; }
+
+
+fieldset {
+ margin-top: 1em;
+ padding: 1em;
+ background-color: #f3f6f8;
+ font:80%/1 sans-serif;
+ border:1px solid #ddd;
+}
+
+legend {
+ padding: 0.2em 0.5em;
+ background-color: #fff;
+ border:1px solid #aaa;
+ font-size:90%;
+ text-align:right;
+}
+
+label {
+ display:block;
+}
+
+label.error {
+ display:block;
+ border-color: red;
+ border-width: 1px;
+}
+
+label .field {
+ float:left;
+ width:25%;
+ margin-right:0.5em;
+ padding-top:0.2em;
+ text-align:right;
+ font-weight:bold;
+}
+
+#vlist {
+ padding: 0 1px 1px;
+ margin-left: 0;
+ font: bold 12px Verdana, sans-serif;
+ background: gray;
+ width: 13em;
+}
+
+#vlist li {
+ list-style: none;
+ margin: 0;
+ border-top: 1px solid gray;
+ text-align: left;
+}
+
+#vlist li a {
+ display: block;
+ padding: 0.25em 0.5em 0.25em 0.75em;
+ border-left: 1em solid #7d95b5;
+ background: #d0d8e4;
+ text-decoration: none;
+}
+
+#vlist li a:hover {
+ border-color: #227;
+}
+
+.view .field {
+ background-color: #f3f6f8;
+ border-left: 1px solid #7695b5;
+ border-top: 1px solid #7695b5;
+ padding: 1px 10px 0px 2px;
+}
+
+#addnew {
+ width: 50%;
+ float: left;
+}
+
+#search {
+ width: 50%;
+ float:right;
+}
+
+.error { color: #d00; }
+
+.action {
+ border: 1px outset #7d95b5;
+ style:block;
+}
+
+.action:hover {
+ color: #fff;
+ text-decoration: none;
+ background-color: #7d95b5;
+}
+
+.actionform {
+ display: inline;
+}
+
+.actionbutton {
+ height: 16px;
+ width: 40px;
+ font-family: sans-serif;
+ font-size: 10px;
+ border: 1px outset;
+ background-color: #fff;
+ margin-bottom: 0px;
+}
+
+.actionbutton:hover {
+ color: #fff;
+ background-color: #7d95b5;
+}
+
+.actions {
+ white-space: nowrap;
+}
+
+.field {
+ display:inline;
+}
+
+#login { width: 400px; }
+
+#login input[type=text] { width: 150px; }
+#login input[type=password] { width: 150px; }
+
+.pager {
+ font: 11px Arial, Helvetica, sans-serif;
+ text-align: center;
+ border: solid 1px #e2e2e2;
+ border-left: 0;
+ border-right: 0;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ margin: 0px;
+ background-color: #f3f6f8;
+}
+
+.pager a {
+ padding: 2px 6px;
+ border: solid 1px #ddd;
+ background: #fff;
+ text-decoration: none;
+}
+
+.pager a:visited {
+ padding: 2px 6px;
+ border: solid 1px #ddd;
+ background: #fff;
+ text-decoration: none;
+}
+
+.pager .current-page {
+ padding: 2px 6px;
+ font-weight: bold;
+ vertical-align: top;
+}
+
+.pager a:hover {
+ color: #fff;
+ background: #7d95b5;
+ border-color: #036;
+ text-decoration: none;
+}
+
--- /dev/null
+<h3> Class::DBI meta info for [% classmetadata.name %] </h3>
+[%
+ USE this = Class(classmetadata.name);
+ USE Dumper; Dumper.dump(this.meta_info);
+%]
--- /dev/null
+<div id="search">
+<form method="get" action="[% base %]/[% classmetadata.table %]/search/">
+<fieldset>
+<legend>Search</legend>
+ [% INCLUDE display_search_inputs; %]
+ <input type="submit" name="search" value="search"/>
+</fieldset>
+</form>
+</div>
--- /dev/null
+[%#
+
+=head1 addnew
+
+This is the interface to adding a new instance of an object. (or a new
+row in the database, if you want to look at it that way) It displays a
+form containing a list of HTML components for each of the columns in the
+table.
+
+=cut
+
+#%]
+
+<div id="addnew">
+<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
+ <fieldset>
+<legend>Add a new [% classmetadata.moniker %]</legend>
+ [% FOR col = classmetadata.columns %]
+ [% NEXT IF col == "id" %]
+ <label><span class="field">[% classmetadata.colnames.$col %]</span>
+ [%
+ SET elem = classmetadata.cgi.$col.clone;
+ IF request.action == 'do_edit';
+ IF elem.tag == "textarea";
+ elem = elem.push_content(request.param(col));
+ ELSE;
+ elem.attr("value", request.param(col));
+ END;
+ END;
+ elem.as_XML; %]
+ </label>
+ [% IF errors.$col %]
+ <span class="error">[% errors.$col | html %]</span>
+ [% END %]
+
+ [% END; %]
+ <input type="submit" name="create" value="create" />
+ <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
+</fieldset>
+</form>
+</div>
--- /dev/null
+[%#
+
+=head1 edit
+
+This is the edit page. It edits the passed-in object, by displaying a
+form similar to L<addnew> but with the current values filled in.
+
+=cut
+
+#%]
+[% PROCESS macros %]
+[% INCLUDE header %]
+[% INCLUDE title %]
+
+[% IF request.action == 'edit' %]
+[% INCLUDE navbar %]
+[% END %]
+
+[% IF object %]
+<div id="title">Edit a [% classmetadata.moniker %]</div>
+<form action="[% base %]/[% item.table %]/do_edit/[% item.id %]" method="post">
+<fieldset>
+<legend>Edit [% object.name %]</legend>
+ [% FOR col = classmetadata.columns;
+ NEXT IF col == "id" OR col == classmetadata.table _ "_id";
+ '<label><span class="field">';
+ classmetadata.colnames.$col || col | ucfirst | replace('_',' '); ":</span>";
+ object.to_field(col).as_XML;
+ "</label>";
+ IF errors.$col;
+ '<span class="error">'; errors.$col;'</span>';
+ END;
+ END %]
+ <input type="submit" name="edit" value="edit"/>
+ <input type="hidden" name="__form_id" value="[% request.make_random_id %]">
+ </fieldset></form>
+
+[% ELSE %]
+
+<div id="addnew">
+<form method="post" action="[% base %]/[% classmetadata.table %]/do_edit/">
+<fieldset>
+<legend>Add a new [% classmetadata.moniker %]</legend>
+ [% FOR col = classmetadata.columns %]
+ [% NEXT IF col == "id" %]
+ <label><span class="field">[% classmetadata.colnames.$col %]</span>
+ [%
+ SET elem = classmetadata.cgi.$col.clone;
+ IF request.action == 'do_edit';
+ IF elem.tag == "textarea";
+ elem = elem.push_content(request.param(col));
+ ELSE;
+ elem.attr("value", request.param(col));
+ END;
+ END;
+ elem.as_XML; %]
+ </label>
+ [% IF errors.$col %]
+ <span class="error">[% errors.$col | html %]</span>
+ [% END %]
+
+ [% END; %]
+ <input type="submit" name="create" value="create" />
+ <input type="hidden" name="__form_id" value="[% request.make_random_id %]" />
+</fieldset>
+</form>
+</div>
+
+[% END %]
+[% INCLUDE footer %]
--- /dev/null
+ </div>
+ </body>
+</html>
--- /dev/null
+[%#
+
+=head1 frontpage
+
+This is the frontpage for your Maypole application.
+It shows a list of all tables it is allowed to display.
+
+=cut
+
+#%]
+[% INCLUDE header %]
+<div id="title">
+ [% config.application_name || "A poorly configured Maypole application" %]
+</div>
+<div id="frontpage_list">
+<ul>
+[% FOR table = config.display_tables %]
+ <li>
+ <a href="[% base %]/[%table%]/list">List by [%table %]</a>
+ </li>
+[% END %]
+</ul>
+</div>
+
+[% INCLUDE maypole %]
+
+[% INCLUDE footer %]
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>
+ [%
+ title || config.application_name ||
+ "A poorly configured Maypole application"
+ %]
+ </title>
+ <meta http-equiv="Content-Type" content="text/html; charset=[% request.document_encoding %]" />
+ <base href="[% config.uri_base%]"/>
+ <link title="Maypole" href="[% config.uri_base %]/maypole.css" type="text/css" rel="stylesheet" />
+ </head>
+ <body>
+ <div class="content">
--- /dev/null
+[% PROCESS macros %]
+[% INCLUDE header %]
+[% INCLUDE title %]
+[% IF search %]
+ <div id="title">Search results</div>
+[% ELSE %]
+ <div id="title">Listing of all [% classmetadata.plural %]</div>
+[% END %]
+[% INCLUDE navbar %]
+<div class="list">
+ <table id="matrix">
+ <tr>
+ [% FOR col = classmetadata.list_columns.list;
+ NEXT IF col == "id" OR col == classmetadata.table _ "_id";
+ "<th>";
+ SET additional = "?order=" _ col;
+ SET additional = additional _ "&page=" _ pager.current_page
+ IF pager;
+ SET additional = additional _ "&o2=desc"
+ IF col == request.params.order and request.params.o2 != "desc";
+ SET action = "list";
+ FOR name = classmetadata.columns.list;
+ IF request.query.$name;
+ SET additional =
+ additional _ "&" _ name _ "=" _
+ request.params.$name;
+ SET action = "search";
+ END;
+ END;
+ USE model_obj = Class request.model_class;
+ IF model_obj.find_column(col);
+ link(classmetadata.table, action, additional,
+ classmetadata.colnames.$col);
+ IF col == request.params.order;
+ IF request.params.o2 != "desc";
+ "↓";
+ ELSE;
+ "↑";
+ END;
+ END;
+ ELSE;
+ classmetadata.colnames.$col || col FILTER ucfirst;
+ END;
+ "</th>";
+ END %]
+ <th id="actionth">Actions</th>
+ </tr>
+ [% SET count = 0;
+ FOR item = objects;
+ SET count = count + 1;
+ "<tr";
+ ' class="alternate"' IF count % 2;
+ ">";
+ display_line(item);
+ "</tr>";
+ END %]
+ </table>
+
+[% INCLUDE pager %]
+[% INCLUDE addnew %]
+[% INCLUDE search_form %]
+</div>
+[% INCLUDE footer %]
--- /dev/null
+[% PROCESS macros %]
+[% INCLUDE header %]
+[% INCLUDE title %]
+[% user_field = config.auth.user_field || "user" %]
+
+ <div id="title">You need to log in</div>
+
+ <div id="login">
+ [% IF login_error %]
+ <div class="error"> [% login_error | html %] </div>
+ [% END %]
+ <form method="post" action="[% base %]/[% request.path %]">
+ <fieldset>
+ <legend>Login</legend>
+ <label>
+ <span class="field">Username:</span>
+ <input name="[% user_field %]" type="text" value="[% cgi_params.$user_field | html %]" />
+ </label>
+ <label>
+ <span class="field">Password:</span>
+ <input name="password" type="password" value="[% cgi_params.passwrd | html %]"/>
+ </label>
+ <input type="submit" name="login" value="Submit"/>
+ </fieldset>
+ </form>
+ </div>
+
--- /dev/null
+[%#
+
+=head1 MACROS
+
+These are some default macros which are used by various templates in the
+system.
+
+=head2 link
+
+This creates an <A HREF="..."> to a command in the Apache::MVC system by
+catenating the base URL, table, command, and any arguments.
+
+#%]
+[%
+MACRO link(table, command, additional, label) BLOCK;
+ SET lnk = base _ "/" _ table _ "/" _ command _ "/" _ additional;
+ lnk = lnk | uri | html;
+ '<a href="' _ lnk _ '">';
+ label | html;
+ "</a>";
+END;
+%]
+
+[%#
+
+=head2 maybe_link_view
+
+C<maybe_link_view> takes something returned from the database - either
+some ordinary data, or 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.
+
+#%]
+
+[%
+MACRO maybe_link_view(object) BLOCK;
+ IF object.isa('Maypole::Model::Base');
+ link(object.table, "view", object.id.join('/'), object);
+ ELSE;
+ object | html ;
+ END;
+END;
+%]
+
+[%#
+
+=head2 display_line
+
+C<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 C<id> column by default, and magically
+URLifies columns called C<url>. This may be considered too much magic
+for some.
+
+#%]
+[% MACRO display_line(item) BLOCK;
+ FOR col = classmetadata.list_columns;
+ NEXT IF col == "id" OR col == classmetadata.table _ "_id";
+ col_obj = item.find_column(col);
+ "<td>";
+ IF col == "url" AND item.url;
+ '<a href="'; item.url | html ; '"> '; item.url; '</a>';
+ ELSIF col == classmetadata.stringify_column;
+ maybe_link_view(item);
+ ELSIF col_obj; # its a real column
+ accessor = item.accessor_name_for(col_obj) ||
+ item.accessor_name(col_obj); # deprecated in cdbi
+ maybe_link_view(item.$accessor);
+ ELSE;
+ item.$col;
+ END;
+
+ "</td>";
+ END;
+ '<td class="actions">';
+ button(item, "edit");
+ button(item, "delete");
+ "</td>";
+END %]
+[%#
+
+=head2 button
+
+This is a generic button, which performs an action on an object.
+
+=cut
+
+#%]
+[% MACRO button(obj, action) BLOCK; %]
+[% IF obj.is_public(action) %]
+<form class="actionform" action="[% base %]/[% obj.table %]/[% action %]/[% obj.id.join('/') %]" method="post">
+<div class="field"><input class="actionbutton" type="submit" value="[% action %]" /></div></form>
+[% END %]
+[% END %]
+[%#
+
+=head2 view_related
+
+This takes an object, and looks up the C<related_accessors>; this should
+give a list of accessors that can be called to get a list of related
+objects. It then displays a title for that accessor, (i.e. "Beers" for a
+brewery) calls the accesor, and displays a list of the results.
+
+=cut
+
+#%]
+[%
+MACRO view_related(object) BLOCK;
+ FOR accessor = classmetadata.related_accessors.list;
+ "<div id=\"subtitle\">"; accessor | ucfirst; "</div>\n";
+ "<ul id=\"vlist\">";
+ FOR thing = object.$accessor;
+ "<li>"; maybe_link_view(thing); "</li>\n";
+ END;
+ "</ul>";
+ END;
+END;
+
+MACRO test_xxxx(myblock) BLOCK;
+ FOR col = classmetadata.columns;
+ NEXT IF col == "id";
+ myblock;
+ END;
+END;
+%]
+[%#
+
+=head2 view_item
+
+This takes an object and and displays its properties in a table.
+
+=cut
+
+#%]
+[% MACRO view_item(item) BLOCK; %]
+ [% SET string = classmetadata.stringify_column %]
+ <div id="title"> [% item.$string | html %]</div>
+ [% INCLUDE navbar %]
+ <table class="view">
+ <tr>
+ <td class="field">[% classmetadata.colnames.$string %]</td>
+ <td>[% item.$string | html %]</td>
+ </tr>
+ [% FOR col = classmetadata.columns.list;
+ NEXT IF col == "id" OR col == string OR col == classmetadata.table _ "_id";;
+ NEXT UNLESS item.$col;
+ %]
+[%#
+
+=for doc
+
+It gets the displayable form of a column's name from the hash returned
+from the C<column_names> method:
+
+#%]
+ <tr>
+ <td class="field">[% classmetadata.colnames.$col ||
+ col | ucfirst | replace('_',' '); %]</td>
+ <td>
+ [% IF col == "url" && item.url; # Possibly too much magic.
+ '<a href="'; item.url | html ; '"> '; item.url; '</a>';
+ ELSIF item.$col.size > 1; # has_many column
+ FOR thing IN item.$col;
+ maybe_link_view(thing);", ";
+ END;
+
+ ELSE;
+ maybe_link_view(item.$col);
+ END; %]
+[%#
+
+This tests whether or not the returned value is an object, and if so,
+creates a link to a page viewing that object; if not, it just displays
+the text as normal. The object is linked using its stringified name;
+by default this calls the C<name> method, or returns the object's ID
+if there is no C<name> method or other stringification method defined.
+
+=cut
+
+#%]
+ </td>
+ </tr>
+ [% END; %]
+ </table>
+[% END %]
--- /dev/null
+<!-- boxes -->
+<div style='position:absolute;top:220px;left:130px;border-bottom-width:260px;border-right-width:370px;' class='deco1'> </div>
+<div style='position:absolute;top:260px;left:190px;border-bottom-width:170px;border-right-width:530px;' class='deco2'> </div>
+<div style='position:absolute;top:240px;left:220px;border-bottom-width:340px;border-right-width:440px;' class='deco4'> </div>
+<div style='position:absolute;top:160px;left:330px;border-bottom-width:160px;border-right-width:280px;' class='deco1'> </div>
+<div style='position:absolute;top:190px;left:290px;border-bottom-width:430px;border-right-width:130px;' class='deco2'> </div>
+<!-- end of boxes -->
--- /dev/null
+html {
+ padding-right: 0px;
+ padding-left: 0px;
+ padding-bottom: 0px;
+ margin: 0px;
+ padding-top: 0px
+}
+body {
+ font-family: sans-serif;
+ padding-right: 0px;
+ padding-left: 0px;
+ padding-bottom: 0px;
+ margin: 0px; padding-top: 0px;
+ background-color: #fff;
+}
+#frontpage_list {
+ position: absolute;
+ z-index: 5;
+ padding: 0px 100px 0px 0px;
+ margin:0 0.5%;
+ margin-bottom:1em;
+ margin-top: 1em;
+ background-color: #fff;
+}
+
+#frontpage_list a:hover {
+ background-color: #d0d8e4;
+}
+
+#frontpage_list ul {
+ list-style-type: square;
+}
+
+.content {
+ padding: 12px;
+ margin-top: 1px;
+ margin-bottom:0px;
+ margin-left: 15px;
+ margin-right: 15px;
+ border-color: #000000;
+ border-top: 0px;
+ border-bottom: 0px;
+ border-left: 1px;
+ border-right: 1px;
+}
+
+A {
+ text-decoration: none;
+ color:#225
+}
+A:hover {
+ text-decoration: underline;
+ color:#222
+}
+
+#title {
+ z-index: 6;
+ width: 100%;
+ height: 18px;
+ margin-top: 10px;
+ font-size: 90%;
+ border-bottom: 1px solid #ddf;
+ text-align: left;
+}
+
+#subtitle {
+ postion: absolute;
+ z-index: 6;
+ padding: 10px;
+ margin-top: 2em;
+ height: 18px;
+ text-align: left;
+ background-color: #fff;
+}
+
+input[type=text] {
+ height: 16px;
+ width: 136px;
+ font-family: sans-serif;
+ font-size: 11px;
+ color: #2E415A;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+input[type=submit] {
+ height: 18px;
+ width: 60px;
+ font-family: sans-serif;
+ font-size: 11px;
+ border: 1px outset;
+ background-color: #fff;
+ padding: 0px 0px 2px 0px;
+ margin-bottom: 5px;
+}
+
+input:hover[type=submit] {
+ color: #fff;
+ background-color: #7d95b5;
+}
+
+textarea {
+ width: 136px;
+ font-family: sans-serif;
+ font-size: 11px;
+ color: #2E415A;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+select {
+ height: 16px;
+ width: 140px;
+ font-family: sans-serif;
+ font-size: 12px;
+ color: #202020;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+.deco1 {
+ font-size: 0px;
+ z-index:1;
+ border:0px;
+ border-style:solid;
+ border-color:#4d6d99;
+ background-color:#4d6d99;
+}
+
+.deco2 {
+ z-index:2;
+ border:0px;
+ border-style:solid;
+ border-color:#627ea5;
+ background-color:#627ea5;
+}
+
+
+.deco3 {
+ z-index:3;
+ border:0px;
+ border-style:solid;
+ border-color:#7d95b5;
+ background-color:#7d95b5;
+}
+
+.deco4 {
+ z-index:4;
+ border:0px;
+ border-style:solid;
+ border-color:#d0d8e4;
+ background-color:#d0d8e4;
+}
+
+
+table {
+ border: 0px solid;
+ background-color: #ffffff;
+}
+
+#matrix { width: 100%; }
+
+#matrix th {
+ background-color: #b5cadc;
+ border: 1px solid #778;
+ font: bold 12px Verdana, sans-serif;
+}
+
+#matrix #actionth {
+ width: 1px;
+ padding: 0em 1em 0em 1em;
+}
+
+#matrix tr.alternate { background-color:#e3eaf0; }
+#matrix tr:hover { background-color: #b5cadc; }
+#matrix td { font: 12px Verdana, sans-serif; }
+
+#navlist {
+ padding: 3px 0;
+ margin-left: 0;
+ margin-top:3em;
+ border-bottom: 1px solid #778;
+ font: bold 12px Verdana, sans-serif;
+}
+
+#navlist li {
+ list-style: none;
+ margin: 0;
+ display: inline;
+}
+
+#navlist li a {
+ padding: 3px 0.5em;
+ margin-left: 3px;
+ border: 1px solid #778;
+ border-bottom: none;
+ background: #b5cadc;
+ text-decoration: none;
+}
+
+#navlist li a:link { color: #448; }
+#navlist li a:visited { color: #667; }
+
+#navlist li a:hover {
+ color: #000;
+ background: #eef;
+ border-top: 4px solid #7d95b5;
+ border-color: #227;
+}
+
+#navlist #active a {
+ background: white;
+ border-bottom: 1px solid white;
+ border-top: 4px solid;
+}
+
+td { font: 12px Verdana, sans-serif; }
+
+
+fieldset {
+ margin-top: 1em;
+ padding: 1em;
+ background-color: #f3f6f8;
+ font:80%/1 sans-serif;
+ border:1px solid #ddd;
+}
+
+legend {
+ padding: 0.2em 0.5em;
+ background-color: #fff;
+ border:1px solid #aaa;
+ font-size:90%;
+ text-align:right;
+}
+
+label {
+ display:block;
+}
+
+label.error {
+ border-color: red;
+ border-width: 1px;
+}
+
+label .field {
+ float:left;
+ width:25%;
+ margin-right:0.5em;
+ padding-top:0.2em;
+ text-align:right;
+ font-weight:bold;
+}
+
+#vlist {
+ padding: 0 1px 1px;
+ margin-left: 0;
+ font: bold 12px Verdana, sans-serif;
+ background: gray;
+ width: 13em;
+}
+
+#vlist li {
+ list-style: none;
+ margin: 0;
+ border-top: 1px solid gray;
+ text-align: left;
+}
+
+#vlist li a {
+ display: block;
+ padding: 0.25em 0.5em 0.25em 0.75em;
+ border-left: 1em solid #7d95b5;
+ background: #d0d8e4;
+ text-decoration: none;
+}
+
+#vlist li a:hover {
+ border-color: #227;
+}
+
+.view .field {
+ background-color: #f3f6f8;
+ border-left: 1px solid #7695b5;
+ border-top: 1px solid #7695b5;
+ padding: 1px 10px 0px 2px;
+}
+
+#addnew {
+ width: 50%;
+ float: left;
+}
+
+#search {
+ width: 50%;
+ float:right;
+}
+
+.error { color: #d00; }
+
+.action {
+ border: 1px outset #7d95b5;
+ style:block;
+}
+
+.action:hover {
+ color: #fff;
+ text-decoration: none;
+ background-color: #7d95b5;
+}
+
+.actionform {
+ display: inline;
+}
+
+.actionbutton {
+ height: 16px;
+ width: 40px;
+ font-family: sans-serif;
+ font-size: 10px;
+ border: 1px outset;
+ background-color: #fff;
+ margin-bottom: 0px;
+}
+
+.actionbutton:hover {
+ color: #fff;
+ background-color: #7d95b5;
+}
+
+.actions {
+ white-space: nowrap;
+}
+
+.field {
+ display:inline;
+}
+
+#login { width: 400px; }
+
+#login input[type=text] { width: 150px; }
+#login input[type=password] { width: 150px; }
+
+.pager {
+ font: 11px Arial, Helvetica, sans-serif;
+ text-align: center;
+ border: solid 1px #e2e2e2;
+ border-left: 0;
+ border-right: 0;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ margin: 0px;
+ background-color: #f3f6f8;
+}
+
+.pager a {
+ padding: 2px 6px;
+ border: solid 1px #ddd;
+ background: #fff;
+ text-decoration: none;
+}
+
+.pager a:visited {
+ padding: 2px 6px;
+ border: solid 1px #ddd;
+ background: #fff;
+ text-decoration: none;
+}
+
+.pager .current-page {
+ padding: 2px 6px;
+ font-weight: bold;
+ vertical-align: top;
+}
+
+.pager a:hover {
+ color: #fff;
+ background: #7d95b5;
+ border-color: #036;
+ text-decoration: none;
+}
+
--- /dev/null
+[%#
+
+=head1 navbar
+
+This is a navigation bar to go across the page. (Or down the side, or
+whatetver you want to do with it.) It displays all the tables which are
+accessible, with a link to the list page for each one.
+
+#%]
+[% PROCESS macros %]
+<div id="navcontainer">
+<ul id="navlist">
+[%
+ FOR table = config.display_tables;
+ '<li '; 'id="active"' IF table == classmetadata.table; '>';
+ # Hack
+ link(table, "list", "", table);
+ '</li>';
+ END;
+%]
+</ul>
+</div>
--- /dev/null
+[%#
+
+=head1 pager
+
+This controls the pager display at the bottom (by default) of the list
+and search views. It expects a C<pager> template argument which responds
+to the L<Data::Page> interface.
+
+#%]
+[%
+IF pager AND pager.first_page != pager.last_page;
+%]
+<p class="pager">Pages:
+[%
+ UNLESS pager_action;
+ SET pager_action = request.action;
+ END;
+
+ SET begin_page = pager.current_page - 10;
+ IF begin_page < 1;
+ SET begin_page = pager.first_page;
+ END;
+ SET end_page = pager.current_page + 10;
+ IF pager.last_page < end_page;
+ SET end_page = pager.last_page;
+ END;
+ FOREACH num = [begin_page .. end_page];
+ IF num == pager.current_page;
+ "<span class='current-page'>"; num; "</span>";
+ ELSE;
+ SET label = num;
+ SET args = "?page=" _ num;
+ SET args = args _ "&order=" _ request.params.order
+ IF request.params.order;
+ SET args = args _ "&o2=desc"
+ IF request.params.o2 == "desc";
+ FOR col = classmetadata.columns.list;
+ IF request.params.$col;
+ SET args = args _ "&" _ col _ "=" _ request.params.$col;
+ SET action = "search";
+ END;
+ END;
+ link(classmetadata.table, pager_action, args, label);
+ END;
+ END;
+%]
+</p>
+[% END %]
--- /dev/null
+<div id="search">
+<form method="get" action="[% base %]/[% classmetadata.moniker %]/search/">
+<fieldset>
+<legend>Search</legend>
+ [% FOR col = classmetadata.columns;
+ NEXT IF col == "id" OR col == classmetadata.table _ "_id";
+ %]
+ <label>
+ <span class="field">[% classmetadata.colnames.$col; %]</span>
+ [% SET element = classmetadata.cgi.$col;
+ IF element.tag == "select";
+ USE element_maker = Class("HTML::Element");
+ SET element = element.unshift_content(
+ element_maker.new("option", value," "));
+ END;
+ element.as_XML; %]
+ </label>
+ [% END; %]
+ <input type="submit" name="search" value="search"/>
+ </fieldset>
+</form>
+</div>
--- /dev/null
+<div id="search">
+<form method="get" action="[% base %]/[% classmetadata.table %]/search/">
+<fieldset>
+<legend>Search</legend>
+ [% INCLUDE display_search_inputs; %]
+ <input type="submit" name="search" value="search"/>
+</fieldset>
+</form>
+</div>
--- /dev/null
+ <a href="[% base %]/frontpage">[% config.application_name %]</a>
--- /dev/null
+[%#
+
+=for doc
+
+The C<view> template takes some objects (usually just one) from
+C<objects> and displays the object's properties in a table.
+
+=cut
+
+#%]
+[% PROCESS macros %]
+[% INCLUDE header %]
+[% view_item(object); %]
+[%#
+
+=for doc
+
+The C<view> template also displays a list of other objects related to the first
+one via C<has_many> style relationships; this is done by calling the
+C<related_accessors> method - see L<Model/related_accessors> - to return
+a list of has-many accessors. Next it calls each of those accessors, and
+displays the results in a table.
+
+#%]
+ <br /><a href="[%base%]/[%object.table%]/list">Back to listing</a>
+[% view_related(object); %]
+
+[%
+ button(object, "edit");
+ button(object, "delete");
+%]
+[% INCLUDE footer %]
"a pub has beers on handpumps");
1;
-There's a version of this program in the F<ex/> directory in the Maypole
+There's a version of this program in the F<examples/> directory in the Maypole
files that you downloaded in the F<~root/.cpan/> build area.
This defines the C<BeerDB> application.
To set it up as a mod_perl handler, just tell the Apache configuration
our $error_template;
{ local $/; $error_template = <DATA>; }
+our $VERSION = '2.11';
+
use strict;
sub template {
[% IF object %]
<div id="title">Edit a [% classmetadata.moniker %]</div>
-<form action="[% base %]/[% item.table %]/do_edit/[% item.id %]" method="post">
+<form action="[% base %]/[% item.table %]/do_edit/[% object.id %]" method="post">
<fieldset>
<legend>Edit [% object.name %]</legend>
[% FOR col = classmetadata.columns;
#!/usr/bin/perl -w
use Test::More;
-use lib 'ex'; # Where BeerDB should live
+use lib 'examples'; # Where BeerDB should live
BEGIN {
$ENV{BEERDB_DEBUG} = 0;
#!/usr/bin/perl -w
use Test::More;
-use lib 'ex'; # Where BeerDB should live
+use lib 'examples'; # Where BeerDB should live
BEGIN {
$ENV{BEERDB_DEBUG} = 2;
#!/usr/bin/perl -w
use Test::More;
use Data::Dumper;
-use lib 'ex'; # Where BeerDB should live
+use lib 'examples'; # Where BeerDB should live
BEGIN {
- plan tests => 65;
+ plan tests => 45;
}
$db = 'test';