]> git.decadent.org.uk Git - maypole.git/commitdiff
Beginning to a fancy example app of 2.11 features . See
authorbiopete <biopete@invalid>
Tue, 11 Jul 2006 17:44:37 +0000 (17:44 +0000)
committerbiopete <biopete@invalid>
Tue, 11 Jul 2006 17:44:37 +0000 (17:44 +0000)
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

31 files changed:
ex/fancy_example/BeerDB.pm [new file with mode: 0644]
ex/fancy_example/BeerDB/Base.pm [new file with mode: 0644]
ex/fancy_example/BeerDB/Beer.pm [new file with mode: 0644]
ex/fancy_example/BeerDB/Brewery.pm [new file with mode: 0644]
ex/fancy_example/BeerDB/Drinker.pm [new file with mode: 0644]
ex/fancy_example/beerdb.sql [new file with mode: 0644]
ex/fancy_example/templates/custom/addnew [new file with mode: 0644]
ex/fancy_example/templates/custom/display_inputs [new file with mode: 0644]
ex/fancy_example/templates/custom/display_inputs.recursive [new file with mode: 0644]
ex/fancy_example/templates/custom/display_search_inputs [new file with mode: 0644]
ex/fancy_example/templates/custom/edit [new file with mode: 0644]
ex/fancy_example/templates/custom/header [new file with mode: 0644]
ex/fancy_example/templates/custom/maypole.css [new file with mode: 0644]
ex/fancy_example/templates/custom/search_form [new file with mode: 0644]
ex/fancy_example/templates/drinker/view [new file with mode: 0644]
ex/fancy_example/templates/factory/addnew [new file with mode: 0644]
ex/fancy_example/templates/factory/edit [new file with mode: 0644]
ex/fancy_example/templates/factory/footer [new file with mode: 0644]
ex/fancy_example/templates/factory/frontpage [new file with mode: 0644]
ex/fancy_example/templates/factory/header [new file with mode: 0644]
ex/fancy_example/templates/factory/list [new file with mode: 0644]
ex/fancy_example/templates/factory/login [new file with mode: 0644]
ex/fancy_example/templates/factory/macros [new file with mode: 0644]
ex/fancy_example/templates/factory/maypole [new file with mode: 0644]
ex/fancy_example/templates/factory/maypole.css [new file with mode: 0644]
ex/fancy_example/templates/factory/navbar [new file with mode: 0644]
ex/fancy_example/templates/factory/pager [new file with mode: 0644]
ex/fancy_example/templates/factory/search_form [new file with mode: 0644]
ex/fancy_example/templates/factory/search_form_recursive [new file with mode: 0644]
ex/fancy_example/templates/factory/title [new file with mode: 0644]
ex/fancy_example/templates/factory/view [new file with mode: 0644]

