#!/usr/bin/perl -w package ResultHelper; use strict; use Tracer; use FIG; use URI::Escape; =head1 Search Result Display Helper =head2 Introduction This is the base class for search output. It provides common methods for formatting the output and providing options to the caller. This class is never used by itself. Instead, a subclass (whose name begins with C is constructed. The following fields are maintained by this object. =over 4 =item parent The parent search helper. =item record An B representing the current data. =item columns Reference to a hash specifying the different possible columns that can be included in the result. =back Additional fields may be appended by the subclasses. =head2 Column Processing The subclass will generally have multiple column names defined. For each column, several bits of information are needed-- how to format the column, how to compute the column value at search time, how to compute it at run time (which is optional), the column title to be used, and whether or not the column should be included in a download. The orthodox object-oriented method for doing this would be to define a B class and define each possible column using a subclass. To make things a little less cumbersome, we instead define each column using a static method in the subclass. The method gets as its parameters the result helper object and the type of information required. For example, the following call would ask for the title of a column called C. my $title = RHFeatures::orgName(title => $rhelp); The B method itself would look like this. sub orgName { # Get the parameters. my ($type, $rhelp, $key) = @_; # Declare the return variable. my $retVal; # Process according to the information requested. if ($type eq 'title' { # Return the title for this column. $retVal = 'Organism and Gene ID'; } elsif ($type eq 'download') { # This field should be included in a download. $retVal = 'text'; # or 'num', or 'list', or '' } elsif ($type eq 'style') { # This is a text field, so it's left-aligned. $retVal = "leftAlign"; } elsif ($type eq 'value') { # Get the organism and feature name. $rhelp->FeatureName(); } elsif ($type eq 'runTimeValue') { # This field does not require a runtime value. }; return $retVal; } The method is essentially a giant case statement based on the type of data desired. The types are =over 4 =item title Return the title of the column to be used when it is displayed. =item download Identifies how the column should be downloaded. An empty string means it should not be downloaded at all. The other values are C, indicating that the column contains numeric data, C, indicating that the column contains an html-escaped string, C, indicating that the column contains a L or L, C, indicating that the column contains a comma-separated list with optional hyperlinks, or C,indicating that the column contains multi-line aligned text with individual lines separated by a C
tag. =item style Return the style to be used to display each table cell in the column. The return value is a valid C class name from the style sheet. The style sheet should contain styles for C, C, and C
to accomodate the most common requirements. =item value Return the value to be stored in the result cache. In most cases, this should be an html string. If the value is to be computed when the data is displayed (which is sometimes necessary for performance reasons), then the return value should be of the form C<%%>IC<=>I, where I is the column name and I is a value used to compute the result at display time. The key value will be passed as a third parameter to the column method. =item runTimeValue Return the value to be displayed. This method is only used when the information is not easily available at the time the result cache is built. =back The idea behind this somewhat cumbersome design is that new columns can be added very easily by simply adding a new method to the result helper. Note that a column name must be a valid PERL method name! This means no spaces or other fancy stuff. Run-time values are a bit tricky, and require some explanation. The normal procedure during a search is to compute the values to be displayed as soon as an item is found and store them directly in the result cache. Run-time values are those that are too expensive to compute during the search, so they are not computed until the result cache is displayed. Because a search can return thousands of results, but only 50 or so are displayed at a time, this makes a big difference. =head2 Extra Columns It is necessary for individual searches to be able to create output columns specific to the type of search. These are called extra columns. To create extra columns, you use the L method. This method specifies the location of an extra column in the column list, its name, and its format. The extra columns are put in whatever positions the user specifies, although if you try to put two columns in the same place or add a column before another added column, this could cause the position to shift. Unlike regular columns, there is no need to compute a value or run-time value. The other column properties (title, style, etc.) are stored in the extra column's definition in this object. When the column headers are written, the header for an extra column is in the form CI. The I is a frozen copy of the extra column's hash. When the headers are read back in, the extra column data is thawed into the hash so that the various options are identical to what they were when the result cache was created. Extra columns are the most volatile requirement in the whole search system. I will count myself happy if this implementation of them lasts more than a week. =cut # This value is used to do a single indent level in the XML output. use constant XML_INDENT => " "; =head2 Public Methods =head3 new C<< my $rhelp = ResultHelper->new($shelp); >> Construct a new ResultHelper object to serve the specified search helper. =over 4 =item shelp Parent search helper that is generating the output. =item type Classname used to format requests for columns. =item extras Reference to a hash of extra column data keyed on extra column name. For each extra column, it contains the column's current value. =item cache A hash for use by the run-time value methods, to save time when multiple run-time values use the same base object. =item columns The list of the columns to displayed in the search results. Normal columns are stored as strings. Extra columns are stored as hash references. =item record Data record for the current output row. =item id ID for the current output row. =item RETURN Returns a newly-constructed result helper. =back =cut sub new { # Get the parameters. my ($class, $shelp) = @_; # Save the result type in the CGI parms. my $cgi = $shelp->Q(); $cgi->param(-name => 'ResultType', -value => substr($class, 2)); Trace("Result helper created of type $class.") if T(3); # Create the $rhelp object. my $retVal = { parent => $shelp, record => undef, id => undef, type => $class, extras => {}, cache => {}, columns => [], }; # Return it. return $retVal; } =head3 DB C<< my $sprout = $rhelp->DB(); >> Return the Sprout object for accessing the database. =cut sub DB { # Get the parameters. my ($self) = @_; # Return the parent helper's database object. return $self->Parent()->DB(); } =head3 PutData C<< $rhelp->PutData($sortKey, $id, $record); >> Store a line of data in the result file. =over 4 =item sortKey String to be used for sorting this line of data among the others. =item id ID string for the result line. This is not shown in the results, but is used by some of the download methods. =item record An B containing data to be used by the column methods. =back =cut sub PutData { # Get the parameters. my ($self, $sortKey, $id, $record) = @_; # Save the data record and ID so the column methods can get to it. $self->{record} = $record; $self->{id} = $id; # Loop through the columns, producing output data. my @outputCols = (); for my $column (@{$self->{columns}}) { push @outputCols, $self->ColumnValue($column); } # Get the parent search helper. my $shelp = $self->{parent}; # Write the column data. $shelp->WriteColumnData($sortKey, $id, @outputCols); } =head3 GetColumnHeaders C<< my $colHdrs = $rhelp->GetColumnHeaders(); >> Return the list of column headers for this session. The return value is a reference to the live column header list. =cut sub GetColumnHeaders { # Get the parameters. my ($self) = @_; # Return the column headers. return $self->{columns}; } =head3 DownloadFormatsAvailable C<< my %dlTypes = $rhelp->DownloadFormatsAvailable(); >> Return a hash mapping each download type to a download description. The default is the C format, which is a tab-delimited download, and the C format, which is XML. If you want additional formats, override L. =cut sub DownloadFormatsAvailable { # Get the parameters. my ($self) = @_; Trace("Creating download type hash.") if T(3); # Declare the return variable. my %retVal = ( tbl => 'Results table as a tab-delimited file', xml => 'Results table in XML format'); Trace("Asking for download formats from the helper.") if T(3); # Ask for more formats. $self->MoreDownloadFormats(\%retVal); # Return the resulting hash. return %retVal; } =head3 DownloadDataLine C<< $rhelp->DownloadDataLine($objectID, $dlType, \@cols, \@colHdrs); >> Return one or more lines of download data. The exact data returned depends on the download type. =over 4 =item objectID ID of the object whose data is in this line of results. =item dlType The type of download (e.g. C, C). =item eol The end-of-line character to use. =item cols A reference to a list of the data columns, or a string containing C
or C