X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=doc%2FRequest.pod;h=bc2b303555fedc51257a4e446fefc499def7aba8;hb=85968ef28d078868c8e2865a3e76f81ddd4ec94c;hp=96f7c552ae954e378d696f61bfaf507654ed2211;hpb=2c2a72adcc01c1260b410103fa6bab06623f04be;p=maypole.git diff --git a/doc/Request.pod b/doc/Request.pod index 96f7c55..bc2b303 100644 --- a/doc/Request.pod +++ b/doc/Request.pod @@ -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 routines in a separate module, +so you say: + + package BeerDB::Beer; + BeerDB::Beer->has_a(brewery => "BeerDB::Brewery"); + sub foo :Exported {} + +And in F, you put: + + use BeerDB::Beer; + +It doesn't work. + +B: It doesn't work because of the timing of the module +loading. C will try to set up the C 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 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 module programatically to create test suites for your application. See the Maypole tests themselves or the documentation to C 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: So far we've been using the C 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. This is responsible for taking +C<$r-E{path}> and filling the C, C and C slots +of the request object. Normally it does this just by splitting the path +on Cs, but you can do it any way you want, including getting the +information from C form parameters or session variables. + +For instance, suppose we want our URLs to be of the form +C, we could provide a C 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 from the query. Later methods +will confirm whether or not these tables and actions exist. + +See L 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: 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 config parameter or, if this is not +given, calling the C 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{output}>. +=head3 REST + +You want to provide a programmatic interface to your Maypole site. + +B: The best way to do this is with C, which uses a +descriptive URL to encode the request. For instance, in L 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, 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 or L) request +a URL of the form +C. +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 +action, and use it to fill in the output in the same way as we did when +displaying a picture. Since C is not the ID of a +row in the user table, it will appear in the C 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 will be placed in the C 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 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: This is basically what the default C template and +C 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, and we process the same F 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"; + "

"; + ""; classmetadata.colnames.$col; ""; + ": "; + item.to_field(col).as_HTML; + "

"; + IF errors.$col; + ""; errors.$col; ""; + END; + END; + +If we're designing our own templates, instead of using generic ones, we +can make this process a lot simpler. For instance: + +
+ + + [% IF errors.forename OR errors.surname %] + + + + + [% 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. Hence: + + + + +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: + + + + =head3 Uploading files and other data You want the user to be able to upload files to store in the database.
+ First name: + + Last name: +
[% errors.forename %] [% errors.surname %]
+ First name: + + Last name: +
+ First name: + + Last name: +