]> 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.
 
+=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
@@ -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.
 
-=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
 
@@ -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.
 
-=head3 Maypole for mobile devices
-
-XXX
-
 =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}>.
 
+=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
@@ -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.
 
+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
@@ -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.
 
+=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.