diff --git a/ex/fancy_example/BeerDB.pm b/ex/fancy_example/BeerDB.pm
new file mode 100644 (file)
index 0000000..593851f
--- /dev/null
@@ -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 (file)
index 0000000..0f980d9
--- /dev/null
@@ -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 (file)
index 0000000..d7de346
--- /dev/null
@@ -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 (file)
index 0000000..ad99483
--- /dev/null
@@ -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 (file)
index 0000000..f3edd7b
--- /dev/null
@@ -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 (file)
index 0000000..6089c94
--- /dev/null
@@ -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 (file)
index 0000000..7053240
--- /dev/null
@@ -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; %]
+
+<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>
diff --git a/ex/fancy_example/templates/custom/display_inputs b/ex/fancy_example/templates/custom/display_inputs
new file mode 100644 (file)
index 0000000..201227c
--- /dev/null
@@ -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;
+%]
+
+       <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; 
+               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 (file)
index 0000000..5c7d565
--- /dev/null
@@ -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;
+%]
+
+       <label>
+               [% req_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; 
+               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 (file)
index 0000000..ee1d586
--- /dev/null
@@ -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; "<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;
+               # 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; %]
+       
+
diff --git a/ex/fancy_example/templates/custom/edit b/ex/fancy_example/templates/custom/edit
new file mode 100644 (file)
index 0000000..dae8c42
--- /dev/null
@@ -0,0 +1,72 @@
+[%#
+
+=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 %]
diff --git a/ex/fancy_example/templates/custom/header b/ex/fancy_example/templates/custom/header
new file mode 100644 (file)
index 0000000..c21fff7
--- /dev/null
@@ -0,0 +1,16 @@
+<!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">
diff --git a/ex/fancy_example/templates/custom/maypole.css b/ex/fancy_example/templates/custom/maypole.css
new file mode 100644 (file)
index 0000000..b13b4f1
--- /dev/null
@@ -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 (file)
index 0000000..5d540fb
--- /dev/null
@@ -0,0 +1,9 @@
+<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>
diff --git a/ex/fancy_example/templates/drinker/view b/ex/fancy_example/templates/drinker/view
new file mode 100644 (file)
index 0000000..50a51ca
--- /dev/null
@@ -0,0 +1,53 @@
+[%#
+
+=for doc
+
+Drinker  C<view> 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);
+
+%]
+<div id="search">
+<form action="[% base _ "/" _ request.table _ "/" _ request.action _ "/" _ object.id %]" method="post">
+<fieldset>
+<legend> Drink a beer </legend>
+
+[% 
+       INCLUDE display_inputs classmetadata.columns=['pints'] ;%]
+
+
+<input type='submit'name='submit' value='Hellyeah!'\>
+</fieldset>
+</form>
+
+</div>
+
+[%#
+
+=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 %]
diff --git a/ex/fancy_example/templates/factory/addnew b/ex/fancy_example/templates/factory/addnew
new file mode 100644 (file)
index 0000000..2334496
--- /dev/null
@@ -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
+
+#%]
+
+<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>
diff --git a/ex/fancy_example/templates/factory/edit b/ex/fancy_example/templates/factory/edit
new file mode 100644 (file)
index 0000000..cf88311
--- /dev/null
@@ -0,0 +1,70 @@
+[%#
+
+=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 %]
diff --git a/ex/fancy_example/templates/factory/footer b/ex/fancy_example/templates/factory/footer
new file mode 100644 (file)
index 0000000..1b8ae55
--- /dev/null
@@ -0,0 +1,3 @@
+       </div>
+    </body>
+</html>
diff --git a/ex/fancy_example/templates/factory/frontpage b/ex/fancy_example/templates/factory/frontpage
new file mode 100644 (file)
index 0000000..ac47269
--- /dev/null
@@ -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 %]
+<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 %]
diff --git a/ex/fancy_example/templates/factory/header b/ex/fancy_example/templates/factory/header
new file mode 100644 (file)
index 0000000..ba0b190
--- /dev/null
@@ -0,0 +1,16 @@
+<!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">
diff --git a/ex/fancy_example/templates/factory/list b/ex/fancy_example/templates/factory/list
new file mode 100644 (file)
index 0000000..9abbc01
--- /dev/null
@@ -0,0 +1,63 @@
+[% 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";
+                        "&darr;";
+                    ELSE;
+                        "&uarr;";
+                    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 %]
diff --git a/ex/fancy_example/templates/factory/login b/ex/fancy_example/templates/factory/login
new file mode 100644 (file)
index 0000000..af08e5b
--- /dev/null
@@ -0,0 +1,27 @@
+[% 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>
+
diff --git a/ex/fancy_example/templates/factory/macros b/ex/fancy_example/templates/factory/macros
new file mode 100644 (file)
index 0000000..fc75d09
--- /dev/null
@@ -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 <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 %]
diff --git a/ex/fancy_example/templates/factory/maypole b/ex/fancy_example/templates/factory/maypole
new file mode 100644 (file)
index 0000000..7ab2744
--- /dev/null
@@ -0,0 +1,7 @@
+<!-- boxes -->
+<div style='position:absolute;top:220px;left:130px;border-bottom-width:260px;border-right-width:370px;' class='deco1'>&nbsp;</div>
+<div style='position:absolute;top:260px;left:190px;border-bottom-width:170px;border-right-width:530px;' class='deco2'>&nbsp;</div>
+<div style='position:absolute;top:240px;left:220px;border-bottom-width:340px;border-right-width:440px;' class='deco4'>&nbsp;</div>
+<div style='position:absolute;top:160px;left:330px;border-bottom-width:160px;border-right-width:280px;' class='deco1'>&nbsp;</div>
+<div style='position:absolute;top:190px;left:290px;border-bottom-width:430px;border-right-width:130px;' class='deco2'>&nbsp;</div>
+<!-- end of boxes -->
diff --git a/ex/fancy_example/templates/factory/maypole.css b/ex/fancy_example/templates/factory/maypole.css
new file mode 100644 (file)
index 0000000..d63be55
--- /dev/null
@@ -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 (file)
index 0000000..0c8b168
--- /dev/null
@@ -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 %]
+<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> 
diff --git a/ex/fancy_example/templates/factory/pager b/ex/fancy_example/templates/factory/pager
new file mode 100644 (file)
index 0000000..78c89fd
--- /dev/null
@@ -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<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 _ "&amp;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 %]
diff --git a/ex/fancy_example/templates/factory/search_form b/ex/fancy_example/templates/factory/search_form
new file mode 100644 (file)
index 0000000..d10101e
--- /dev/null
@@ -0,0 +1,22 @@
+<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>
diff --git a/ex/fancy_example/templates/factory/search_form_recursive b/ex/fancy_example/templates/factory/search_form_recursive
new file mode 100644 (file)
index 0000000..5d540fb
--- /dev/null
@@ -0,0 +1,9 @@
+<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>
diff --git a/ex/fancy_example/templates/factory/title b/ex/fancy_example/templates/factory/title
new file mode 100644 (file)
index 0000000..401f0a3
--- /dev/null
@@ -0,0 +1 @@
+    <a href="[% base %]/frontpage">[% config.application_name %]</a>
diff --git a/ex/fancy_example/templates/factory/view b/ex/fancy_example/templates/factory/view
new file mode 100644 (file)
index 0000000..9f06086
--- /dev/null
@@ -0,0 +1,32 @@
+[%#
+
+=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 %]