API additions and enhancements:
Maypole::Application:
- -Init flag (wishlist 14123)
+ - -Init flag (wishlist 14123)
+ - recognises Maypole::HTTPD and installs Maypole::HTTPD::Frontend
+ as its frontend
Maypole::Headers:
add() alias to push() (wishlist 14142)
Maypole:
- - session() attribute, and get_session() method (no-op)
- - user() attribute, and get_user() method (no-op)
- - get_session() now called during handler_guts() before authenticate()
+ - get_session() method (no-op)
+ - get_user() method (no-op)
+ - get_session() is called during handler_guts() before authenticate()
- new preprocess_path() method added and called by parse_path(),
parse_path() will leave any properties set by preprocess_path() in
place
- start_request_hook() added
+ - status() attribute added (though only used by start_request_hook()
+ so far)
- setup() split into setup(), setup_model(), and load_model_subclass()
- added new path processing methods for ssl and default table/action
- added make_path()
- added make_uri()
Templates:
- Improved pager macro/include
+ - added the status() attribute, although it's not used in many places
+ yet
Bug fixes:
Fix to cgi_maypole.t (bug 11346)
sub get_request {
my ($self, $r) = @_;
- my $ar = (APACHE2) ? Apache2::Request->new($r) : Apache::Request->new($r);
+ my $ar = (APACHE2) ? Apache2::Request->new($r) : Apache::Request->instance($r);
$self->ar($ar);
}
use Maypole::Headers;
use Maypole::Constants;
-our $VERSION = '2.10';
+our $VERSION = '2.11';
-__PACKAGE__->mk_accessors( qw( cgi ) );
+__PACKAGE__->mk_accessors( qw/cgi/ );
=head1 NAME
=item send_output
+Generates output (using C<collect_output>) and prints it.
+
=cut
sub send_output
{
my $r = shift;
+ print $r->collect_output;
+}
+
+=item collect_output
+
+Gathers headers and output together into a string and returns it.
+
+Splitting this code out of C<send_output> supports L<Maypole::HTTPD::Frontend>.
+=cut
+
+sub collect_output
+{
+ my $r = shift;
+
# Collect HTTP headers
my %headers = (
-type => $r->content_type,
$headers{"-$_"} = $r->headers_out->get($_);
}
- print $r->cgi->header(%headers), $r->output;
+ return $r->cgi->header(%headers) . $r->output;
}
=item get_template_root
__PACKAGE__->mk_accessors(
qw( params query objects model_class template_args output path
args action template error document_encoding content_type table
- headers_in headers_out stash session user)
+ headers_in headers_out stash status)
);
__PACKAGE__->config( Maypole::Config->new() );
You can also set the C<debug> flag via L<Maypole::Application>.
+Some packages respond to higher debug levels, try increasing it to 2 or 3.
+
+
=cut
sub debug { 0 }
$class->load_model_subclass($subclass);
$config->model->adopt($subclass) if $config->model->can("adopt");
-
-# eval "use $subclass";
-# die "Error loading $subclass: $@"
-# if $@ and $@ !~ /Can\'t locate \S+ in \@INC/;
}
}
(my $filename = $subclass) =~ s!::!/!g;
die "Loading '$subclass' failed: $@\n"
unless $@ =~ /Can\'t locate \Q$filename\E\.pm/;
- warn "Did not find external module for '$subclass'\n"
+ warn "No external module for '$subclass'"
if $class->debug > 1;
}
}
# hook useful for declining static requests e.g. images, or perhaps for
# sanitizing request parameters
- my $status = $self->start_request_hook;
- return $status unless $status == Maypole::Constants::OK();
+ $self->status(Maypole::Constants::OK()); # set the default
+ $self->__call_hook('start_request_hook');
+ return $self->status unless $self->status == Maypole::Constants::OK();
+
+ die "status undefined after start_request_hook()" unless defined
+ $self->status;
- $self->session($self->get_session);
- $self->user($self->get_user);
+ $self->get_session;
+ $self->get_user;
- $status = $self->handler_guts;
+ my $status = $self->handler_guts;
# moving this here causes unit test failures - need to check why
# before committing the move
return $status;
}
+# Instead of making plugin authors use the NEXT::DISTINCT hoopla to ensure other
+# plugins also get to call the hook, we can cycle through the application's
+# @ISA and call them all here. Doesn't work for setup() though, because it's
+# too ingrained in the stack. We could add a run_setup() method, but we'd break
+# lots of existing code.
+sub __call_hook
+{
+ my ($self, $hook) = @_;
+
+ my @plugins;
+ {
+ my $class = ref($self);
+ no strict 'refs';
+ @plugins = @{"$class\::ISA"};
+ }
+
+ # this is either a custom method in the driver, or the method in the 1st
+ # plugin, or the 'null' method in the frontend (i.e. inherited from
+ # Maypole.pm) - we need to be careful to only call it once
+ my $first_hook = $self->can($hook);
+ $self->$first_hook;
+
+ my %seen = ( $first_hook => 1 );
+
+ # @plugins includes the frontend
+ foreach my $plugin (@plugins)
+ {
+ next unless my $plugin_hook = $plugin->can($hook);
+ next if $seen{$plugin_hook}++;
+ $self->$plugin_hook;
+ }
+}
+
=item handler_guts
This is the main request handling method and calls various methods to handle the
=item start_request_hook
This is called immediately after setting up the basic request. The default
-method simply returns C<Maypole::Constants::OK>.
+method does nothing.
-Any other return value causes Maypole to abort further processing of the
-request. This is useful for filtering out requests for static files, e.g.
-images, which should not be processed by Maypole or by the templating engine:
+The value of C<< $r->status >> is set to C<OK> before this hook is run. Your
+implementation can change the status code, or leave it alone.
+
+After this hook has run, Maypole will check the value of C<status>. For any
+value other than C<OK>, Maypole returns the C<status> immediately.
+
+This is useful for filtering out requests for static files, e.g. images, which
+should not be processed by Maypole or by the templating engine:
sub start_request_hook
{
my ($r) = @_;
- return Maypole::Constants::DECLINED if $r->path =~ /\.jpg$/;
- return Maypole::Constants::OK;
+ $r->status(DECLINED) if $r->path =~ /\.jpg$/;
}
+
+Multiple plugins, and the driver, can define this hook - Maypole will call all
+of them. You should check for and probably not change any non-OK C<status>
+value:
+ package Maypole::Plugin::MyApp::SkipFavicon;
+
+ sub start_request_hook
+ {
+ my ($r) = @_;
+
+ # check if a previous plugin has already DECLINED this request
+ # - probably unnecessary in this example, but you get the idea
+ return unless $r->status == OK;
+
+ # then do our stuff
+ $r->status(DECLINED) if $r->path =~ /favicon\.ico/;
+ }
+
=cut
-sub start_request_hook { Maypole::Constants::OK }
+sub start_request_hook { }
=item is_applicable
my $action = $self->action;
return 1 if $self->model_class->is_public($action);
- warn "The action '$action' is not applicable to the table $table"
+ warn "The action '$action' is not applicable to the table '$table'"
if $self->debug;
return 0;
=cut
1;
+
+__END__
+
+ =item register_cleanup($coderef)
+
+Analogous to L<Apache>'s C<register_cleanup>. If an Apache request object is
+available, this call simply redispatches there. If not, the cleanup is
+registered in the Maypole request, and executed when the request is
+C<DESTROY>ed.
+
+This method is only useful in persistent environments, where you need to ensure
+that some code runs when the request finishes, no matter how it finishes (e.g.
+after an unexpected error).
+
+ =cut
+
+{
+ my @_cleanups;
+
+ sub register_cleanup
+ {
+ my ($self, $cleanup) = @_;
+
+ die "register_cleanup() is an instance method, not a class method"
+ unless ref $self;
+ die "Cleanup must be a coderef" unless ref($cleanup) eq 'CODE';
+
+ if ($self->can('ar') && $self->ar)
+ {
+ $self->ar->register_cleanup($cleanup);
+ }
+ else
+ {
+ push @_cleanups, $cleanup;
+ }
+ }
+
+ sub DESTROY
+ {
+ my ($self) = @_;
+
+ while (my $cleanup = shift @_cleanups)
+ {
+ eval { $cleanup->() };
+ if ($@)
+ {
+ warn "Error during request cleanup: $@";
+ }
+ }
+ }
+}
+
use strict;
use warnings;
+
use UNIVERSAL::require;
use Maypole;
use Maypole::Config;
my $frontend = 'Apache::MVC' if $ENV{MOD_PERL};
+ $frontend = 'Maypole::HTTPD::Frontend' if $ENV{MAYPOLE_HTTPD};
+
my $masonx;
if ( grep { /^MasonX$/ } @plugins )
{
my $autosetup=0;
my $autoinit=0;
my @plugin_modules;
+
+ foreach (@plugins)
{
- foreach (@plugins) {
- if (/^\-Setup$/) { $autosetup++; }
- elsif (/^\-Init$/) { $autoinit++ }
- elsif (/^\-Debug(\d*)$/) {
- my $d = $1 || 1;
- no strict 'refs';
- *{"$caller\::debug"} = sub { $d };
- warn "Debugging (level $d) enabled for $caller";
- }
- elsif (/^-.*$/) { warn "Unknown flag: $_" }
- else {
- my $plugin = "Maypole::Plugin::$_";
- if ($plugin->require) {
- push @plugin_modules, "Maypole::Plugin::$_";
- warn "Loaded plugin: $plugin for $caller"
- if $caller->can('debug') && $caller->debug;
- } else {
- die qq(Loading plugin "$plugin" for $caller failed: )
- . $UNIVERSAL::require::ERROR;
- }
+ if (/^\-Setup$/) { $autosetup++; }
+ elsif (/^\-Init$/) { $autoinit++ }
+ elsif (/^\-Debug(\d*)$/) {
+ my $d = $1 || 1;
+ no strict 'refs';
+ *{"$caller\::debug"} = sub { $d };
+ warn "Debugging (level $d) enabled for $caller";
+ }
+ elsif (/^-.*$/) { warn "Unknown flag: $_" }
+ else {
+ my $plugin = "Maypole::Plugin::$_";
+ if ($plugin->require) {
+ push @plugin_modules, "Maypole::Plugin::$_";
+ warn "Loaded plugin: $plugin for $caller"
+ if $caller->can('debug') && $caller->debug;
+ } else {
+ die qq(Loading plugin "$plugin" for $caller failed: )
+ . $UNIVERSAL::require::ERROR;
}
}
}
+
no strict 'refs';
push @{"${caller}::ISA"}, @plugin_modules, $frontend;
$caller->config(Maypole::Config->new);
described as a Presentation Model (see below). In complex Maypole applications,\r
it is good practise to separate the domain model (the 'heart' of the\r
application) into a separate class hierarchy (see\r
-L<Maypole::Manual::Inheritance).\r
+L<Maypole::Manual::Inheritance>).\r
\r
The distinction is relatively unimportant when using Maypole in 'default' mode - \r
i.e. using L<Maypole::Model::CDBI>, and allowing Maypole to autogenerate the \r
outweighed by the heuristic advantage of separating different layers into\r
separate class hierarchies.\r
\r
+=back\r
+\r
=head3 Presentation Model\r
\r
This pattern more accurately describes the role of the Maypole model.\r
the role of the C<vars()> method on L<Maypole::View::Base>, which transmits the\r
new values to the templates.\r
\r
-=back\r
-\r
=head1 AUTHOR\r
\r
David Baird, C<< <cpan@riverside-cms.co.uk> >>\r
use Maypole::Constants;
use attributes ();
+# don't know why this is a global - drb
our %remember;
sub MODIFY_CODE_ATTRIBUTES
$r->{template} = $method;
my $obj = $class->fetch_objects($r);
$r->objects([$obj]) if $obj;
+
$class->$method( $r, $obj, @{ $r->{args} } );
}
return 1 if $attrs{Exported};
- warn "$action not exported" if Maypole->debug;
+ warn "'$action' not exported";
return 0;
}
}
-sub do_edit : Exported {
- my ( $self, $r ) = @_;
- my $h = CGI::Untaint->new( %{ $r->{params} } );
- my $creating = 0;
- my ($obj) = @{ $r->objects || [] };
+sub do_edit : Exported
+{
+ my ($self, $r, $obj) = @_;
+
+ my $config = $r->config;
+ my $table = $r->table;
+
+ my $required_cols = $config->{$table}->{required_cols} || [];
+
+ ($obj, my $fatal, my $creating) = $self->_do_update_or_create($r, $obj, $required_cols);
+
+ # handle errors, if none, proceed to view the newly created/updated object
+ my %errors = $fatal ? (FATAL => $fatal) : $obj->cgi_update_errors;
+
+ if (%errors)
+ {
+ # Set it up as it was:
+ $r->template_args->{cgi_params} = $r->params;
+ $r->template_args->{errors} = \%errors;
+
+ undef $obj if $creating;
+ $r->template("edit");
+ }
+ else
+ {
+ $r->template("view");
+ }
+
+ $r->objects( $obj ? [$obj] : []);
+}
+
+# drb - I've (probably temporarily) split this out from do_edit, so it's
+# reported by Mp::P::Trace
+sub _do_update_or_create
+{
+ my ($self, $r, $obj, $required_cols) = @_;
+
my $fatal;
- if ($obj) {
+ my $creating = 0;
+ my $h = CGI::Untaint->new( %{$r->params} );
+
+ # update or create
+ if ($obj)
+ {
# We have something to edit
- eval {
- $obj->update_from_cgi( $h =>
- { required => $r->{config}{ $r->{table} }{required_cols} || [], }
- );
- };
+ eval { $obj->update_from_cgi( $h => {required => $required_cols} ) };
$fatal = $@;
}
- else {
- eval {
- $obj =
- $self->create_from_cgi( $h =>
- { required => $r->{config}{ $r->{table} }{required_cols} || [], }
- );
+ else
+ {
+ eval {
+ $obj = $self->create_from_cgi( $h => {required => $required_cols} )
};
- if ($fatal = $@) {
+
+ if ($fatal = $@)
+ {
warn "$fatal" if $r->debug;
}
$creating++;
}
- if ( my %errors = $fatal ? (FATAL => $fatal) : $obj->cgi_update_errors ) {
-
- # Set it up as it was:
- $r->{template_args}{cgi_params} = $r->{params};
- $r->{template_args}{errors} = \%errors;
-
- undef $obj if $creating;
- $r->template("edit");
- }
- else {
- $r->{template} = "view";
- }
- $r->objects( $obj ? [$obj] : []);
+
+ return $obj, $fatal, $creating;
}
-
+
sub delete : Exported {
return shift->SUPER::delete(@_) if caller ne "Maypole::Model::Base";
my ( $self, $r ) = @_;
use Test::More;
eval "use Test::Pod::Coverage 1.04";
-plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@;
+plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage ($@)" if $@;
all_pod_coverage_ok({ also_private => [ qr/^[A-Z_]+$/ ], });
make_uri get_template_root get_request
parse_location send_output
start_request_hook
- session get_session
+ get_session
+ get_user
/;
can_ok(Maypole => @API);
--- /dev/null
+html {
+ padding-right: 0px;
+ padding-left: 0px;
+ padding-bottom: 0px;
+ margin: 0px;
+ padding-top: 0px
+}
+body {
+ font-family: sans-serif;
+ padding-right: 0px;
+ padding-left: 0px;
+ padding-bottom: 0px;
+ margin: 0px; padding-top: 0px;
+ background-color: #fff;
+}
+#frontpage_list {
+ position: absolute;
+ z-index: 5;
+ padding: 0px 100px 0px 0px;
+ margin:0 0.5%;
+ margin-bottom:1em;
+ margin-top: 1em;
+ background-color: #fff;
+}
+
+#frontpage_list a:hover {
+ background-color: #d0d8e4;
+}
+
+#frontpage_list ul {
+ list-style-type: square;
+}
+
+.content {
+ padding: 12px;
+ margin-top: 1px;
+ margin-bottom:0px;
+ margin-left: 15px;
+ margin-right: 15px;
+ border-color: #000000;
+ border-top: 0px;
+ border-bottom: 0px;
+ border-left: 1px;
+ border-right: 1px;
+}
+
+A {
+ text-decoration: none;
+ color:#225
+}
+A:hover {
+ text-decoration: underline;
+ color:#222
+}
+
+#title {
+ z-index: 6;
+ width: 100%;
+ height: 18px;
+ margin-top: 10px;
+ font-size: 90%;
+ border-bottom: 1px solid #ddf;
+ text-align: left;
+}
+
+#subtitle {
+ postion: absolute;
+ z-index: 6;
+ padding: 10px;
+ margin-top: 2em;
+ height: 18px;
+ text-align: left;
+ background-color: #fff;
+}
+
+input[type=text] {
+ height: 16px;
+ width: 136px;
+ font-family: sans-serif;
+ font-size: 11px;
+ color: #2E415A;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+input[type=submit] {
+ height: 18px;
+ width: 60px;
+ font-family: sans-serif;
+ font-size: 11px;
+ border: 1px outset;
+ background-color: #fff;
+ padding: 0px 0px 2px 0px;
+ margin-bottom: 5px;
+}
+
+input:hover[type=submit] {
+ color: #fff;
+ background-color: #7d95b5;
+}
+
+textarea {
+ width: 136px;
+ font-family: sans-serif;
+ font-size: 11px;
+ color: #2E415A;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+select {
+ height: 16px;
+ width: 140px;
+ font-family: sans-serif;
+ font-size: 12px;
+ color: #202020;
+ padding: 0px;
+ margin-bottom: 5px;
+}
+
+.deco1 {
+ font-size: 0px;
+ z-index:1;
+ border:0px;
+ border-style:solid;
+ border-color:#4d6d99;
+ background-color:#4d6d99;
+}
+
+.deco2 {
+ z-index:2;
+ border:0px;
+ border-style:solid;
+ border-color:#627ea5;
+ background-color:#627ea5;
+}
+
+
+.deco3 {
+ z-index:3;
+ border:0px;
+ border-style:solid;
+ border-color:#7d95b5;
+ background-color:#7d95b5;
+}
+
+.deco4 {
+ z-index:4;
+ border:0px;
+ border-style:solid;
+ border-color:#d0d8e4;
+ background-color:#d0d8e4;
+}
+
+
+table {
+ border: 0px solid;
+ background-color: #ffffff;
+}
+
+#matrix { width: 100%; }
+
+#matrix th {
+ background-color: #b5cadc;
+ border: 1px solid #778;
+ font: bold 12px Verdana, sans-serif;
+}
+
+#matrix #actionth {
+ width: 1px;
+ padding: 0em 1em 0em 1em;
+}
+
+#matrix tr.alternate { background-color:#e3eaf0; }
+#matrix tr:hover { background-color: #b5cadc; }
+#matrix td { font: 12px Verdana, sans-serif; }
+
+#navlist {
+ padding: 3px 0;
+ margin-left: 0;
+ margin-top:3em;
+ border-bottom: 1px solid #778;
+ font: bold 12px Verdana, sans-serif;
+}
+
+#navlist li {
+ list-style: none;
+ margin: 0;
+ display: inline;
+}
+
+#navlist li a {
+ padding: 3px 0.5em;
+ margin-left: 3px;
+ border: 1px solid #778;
+ border-bottom: none;
+ background: #b5cadc;
+ text-decoration: none;
+}
+
+#navlist li a:link { color: #448; }
+#navlist li a:visited { color: #667; }
+
+#navlist li a:hover {
+ color: #000;
+ background: #eef;
+ border-top: 4px solid #7d95b5;
+ border-color: #227;
+}
+
+#navlist #active a {
+ background: white;
+ border-bottom: 1px solid white;
+ border-top: 4px solid;
+}
+
+td { font: 12px Verdana, sans-serif; }
+
+
+fieldset {
+ margin-top: 1em;
+ padding: 1em;
+ background-color: #f3f6f8;
+ font:80%/1 sans-serif;
+ border:1px solid #ddd;
+}
+
+legend {
+ padding: 0.2em 0.5em;
+ background-color: #fff;
+ border:1px solid #aaa;
+ font-size:90%;
+ text-align:right;
+}
+
+label {
+ display:block;
+}
+
+label .field {
+ float:left;
+ width:25%;
+ margin-right:0.5em;
+ padding-top:0.2em;
+ text-align:right;
+ font-weight:bold;
+}
+
+#vlist {
+ padding: 0 1px 1px;
+ margin-left: 0;
+ font: bold 12px Verdana, sans-serif;
+ background: gray;
+ width: 13em;
+}
+
+#vlist li {
+ list-style: none;
+ margin: 0;
+ border-top: 1px solid gray;
+ text-align: left;
+}
+
+#vlist li a {
+ display: block;
+ padding: 0.25em 0.5em 0.25em 0.75em;
+ border-left: 1em solid #7d95b5;
+ background: #d0d8e4;
+ text-decoration: none;
+}
+
+#vlist li a:hover {
+ border-color: #227;
+}
+
+.view .field {
+ background-color: #f3f6f8;
+ border-left: 1px solid #7695b5;
+ border-top: 1px solid #7695b5;
+ padding: 1px 10px 0px 2px;
+}
+
+#addnew {
+ width: 50%;
+ float: left;
+}
+
+#search {
+ width: 50%;
+ float:right;
+}
+
+.error { color: #d00; }
+
+.action {
+ border: 1px outset #7d95b5;
+ style:block;
+}
+
+.action:hover {
+ color: #fff;
+ text-decoration: none;
+ background-color: #7d95b5;
+}
+
+.actionform {
+ display: inline;
+}
+
+.actionbutton {
+ height: 16px;
+ width: 40px;
+ font-family: sans-serif;
+ font-size: 10px;
+ border: 1px outset;
+ background-color: #fff;
+ margin-bottom: 0px;
+}
+
+.actionbutton:hover {
+ color: #fff;
+ background-color: #7d95b5;
+}
+
+.actions {
+ white-space: nowrap;
+}
+
+.field {
+ display:inline;
+}
+
+#login { width: 400px; }
+
+#login input[type=text] { width: 150px; }
+#login input[type=password] { width: 150px; }
+
+.pager {
+ font: 11px Arial, Helvetica, sans-serif;
+ text-align: center;
+ border: solid 1px #e2e2e2;
+ border-left: 0;
+ border-right: 0;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ margin: 0px;
+ background-color: #f3f6f8;
+}
+
+.pager a {
+ padding: 2px 6px;
+ border: solid 1px #ddd;
+ background: #fff;
+ text-decoration: none;
+}
+
+.pager a:visited {
+ padding: 2px 6px;
+ border: solid 1px #ddd;
+ background: #fff;
+ text-decoration: none;
+}
+
+.pager .current-page {
+ padding: 2px 6px;
+ font-weight: bold;
+ vertical-align: top;
+}
+
+.pager a:hover {
+ color: #fff;
+ background: #7d95b5;
+ border-color: #036;
+ text-decoration: none;
+}
+
+++ /dev/null
-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 .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;
-}
-
Write Maypole::Manual::Exceptions\r
Test and refactor external_redirect()\r
\r
-Fix Mp::P::USC\r
+Fix Mp::P::USC. \r
\r
2.12\r
====\r
Implement internal_redirect().\r
Build a more sophisticated app for testing. \r
Move class_of() to the controller - need to do this to support multiple models. \r
-Multiple model support.\r
+Multiple model support - URLs like /$base/$model/$table/$action/$id.\r
+Refactor M-P-USC and M-P-Session into M-P-User, M-P-Session, and M-P-Cookie\r
+\r
\r
3.0\r
====\r