]> git.decadent.org.uk Git - maypole.git/blobdiff - doc/Request.pod
Win32 support
[maypole.git] / doc / Request.pod
index 96f7c552ae954e378d696f61bfaf507654ed2211..bc2b303555fedc51257a4e446fefc499def7aba8 100644 (file)
@@ -17,6 +17,38 @@ These hacks deal with changing the way Maypole relates to the outside world;
 alternate front-ends to the Apache and CGI interfaces, or subclassing chunks
 of the front-end modules to alter Maypole's behaviour in particular ways.
 
 alternate front-ends to the Apache and CGI interfaces, or subclassing chunks
 of the front-end modules to alter Maypole's behaviour in particular ways.
 
+=head3 Separate model class modules
+
+You want to put all the C<BeerDB::Beer> routines in a separate module,
+so you say:
+
+    package BeerDB::Beer;
+    BeerDB::Beer->has_a(brewery => "BeerDB::Brewery");
+    sub foo :Exported {}
+
+And in F<BeerDB.pm>, you put:
+
+    use BeerDB::Beer;
+
+It doesn't work.
+
+B<Solution>: It doesn't work because of the timing of the module
+loading. C<use Beer::Beer> will try to set up the C<has_a> relationships
+at compile time, when the database tables haven't even been set up,
+since they're set up by
+
+    BeerDB->setup("...")
+
+which does its stuff at runtime. There are two ways around this; you can
+either move the C<setup> call to compile time, like so:
+
+    BEGIN { BeerDB->setup("...") }
+
+or move the module loading to run-time (my preferred solution):
+
+    BeerDB->setup("...");
+    BeerDB::Beer->require;
+
 =head3 Debugging with the command line
 
 You're seeing bizarre problems with Maypole output, and you want to test it in
 =head3 Debugging with the command line
 
 You're seeing bizarre problems with Maypole output, and you want to test it in
@@ -40,9 +72,70 @@ You can also use the C<Maypole::CLI> module programatically to create
 test suites for your application. See the Maypole tests themselves or
 the documentation to C<Maypole::CLI> for examples of this.
 
 test suites for your application. See the Maypole tests themselves or
 the documentation to C<Maypole::CLI> for examples of this.
 
-=head3 Changing how params are parsed
+=head3 Changing how URLs are parsed
+
+You don't like the way Maypole URLs look, and want something that either
+fits in with the rest of your site or hides the internal workings of the
+system.
+
+C<Solution>: So far we've been using the C</table/action/id/args> form
+of a URL as though it was "the Maypole way"; well, there is no Maypole
+way. Maypole is just a framework and absolutely everything about it is 
+overridable. 
+
+If we want to provide our own URL handling, the method to override in
+the driver class is C<parse_path>. This is responsible for taking
+C<$r-E<gt>{path}> and filling the C<table>, C<action> and C<args> slots
+of the request object. Normally it does this just by splitting the path
+on C</>s, but you can do it any way you want, including getting the
+information from C<POST> form parameters or session variables. 
+
+For instance, suppose we want our URLs to be of the form
+C<ProductDisplay.html?id=123>, we could provide a C<parse_path> method
+like so:
+
+    sub parse_path {
+        my $r = shift;
+        $r->{path} ||= "ProductList.html";
+        ($r->{table}, $r->{action}) = 
+            ($r->{path} =~ /^(.*?)([A-Z]\w+)\.html/);
+        $r->{table}  = lc $r->{table};
+        $r->{action} = lc $r->{action};
+        my %query = $r->{ar}->args;
+        $self->{args} = [ $query{id} ];
+    }
 
 
-=head3 REST
+This takes the path, which already has the query parameters stripped off
+and parsed, and finds the table and action portions of the filename,
+lower-cases them, and then grabs the C<id> from the query. Later methods
+will confirm whether or not these tables and actions exist.
+
+See L<BuySpy.pod> for another example of custom URL processing.
+
+=head3 Maypole for mobile devices
+
+You want Maypole to use different templates to display on particular
+browsers.
+
+B<Solution>: There are several ways to do this, but here's the neatest
+we've found. Maypole chooses where to get its templates either by
+looking at the C<template_root> config parameter or, if this is not
+given, calling the C<get_template_root> method to ask the front-end to
+try to work it out. We can give the front-end a little bit of help, by
+putting this method in our driver class:
+
+    sub get_template_root {
+        my $r = shift;
+        my $browser = $r->{ar}->headers_in->get('User-Agent');
+        if ($browser =~ /mobile|palm|nokia/i) {
+            "/home/myapp/templates/mobile";
+        } else {
+            "/home/myapp/templates/desktop";
+        }
+    }
+
+(Maybe there's a better way to detect a mobile browser, but you get the
+idea.)
 
 =head2 Content display hacks
 
 
 =head2 Content display hacks
 
@@ -99,10 +192,6 @@ a user's page.
 This hack is so common that it's expected that there'll be a neater
 way of doing this in the future.
 
 This hack is so common that it's expected that there'll be a neater
 way of doing this in the future.
 
-=head3 Maypole for mobile devices
-
-XXX
-
 =head3 XSLT
 
 Here's a hack I've used a number of times. You want to store structured
 =head3 XSLT
 
 Here's a hack I've used a number of times. You want to store structured
@@ -194,6 +283,47 @@ itself; if your file is stored in the filesystem, but you have a file
 name or some other pointer in the database, you can still arrange for
 the data to be fetched and inserted into C<$r-E<gt>{output}>.
 
 name or some other pointer in the database, you can still arrange for
 the data to be fetched and inserted into C<$r-E<gt>{output}>.
 
