From: biopete Date: Tue, 11 Jul 2006 17:44:37 +0000 (+0000) Subject: Beginning to a fancy example app of 2.11 features . See X-Git-Tag: 2.11~16 X-Git-Url: https://git.decadent.org.uk/gitweb/?a=commitdiff_plain;h=7bb021648c28d2f70ec2853f0d01dd49c6437460;p=maypole.git Beginning to a fancy example app of 2.11 features . See BeerDB::Drinker.pm for FromCGI and AsForm stuff. Still needs much work. git-svn-id: http://svn.maypole.perl.org/Maypole/trunk@504 48953598-375a-da11-a14b-00016c27c3ee --- diff --git a/ex/fancy_example/BeerDB.pm b/ex/fancy_example/BeerDB.pm new file mode 100644 index 0000000..593851f --- /dev/null +++ b/ex/fancy_example/BeerDB.pm @@ -0,0 +1,80 @@ +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/]); +BeerDB::Person->untaint_columns( printable => [qw/first_name sur_name dob username password email/]); + + +# 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/ } +sub BeerDB::Pint::display_columns { qw/drinker handpump/ } +sub BeerDB::Person::display_columns { qw/first_name last_name dob email/ } + +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; diff --git a/ex/fancy_example/BeerDB/Base.pm b/ex/fancy_example/BeerDB/Base.pm new file mode 100644 index 0000000..0f980d9 --- /dev/null +++ b/ex/fancy_example/BeerDB/Base.pm @@ -0,0 +1,18 @@ +package BeerDB::Base; +use base qw/Maypole::Model::CDBI/; +use strict; +use warnings; + +# 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) }; +} + +1; diff --git a/ex/fancy_example/BeerDB/Beer.pm b/ex/fancy_example/BeerDB/Beer.pm new file mode 100644 index 0000000..d7de346 --- /dev/null +++ b/ex/fancy_example/BeerDB/Beer.pm @@ -0,0 +1,10 @@ +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; diff --git a/ex/fancy_example/BeerDB/Brewery.pm b/ex/fancy_example/BeerDB/Brewery.pm new file mode 100644 index 0000000..ad99483 --- /dev/null +++ b/ex/fancy_example/BeerDB/Brewery.pm @@ -0,0 +1,10 @@ +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; diff --git a/ex/fancy_example/BeerDB/Drinker.pm b/ex/fancy_example/BeerDB/Drinker.pm new file mode 100644 index 0000000..f3edd7b --- /dev/null +++ b/ex/fancy_example/BeerDB/Drinker.pm @@ -0,0 +1,62 @@ +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 view : Exported { + my ($self, $r, $obj) = @_; + $self->_croak( "Object method only") unless $obj; + + if ($r->params->{submit} eq 'drink') { + $r->params->{drinker} = $self; + my ($pint, $errs) = $self->related_class($r, 'pints')->create_from_cgi($r); + $r->template_args->{errors} = $errs if $errs; + } + + my %cgi = $self->to_cgi('pints'); + $cgi{pints}{drinker} = $obj->to_field(drinker => 'link_hidden', {r => $r}); + $r->template_args->{classmetadata}{cgi} = \%cgi ; +} + + + +#sub foreign_input_delimiter { '__IMODDD__'} + +1; diff --git a/ex/fancy_example/beerdb.sql b/ex/fancy_example/beerdb.sql new file mode 100644 index 0000000..6089c94 --- /dev/null +++ b/ex/fancy_example/beerdb.sql @@ -0,0 +1,67 @@ +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) +); + + diff --git a/ex/fancy_example/templates/custom/addnew b/ex/fancy_example/templates/custom/addnew new file mode 100644 index 0000000..7053240 --- /dev/null +++ b/ex/fancy_example/templates/custom/addnew @@ -0,0 +1,24 @@ +[%# + +=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; %] + +
+
+
+Add a new [% config.TABLES.$tbl.singular || tbl | ucfirst | replace('_',' '); %] + [% INCLUDE display_inputs; %] + + +
+
+
diff --git a/ex/fancy_example/templates/custom/display_inputs b/ex/fancy_example/templates/custom/display_inputs new file mode 100644 index 0000000..201227c --- /dev/null +++ b/ex/fancy_example/templates/custom/display_inputs @@ -0,0 +1,97 @@ +[%# + +=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; +%] + +[% 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; +%] + +[% # 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; +%] + + + + [% IF errors.$col %] + [% errors.$col | html %] + [% END %] +[% END; %] + + + +[% USE this = Class(classmetadata.name); + FOR col IN foreign; + fclass = this.related_class(request, col); + fclass_meta = this.get_classmetadata(fclass); + fclass_meta.cgi = classmetadata.cgi.$col; + INCLUDE display_inputs + col_prefix = col _ "__AF__" _ col_prefix + errors = errors.$col + heading = names.shift + classmetadata = fclass_meta; # localize + END; +%] + diff --git a/ex/fancy_example/templates/custom/display_inputs.recursive b/ex/fancy_example/templates/custom/display_inputs.recursive new file mode 100644 index 0000000..5c7d565 --- /dev/null +++ b/ex/fancy_example/templates/custom/display_inputs.recursive @@ -0,0 +1,97 @@ +[%# + +=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; +%] + +[% FOR col = classmetadata.columns; + NEXT IF !classmetadata.cgi.$col; + NEXT IF col == "id"; + + # Display foreign inputs last + IF (mykeys = classmetadata.cgi.$col.keys); + foreign.push(col); + names.push(classmetadata.colnames.$col); + NEXT; + 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; +%] + + + + [% IF errors.$col %] + [% errors.$col | html %] + [% END %] +[% END; %] + + + +[% USE this = Class(classmetadata.name); + FOR col IN foreign; + fclass = this.related_class(request, col); + fclass_meta = this.get_classmetadata(fclass); + fclass_meta.cgi = classmetadata.cgi.$col; + INCLUDE display_inputs + col_prefix = col _ "__AF__" _ col_prefix + errors = errors.$col + heading = names.shift + classmetadata = fclass_meta; # localize + END; +%] + diff --git a/ex/fancy_example/templates/custom/display_search_inputs b/ex/fancy_example/templates/custom/display_search_inputs new file mode 100644 index 0000000..ee1d586 --- /dev/null +++ b/ex/fancy_example/templates/custom/display_search_inputs @@ -0,0 +1,62 @@ +[%# + +=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; "
"; 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; + # 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; +%] + + +[% END; %] + + diff --git a/ex/fancy_example/templates/custom/edit b/ex/fancy_example/templates/custom/edit new file mode 100644 index 0000000..dae8c42 --- /dev/null +++ b/ex/fancy_example/templates/custom/edit @@ -0,0 +1,72 @@ +[%# + +=head1 edit + +This is the edit page. It edits the passed-in object, by displaying a +form similar to L but with the current values filled in. + +=cut + +#%] +[% PROCESS macros %] +[% INCLUDE header %] +[% INCLUDE title %] + +[% IF request.action == 'edit' %] +[% INCLUDE navbar %] +[% END %] + +[% IF objects.size %] +
Edit a [% classmetadata.moniker %]
+[% FOR item = objects; %] +
+
+Edit [% item.name %] +[% FOR col = classmetadata.columns; + NEXT IF col == "id" OR col == classmetadata.table _ "_id"; + '"; + IF errors.$col; + ''; errors.$col;''; + END; + END %] + + +
+ + [% END %] +[% ELSE %] + +
+
+
+Add a new [% classmetadata.moniker %] + [% FOR col = classmetadata.columns %] + [% NEXT IF col == "id" %] + + [% IF errors.$col %] + [% errors.$col | html %] + [% END %] + + [% END; %] + + +
+
+
+ +[% END %] +[% INCLUDE footer %] diff --git a/ex/fancy_example/templates/custom/header b/ex/fancy_example/templates/custom/header new file mode 100644 index 0000000..c21fff7 --- /dev/null +++ b/ex/fancy_example/templates/custom/header @@ -0,0 +1,16 @@ + + + + + [% + title || config.application_name || + "A poorly configured Maypole application" + %] + + + + + + +
diff --git a/ex/fancy_example/templates/custom/maypole.css b/ex/fancy_example/templates/custom/maypole.css new file mode 100644 index 0000000..b13b4f1 --- /dev/null +++ b/ex/fancy_example/templates/custom/maypole.css @@ -0,0 +1,382 @@ +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; +} + diff --git a/ex/fancy_example/templates/custom/search_form b/ex/fancy_example/templates/custom/search_form new file mode 100644 index 0000000..5d540fb --- /dev/null +++ b/ex/fancy_example/templates/custom/search_form @@ -0,0 +1,9 @@ + diff --git a/ex/fancy_example/templates/drinker/view b/ex/fancy_example/templates/drinker/view new file mode 100644 index 0000000..50a51ca --- /dev/null +++ b/ex/fancy_example/templates/drinker/view @@ -0,0 +1,53 @@ +[%# + +=for doc + +Drinker C template displays drinker and from to drink beer. + +=cut + +#%] +[% PROCESS macros %] +[% INCLUDE header %] +[% view_item(object); %] + +[%# Form to drink a pint. We made sure to only make inputs for pint. could do it + # here like so. + USE this = Class(classmetadata.name); + classmetadata.cgi = this.to_field(pints); + +%] + + +[%# + +=for doc + +The C template also displays a list of other objects related to the first +one via C style relationships; this is done by calling the +C method - see L - to return +a list of has-many accessors. Next it calls each of those accessors, and +displays the results in a table. + +#%] +
Back to listing +[% view_related(object); %] + +[% + button(object, "edit"); + button(object, "delete"); +%] +[% INCLUDE footer %] diff --git a/ex/fancy_example/templates/factory/addnew b/ex/fancy_example/templates/factory/addnew new file mode 100644 index 0000000..2334496 --- /dev/null +++ b/ex/fancy_example/templates/factory/addnew @@ -0,0 +1,41 @@ +[%# + +=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 + +#%] + +
+
+
+Add a new [% classmetadata.moniker %] + [% FOR col = classmetadata.columns %] + [% NEXT IF col == "id" %] + + [% IF errors.$col %] + [% errors.$col | html %] + [% END %] + + [% END; %] + + +
+
+
diff --git a/ex/fancy_example/templates/factory/edit b/ex/fancy_example/templates/factory/edit new file mode 100644 index 0000000..cf88311 --- /dev/null +++ b/ex/fancy_example/templates/factory/edit @@ -0,0 +1,70 @@ +[%# + +=head1 edit + +This is the edit page. It edits the passed-in object, by displaying a +form similar to L but with the current values filled in. + +=cut + +#%] +[% PROCESS macros %] +[% INCLUDE header %] +[% INCLUDE title %] + +[% IF request.action == 'edit' %] +[% INCLUDE navbar %] +[% END %] + +[% IF object %] +
Edit a [% classmetadata.moniker %]
+
+
+Edit [% object.name %] + [% FOR col = classmetadata.columns; + NEXT IF col == "id" OR col == classmetadata.table _ "_id"; + '"; + IF errors.$col; + ''; errors.$col;''; + END; + END %] + + +
+ +[% ELSE %] + +
+
+
+Add a new [% classmetadata.moniker %] + [% FOR col = classmetadata.columns %] + [% NEXT IF col == "id" %] + + [% IF errors.$col %] + [% errors.$col | html %] + [% END %] + + [% END; %] + + +
+
+
+ +[% END %] +[% INCLUDE footer %] diff --git a/ex/fancy_example/templates/factory/footer b/ex/fancy_example/templates/factory/footer new file mode 100644 index 0000000..1b8ae55 --- /dev/null +++ b/ex/fancy_example/templates/factory/footer @@ -0,0 +1,3 @@ +
+ + diff --git a/ex/fancy_example/templates/factory/frontpage b/ex/fancy_example/templates/factory/frontpage new file mode 100644 index 0000000..ac47269 --- /dev/null +++ b/ex/fancy_example/templates/factory/frontpage @@ -0,0 +1,27 @@ +[%# + +=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 %] +
+ [% config.application_name || "A poorly configured Maypole application" %] +
+
+ +
+ +[% INCLUDE maypole %] + +[% INCLUDE footer %] diff --git a/ex/fancy_example/templates/factory/header b/ex/fancy_example/templates/factory/header new file mode 100644 index 0000000..ba0b190 --- /dev/null +++ b/ex/fancy_example/templates/factory/header @@ -0,0 +1,16 @@ + + + + + [% + title || config.application_name || + "A poorly configured Maypole application" + %] + + + + + + +
diff --git a/ex/fancy_example/templates/factory/list b/ex/fancy_example/templates/factory/list new file mode 100644 index 0000000..9abbc01 --- /dev/null +++ b/ex/fancy_example/templates/factory/list @@ -0,0 +1,63 @@ +[% PROCESS macros %] +[% INCLUDE header %] +[% INCLUDE title %] +[% IF search %] +
Search results
+[% ELSE %] +
Listing of all [% classmetadata.plural %]
+[% END %] +[% INCLUDE navbar %] +
+ + + [% FOR col = classmetadata.list_columns.list; + NEXT IF col == "id" OR col == classmetadata.table _ "_id"; + ""; + END %] + + + [% SET count = 0; + FOR item = objects; + SET count = count + 1; + ""; + display_line(item); + ""; + END %] +
"; + 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; + "Actions
+ +[% INCLUDE pager %] +[% INCLUDE addnew %] +[% INCLUDE search_form %] +
+[% INCLUDE footer %] diff --git a/ex/fancy_example/templates/factory/login b/ex/fancy_example/templates/factory/login new file mode 100644 index 0000000..af08e5b --- /dev/null +++ b/ex/fancy_example/templates/factory/login @@ -0,0 +1,27 @@ +[% PROCESS macros %] +[% INCLUDE header %] +[% INCLUDE title %] +[% user_field = config.auth.user_field || "user" %] + +
You need to log in
+ +
+ [% IF login_error %] +
[% login_error | html %]
+ [% END %] +
+
+ Login + + + +
+
+
+ diff --git a/ex/fancy_example/templates/factory/macros b/ex/fancy_example/templates/factory/macros new file mode 100644 index 0000000..fc75d09 --- /dev/null +++ b/ex/fancy_example/templates/factory/macros @@ -0,0 +1,185 @@ +[%# + +=head1 MACROS + +These are some default macros which are used by various templates in the +system. + +=head2 link + +This creates an 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; + ''; + label | html; + ""; +END; +%] + +[%# + +=head2 maybe_link_view + +C 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 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 column by default, and magically +URLifies columns called C. 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); + ""; + IF col == "url" AND item.url; + ' '; item.url; ''; + 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; + + ""; + END; + ''; + button(item, "edit"); + button(item, "delete"); + ""; +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) %] +
+
+[% END %] +[% END %] +[%# + +=head2 view_related + +This takes an object, and looks up the C; 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; + "
"; accessor | ucfirst; "
\n"; + "
    "; + FOR thing = object.$accessor; + "
  • "; maybe_link_view(thing); "
  • \n"; + END; + "
"; + 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 %] +
[% item.$string | html %]
+ [% INCLUDE navbar %] + + + + + + [% 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 method: + +#%] + + + + + [% END; %] +
[% classmetadata.colnames.$string %][% item.$string | html %]
[% classmetadata.colnames.$col || + col | ucfirst | replace('_',' '); %] + [% IF col == "url" && item.url; # Possibly too much magic. + ' '; item.url; ''; + 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 method, or returns the object's ID +if there is no C method or other stringification method defined. + +=cut + +#%] +
+[% END %] diff --git a/ex/fancy_example/templates/factory/maypole b/ex/fancy_example/templates/factory/maypole new file mode 100644 index 0000000..7ab2744 --- /dev/null +++ b/ex/fancy_example/templates/factory/maypole @@ -0,0 +1,7 @@ + +
 
+
 
+
 
+
 
+
 
+ diff --git a/ex/fancy_example/templates/factory/maypole.css b/ex/fancy_example/templates/factory/maypole.css new file mode 100644 index 0000000..d63be55 --- /dev/null +++ b/ex/fancy_example/templates/factory/maypole.css @@ -0,0 +1,381 @@ +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; +} + diff --git a/ex/fancy_example/templates/factory/navbar b/ex/fancy_example/templates/factory/navbar new file mode 100644 index 0000000..0c8b168 --- /dev/null +++ b/ex/fancy_example/templates/factory/navbar @@ -0,0 +1,22 @@ +[%# + +=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 %] + diff --git a/ex/fancy_example/templates/factory/pager b/ex/fancy_example/templates/factory/pager new file mode 100644 index 0000000..78c89fd --- /dev/null +++ b/ex/fancy_example/templates/factory/pager @@ -0,0 +1,48 @@ +[%# + +=head1 pager + +This controls the pager display at the bottom (by default) of the list +and search views. It expects a C template argument which responds +to the L interface. + +#%] +[% +IF pager AND pager.first_page != pager.last_page; +%] +

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; + ""; num; ""; + 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; +%] +

+[% END %] diff --git a/ex/fancy_example/templates/factory/search_form b/ex/fancy_example/templates/factory/search_form new file mode 100644 index 0000000..d10101e --- /dev/null +++ b/ex/fancy_example/templates/factory/search_form @@ -0,0 +1,22 @@ + diff --git a/ex/fancy_example/templates/factory/search_form_recursive b/ex/fancy_example/templates/factory/search_form_recursive new file mode 100644 index 0000000..5d540fb --- /dev/null +++ b/ex/fancy_example/templates/factory/search_form_recursive @@ -0,0 +1,9 @@ + diff --git a/ex/fancy_example/templates/factory/title b/ex/fancy_example/templates/factory/title new file mode 100644 index 0000000..401f0a3 --- /dev/null +++ b/ex/fancy_example/templates/factory/title @@ -0,0 +1 @@ + [% config.application_name %] diff --git a/ex/fancy_example/templates/factory/view b/ex/fancy_example/templates/factory/view new file mode 100644 index 0000000..9f06086 --- /dev/null +++ b/ex/fancy_example/templates/factory/view @@ -0,0 +1,32 @@ +[%# + +=for doc + +The C template takes some objects (usually just one) from +C and displays the object's properties in a table. + +=cut + +#%] +[% PROCESS macros %] +[% INCLUDE header %] +[% view_item(object); %] +[%# + +=for doc + +The C template also displays a list of other objects related to the first +one via C style relationships; this is done by calling the +C method - see L - to return +a list of has-many accessors. Next it calls each of those accessors, and +displays the results in a table. + +#%] +
Back to listing +[% view_related(object); %] + +[% + button(object, "edit"); + button(object, "delete"); +%] +[% INCLUDE footer %]