+=head3 REST
+
+You want to provide a programmatic interface to your Maypole site.
+
+B<Solution>: The best way to do this is with C<REST>, which uses a
+descriptive URL to encode the request. For instance, in L<Flox.pod> we
+describe a social networking system. One neat thing you can do with
+social networks is to use them for reputation tracking, and we can use
+that information for spam detection. So if a message arrives from
+C<person@someco.com>, we want to know if they're in our network of
+friends or not and mark the message appropriately. We'll do this by
+having a web agent (say, L<WWW::Mechanize> or L<LWP::UserAgent>) request
+a URL of the form
+C<http://flox.simon-cozens.org/user/relationship_by_email/person%40someco.com>.
+Naturally, they'll need to present the appropriate cookie just like a
+normal browser, but that's a solved problem. We're just interested in
+the REST request.
+
+The request will return a single integer status code: 0 if they're not
+in the system at all, 1 if they're in the system, and 2 if they're our
+friend.
+
+All we need to do to implement this is provide the C<relationship_by_email>
+action, and use it to fill in the output in the same way as we did when
+displaying a picture. Since C<person%40someco.com> is not the ID of a
+row in the user table, it will appear in the C<args> array:
+
+    use URI::Escape;
+    sub relationship_by_email :Exported {
+        my ($self, $r) = @_;
+        my $email = uri_unescape($r->{args}[0]);
+        $r->{content_type} = "text/plain";
+        my $user;
+        unless (($user) = Flox::User->search(email => $email)) {
+            $r->{content} = "0\n"; return;
+        }
+
+        if ($r->{user}->is_friend($user)) { $r->{content} = "2\n"; return; };
+        $r->{content} = "1\n"; return;
+    }
+
 =head3 Component-based Pages
 
 You're designing something like a portal site which has a number of
 =head3 Component-based Pages
 
 You're designing something like a portal site which has a number of
@@ -219,6 +349,9 @@ C</links/list_comp> will be placed in the C<links> DIV. Naturally, you're
 responsible for exporting actions and creating templates which return 
 fragments of HTML suitable for inserting into the appropriate locations.
 
 responsible for exporting actions and creating templates which return 
 fragments of HTML suitable for inserting into the appropriate locations.
 
+Alternatively, if you've already got all the objects you need, you can
+probably just C<[% PROCESS %]> the templates directly.
+
 =head3 Bailing out with an error
 
 Maypole's error handling sucks. Something really bad has happened to the
 =head3 Bailing out with an error
 
 Maypole's error handling sucks. Something really bad has happened to the
@@ -476,6 +609,82 @@ on an ISBN:
 The request will carry on as though it were a normal C<do_edit> POST, but
 with the additional fields we have provided.
 
 The request will carry on as though it were a normal C<do_edit> POST, but
 with the additional fields we have provided.
 
+=head3 Catching errors in a form
+
+A user has submitted erroneous input to an edit/create form. You want to
+send him back to the form with errors displayed against the erroneous
+fields, but have the other fields maintain the values that the user
+submitted.
+
+B<Solution>: This is basically what the default C<edit> template and
+C<do_edit> method conspire to do, but it's worth highlighting again how
+they work. 
+
+If there are any errors, these are placed in a hash, with each error
+keyed to the erroneous field. The hash is put into the template as
+C<errors>, and we process the same F<edit> template again:
+
+        $r->{template_args}{errors} = \%errors;
+        $r->{template} = "edit";
+
+This throws us back to the form, and so the form's template should take
+note of the errors, like so:
+
+     FOR col = classmetadata.columns;
+        NEXT IF col == "id";
+        "<P>";
+        "<B>"; classmetadata.colnames.$col; "</B>";
+        ": ";
+            item.to_field(col).as_HTML;
+        "</P>";
+        IF errors.$col;
+            "<FONT COLOR=\"#ff0000\">"; errors.$col; "</FONT>";
+        END;
+    END;
+
+If we're designing our own templates, instead of using generic ones, we
+can make this process a lot simpler. For instance:
+
+    <TR><TD>
+    First name: <INPUT TYPE="text" NAME="forename">
+    </TD>
+    <TD>
+    Last name: <INPUT TYPE="text" NAME="surname">
+    </TD></TR>
+
+    [% IF errors.forename OR errors.surname %]
+        <TR>
+        <TD><SPAN class="error">[% errors.forename %]</SPAN> </TD>
+        <TD><SPAN class="error">[% errors.surname %]</SPAN> </TD>
+        </TR>
+    [% END %]
+
+The next thing we want to do is to put the originally-submitted values
+back into the form. We can do this relatively easily because Maypole
+passes the Maypole request object to the form, and the POST parameters
+are going to be stored in a hash as C<request.params>. Hence:
+
+    <TR><TD>
+    First name: <INPUT TYPE="text" NAME="forename"
+    VALUE="[%request.params.forename%]">
+    </TD>
+    <TD>
+    Last name: <INPUT TYPE="text" NAME="surname"
+    VALUE="[%request.params.surname%]"> 
+    </TD></TR>
+
+Finally, we might want to only re-fill a field if it is not erroneous, so
+that we don't get the same bad input resubmitted. This is easy enough:
+
+    <TR><TD>
+    First name: <INPUT TYPE="text" NAME="forename"
+    VALUE="[%request.params.forename UNLESS errors.forename%]">
+    </TD>
+    <TD>
+    Last name: <INPUT TYPE="text" NAME="surname"
+    VALUE="[%request.params.surname UNLESS errors.surname%]"> 
+    </TD></TR>
+
 =head3 Uploading files and other data
 
 You want the user to be able to upload files to store in the database.
 =head3 Uploading files and other data
 
 You want the user to be able to upload files to store in the database.