[Bio] / Sprout / ResultHelper.pm Repository:
ViewVC logotype

View of /Sprout/ResultHelper.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (download) (as text) (annotate)
Mon Jul 16 20:04:51 2007 UTC (12 years, 4 months ago) by parrello
Branch: MAIN
Converted to the new search system.

#!/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<RH> is constructed.

The following fields are maintained by this object.

=over 4

=item parent

The parent search helper.

=item record

An B<ERDBObject> 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<Column> 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<orgName>.

    my $title = RHFeatures::orgName(title => $rhelp);

The B<orgName> 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<num>, indicating that the column contains
numeric data, C<text>, indicating that the column contains an html-escaped string,
C<link>, indicating that the column contains a L</Formlet>, C<list>, indicating that
the column contains a comma-separated list with optional hyperlinks, or C<align>,
indicating that the column contains multi-line aligned text with individual lines
separated by a C<br> tag.

=item style

Return the style to be used to display each table cell in the column. The return
value is a valid C<TD> class name from the style sheet. The style sheet should
contain styles for C<leftAlign>, C<rightAlign>, and C<center> 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<%%>I<colName>C<=>I<key>, where I<colName> is the column name and
I<key> 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</AddExtraColumn> 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 C<X=>I<something>. The I<something> 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<ERDBObject> 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<tbl> format, which is a tab-delimited download, and the C<xml> format,
which is XML. If you want additional formats, override L</MoreDownloadFormats>.

=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<tbl>, C<fasta>).

=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<header> or C<footer>. The strings will cause the header lines
or footer lines to be output rather than a data line.

=item colHdrs

A reference to a list of the column headers. Each header describes the data found
in the corresponding column of the I<cols> list.

=item RETURN

Returns a list of strings that can be written to the download output.

=back

=cut

sub DownloadDataLine {
    # Get the parameters.
    my ($self, $objectID, $dlType, $cols, $colHdrs) = @_;
    # Declare the return variable.
    my @retVal = ();
    # Check the download type.
    if ($dlType eq 'tbl' || $dlType eq 'xml') {
        # Check for headers or footers.
        if ($cols eq 'header') {
            # Here we want headers. Only the XML type has them.
            if ($dlType eq 'xml') {
                @retVal = ('<?xml version="1.0" encoding="utf-8" ?>',
                           '<Results>');
            }
        } elsif ($cols eq 'footer') {
            # Here we want footers. Again, only the XML type requires them.
            if ($dlType eq 'xml') {
                @retVal = ('</Results>');
            }
        } else {
            # Here we are downloading the displayed columns as a tab-delimited file or
            # as XML and we are tasked with producing the output lines for the current
            # row of data. The first thing is to get the download format information
            # about the columns.
            my @keepCols = map { $self->ColumnDownload($_) } @{$colHdrs};
            # Remove the columns that are not being kept. The list we create here
            # will contain the name of each column, its value, and its download format.
            my @actualCols = ();
            for (my $i = 0; $i <= $#keepCols; $i++) {
                if ($keepCols[$i]) {
                    push @actualCols, [$colHdrs->[$i], $self->GetRunTimeValues($cols->[$i]), $keepCols[$i]];
                }
            }
            # Now it's time to do the actual writing, so we need to know if this
            # is XML or tab-delimited.
            if ($dlType eq 'tbl') {
                # Clean up the HTML.
                my @actual = map { HtmlCleanup($_->[1], $_->[2]) } @actualCols;
                # Return the line of data.
                push @retVal, join("\t", @actual);
            } elsif ($dlType eq 'xml') {
                # Convert to XML. Since a single XML tag can contain multiple lines, we re-split them.
                # This is important, because when the lines are output we need to insure the correct
                # EOL character is used.
                my @actual = map { split /\n/, "<$_->[0]>" . XmlCleanup($_->[1], $_->[2]) . "</$_->[0]>" } @actualCols;
                # Return the XML object.
                push @retVal, XML_INDENT x 1 . "<Item id=\"$objectID\">";
                push @retVal, map { XML_INDENT x 2 . $_ } @actual;
                push @retVal, XML_INDENT x 1 . "</Item>";
            }
        }
    } else {
        # Now we have a special-purpose download format, so we let the subclass deal
        # with it.
        @retVal = $self->MoreDownloadDataMethods($objectID, $dlType, $cols, $colHdrs);
    }
    # Return the result.
    return @retVal;
}

=head3 Formlet

C<< my $html = $rhelp->Formlet($caption, $url, $target, %parms); >>

Create a mini-form that posts to the specified URL with the specified parameters. The
parameters will be stored in hidden fields, and the form's only visible control will
be a submit button with the specified caption.

Note that we don't use B<CGI.pm> services here because they generate forms with extra characters
and tags that we don't want to deal with.

This method is tightly bound to L</FormletToLink>, which converts a formlet to a URL. A
change here will require a change to the other method.

=over 4

=item caption

Caption to be put on the form button.

=item url

URL to be put in the form's action parameter.

=item target

Frame or target in which the form results should appear. If C<undef> is specified,
the default target will be used.

=item parms

Hash containing the parameter names as keys and the parameter values as values.

=back

=cut

sub Formlet {
    # Get the parameters.
    my ($self, $caption, $url, $target, %parms) = @_;
    # Compute the target HTML.
    my $targetHtml = ($target ? " target=\"$target\"" : "");
    # Start the form.
    my $retVal = "<form method=\"POST\" action=\"$url\"$target>";
    # Add the parameters.
    for my $parm (keys %parms) {
        $retVal .= "<input type=\"hidden\" name=\"$parm\" value=\"$parms{$parm}\" />";
    }
    # Put in the button.
    $retVal .= "<input type=\"submit\" name=\"submit\" value=\"$caption\" class=\"button\" />";
    # Close the form.
    $retVal .= "</form>";
    # Return the result.
    return $retVal;
}

=head3 HtmlCleanup

C<< my $text = ResultHelper::HtmlCleanup($htmlText, $type); >>

Take a string of Html text and clean it up so it appears as real text.
Note that this method is not yet sophisticated enough to detect right-angle brackets
inside tag parameters, nor can it handle style or script tags. Its only sophistication
is that it knows how to convert a formlet to a URL. Otherwise, it is a dirt simple
method that suffices for search result processing.

=over 4

=item htmlText

Html text to clean up.

=item type

Type of column: C<num> for a number, C<text> for a string, C<list> for a
comma-separated list, and C<link> for a formlet link.

=item RETURN

Returns the downloadable form of the Html string.

=back

=cut

sub HtmlCleanup {
    # Get the parameters.
    my ($htmlText, $type) = @_;
    # Declare the return variable.
    my $retVal;
    # Check for a formlet.
    if ($type eq 'link') {
        # Here we have a formlet and we want to convert it to a URL.
        $retVal = FormletToLink($htmlText);
    } elsif ($type eq 'align') {
        # Here we have multiple lines. Convert the new-lines to serial commas.
        $retVal =~ s/<br\s*\/?>/, /g;
    } else {
        # Here we have normal HTML. Start by taking the raw text.
        $retVal = $htmlText;
        # Delete any tags. This is a very simplistic algorithm that will fail if there
        # is a right angle bracket inside a parameter string.
        $retVal =~ s/<[^>]+>//g;
        # Unescape the & tags.
        $retVal = CGI::unescapeHTML($retVal);
    }
    # Return the result.
    return $retVal;
}

=head3 XmlCleanup

C<< my $text = ResultHelper::XmlCleanup($htmlText, $type); >>

Take a string of Html text and clean it up so it appears as html.

=over 4

=item htmlText

Html text to clean up.

=item type

Type of column: C<num> for a number, C<text> for a string, C<list> for a
comma-separated list, and C<link> for a formlet link.

=item RETURN

Returns the column data in XML format.

=back

=cut

sub XmlCleanup {
    # Get the parameters.
    my ($htmlText, $type) = @_;
    # Declare the return variable.
    my $retVal;
    # Check for a formlet.
    if ($type eq 'link') {
        # Here we have a formlet and we want to convert it to a URL. We begin
        # with the action.
        if ($htmlText =~ /action="([^"]+)"/i) {
            $retVal = $1;
            # Now, parse out the parameters, all of which are stored in the formlet as hidden
            # input fields. This is the point where we assume that the formlet generates things
            # in a well-defined format.
            my @parms = ();
            while ($htmlText =~ /<input\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]+)"/ig) {
                push @parms, "$1=" . uri_escape($2);
            }
            # If there were any parameters, assemble them into the URL.
            if (scalar(@parms)) {
                $retVal .= "?" . join(";", @parms);
            }
        } else {
            # Here the column is empty. We output an empty string.
            $retVal = '';
        }
    } elsif ($type eq 'num' || $type eq 'text') {
        # Here we have a number or text. Return the raw value.
        $retVal = $htmlText;
    } elsif ($type eq 'align') {
        # Here we have aligned text. This is converted into an XML array of lines.
        # First, we find the break tags.
        my @lines = split /<br[^>]+>/, $htmlText;
        # Format the lines as an XML array. The extra new-line causes the first array
        # element to be on a separate line from the first item tag.
        $retVal = "\n" . map { XML_INDENT . "<line>$_</line>\n" } @lines;
    } elsif ($type eq 'list') {
        # Here we have a comma-delimited list of possibly-linked strings. We will convert it to
        # an XML array. First, we get the pieces.
        my @entries = split /\s*,\s*/, $htmlText;
        # Each piece is processed individually, so we can check for hyperlinks.
        # The return value starts with a new-line, so that the first list element
        # is not on the same line as the open tag.
        $retVal = "\n";
        for my $entry (@entries) {
            # Check for a hyperlink.
            if ($entry =~ /<a[^>]+(href="[^"]+")[^>]*>(.+)<\/a>/) {
                # Put the URL in the tag.
                $retVal .= XML_INDENT . "<value $1>$2</value>\n";
            } else {
                # No URL, so the tag is unadorned.
                $retVal .= XML_INDENT . "<value>$entry</value>\n";
            }
        }
    }
    # Return the result.
    return $retVal;
}

=head3 FormletToLink

C<< my $url = ResultHelper::FormletToLink($htmlText); >>

Convert a formlet to a link. This process is bound very tightly with
the way L</Formlet> generates Html. A change there requires a change
here.

=over 4

=item htmlText

HTML text for the formlet.

=item RETURN

Returns a URL that will produce the same result as clicking the formlet button.

=back

=cut

sub FormletToLink {
    # Get the parameters.
    my ($htmlText) = @_;
    # Declare the return variable.
    my $retVal;
    # We begin with the action.
    $htmlText =~ /action="([^"]+)"/i;
    $retVal = $1;
    # Now, parse out the parameters, all of which are stored in the formlet as hidden
    # input fields. This is the point where we assume that the formlet generates things
    # in a well-defined format.
    my @parms = ();
    while ($htmlText =~ /<input\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]+)"/ig) {
        push @parms, "$1=" . uri_escape($2);
    }
    # If there were any parameters, assemble them into the URL.
    if (scalar(@parms)) {
        $retVal .= "?" . join(";", @parms);
    }
    # Return the result.
    return $retVal;
}

=head3 FakeButton

C<< my $html = $rhelp->FakeButton($caption, $url, $target, %parms); >>

Create a fake button that hyperlinks to the specified URL with the specified parameters.
Unlike a real button, this one won't visibly click, but it will take the user to the
correct place.

The parameters of this method are deliberately identical to L</Formlet> so that we
can switch easily from real buttons to fake ones in the code.

=over 4

=item caption

Caption to be put on the button.

=item url

URL for the target page or script.

=item target

Frame or target in which the new page should appear. If C<undef> is specified,
the default target will be used.

=item parms

Hash containing the parameter names as keys and the parameter values as values.
These will be appended to the URL.

=back

=cut

sub FakeButton {
    # Get the parameters.
    my ($self, $caption, $url, $target, %parms) = @_;
    # Declare the return variable.
    my $retVal;
    # Compute the target URL.
    my $targetUrl = "$url?" . join(";", map { "$_=" . uri_escape($parms{$_}) } keys %parms);
    # Compute the target-frame HTML.
    my $targetHtml = ($target ? " target=\"$target\"" : "");
    # Assemble the result.
    return "<a href=\"$targetUrl\" $targetHtml><div class=\"button2 button\">$caption</div></a>";
}

=head3 Parent

C<< my $shelp = $rhelp->Parent(); >>

Return this helper's parent search helper.

=cut

sub Parent {
    # Get the parameters.
    my ($self) = @_;
    # Return the parent.
    return $self->{parent};
}

=head3 Record

C<< my $erdbObject = $rhelp->Record(); >>

Return the record currently stored in this object. The record contains the data for
the result output line being built, and is in the form of a B<ERDBObject>.

=cut

sub Record {
    # Get the parameters.
    my ($self) = @_;
    # Get the record.
    my $retVal = $self->{record};
    # If it does not exist, trace a message.
    Trace("No record found in result helper.") if T(3) && ! defined($retVal);
    # Return the record.
    return $retVal;
}

=head3 ID

C<< my $id = $rhelp->ID(); >>

Return the ID for the record currently stored in this object (if any).


=cut

sub ID {
    # Get the parameters.
    my ($self) = @_;
    # Get the record.
    my $retVal = $self->{id};
    # If it does not exist, trace a message. We say "no record found" because a
    # missing ID implies a missing record.
    Trace("No record found in result helper.") if T(3) && ! defined($retVal);
    # Return the ID.
    return $retVal;
}



=head3 Cache

C<< my $cacheHash = $rhelp->Cache(); >>

Return a reference to the internal cache. The internal cache is used by the
run-time value methods to keep stuff in memory between calls for the same
output line.

=cut

sub Cache {
    # Get the parameters.
    my ($self) = @_;
    # Return the cache.
    return $self->{cache};
}

=head3 PreferredID

C<< my $featureID = $rhelp->PreferredID($featureObject); >>

Return the preferred ID for the specified feature. The feature passed in must be in the
form of an ERDB feature object. The preferred alias type will be determined using the
CGI C<AliasType> parameter, and then cached in the feature object using the name
C<Feature(alias)> so this method can find it easily if it is needed again.

=over 4

=item featureObject

An B<ERDBObject> for the relevant feature.

=item RETURN

The preferred ID for the feature (Locus Tag, Uniprot ID, etc.) if one exists, otherwise
the FIG ID.

=back

=cut

sub PreferredID {
    # Get the parameters.
    my ($self, $featureObject) = @_;
    # Declare the return variable.
    my $retVal;
    # Check for a cached value.
    if ($featureObject->HasField('Feature(alias)')) {
        $retVal = $featureObject->PrimaryValue('Feature(alias)');
    } else {
        # Here we need to compute the alias. First, get the preferred type.
        my $aliasType = $self->Parent()->GetPreferredAliasType();
        # The fallback is to use the FIG ID.
        my $fid = $featureObject->PrimaryValue('Feature(id)');
        $retVal = $fid;
        # We only need to proceed if the preferred type is NOT FIG.
        if ($aliasType ne 'FIG') {
            # Here we need to find a real alias of the specified type. To start,
            # we need a Sprout object.
            my $sprout = $self->DB();
            # Ask for all the aliases connected to this feature ID.
            my @aliases = $sprout->GetFlat(['IsAliasOf'], 'IsAliasOf(to-link) = ?',
                                           [$fid], 'IsAliasOf(from-link)');
            # Extract an alias of the preferred type.
            my $foundAlias = AliasAnalysis::Find($aliasType, \@aliases);
            # If an alias was found, use it. Otherwise, the FIG ID will stay in place.
            if (defined($foundAlias)) {
                $retVal = $foundAlias;
            }
        }
        # Save the alias type for future calls.
        $featureObject->AddValues('Feature(alias)', $retVal);
    }
    # Return the ID computed.
    return $retVal;
}


=head2 Column-Related Methods

=head3 Compute

C<< my $retVal = $rhelp->Compute($type, $colName, $runTimeKey); >>

Call a column method to return a result. This involves some fancy C<eval> stuff.
The column method is called as a static method of the relevant subclass.

=over 4

=item type

The type of column data requested: C<title> for the column title, C<style> for the
column's display style, C<value> for the value to be put in the result cache,
C<download> for the indicator of how the column should be included in
downloads, and C<runTimeValue> for the value to be used when the result is
displayed. Note that if a run-time value is required, then the normal value
must be formatted in a special way (see L<Column Processing>).

A little fancy dancing is required for extra columns. For extra columns, only
the title, style, and download status are ever requested. 

=item colName

Name of the column of interest. The name may contain a colon, in which case
the column name is the part before the colon and the value after it is
passed to the column method as the run-time key.

=item runTimeKey (optional)

If a run-time value is desired, this should be the key taken from the value stored
in the result cache.

=item RETURN

Returns the desired result for the specified column.

=back

=cut

sub Compute {
    # Get the parameters.
    my ($self, $type, $colName, $runTimeKey) = @_;
    # Declare the return variable.
    my $retVal;
    # Check for an extra column.
    if (ref $colName eq 'HASH') {
        # Look for the appropriate data from the hash.
        if ($type eq 'value') {
            # The caller wants the column value, which is stored in the "extras"
            # member keyed by column name.
            my $realName = $colName->{name};
            $retVal = $self->{extras}->{$realName};
            Trace("Extra column $realName retrieved value is $retVal.") if T(4);
        } else {
            # The other data items are stored in the column name itself.
            $retVal = $colName->{$type};
        }
    } else {
        # Here we have a real built-in column. The search helper chooses which of
        # these to use (usually by adding to a default list), and we use static
        # methods in our subclass to process them. An eval call is used to
        # accomplish the result. First, we do some goofiness so we can deal
        # with the possible absence of a run-time key.
        my $realRunTimeKey = (defined $runTimeKey ? ", '$runTimeKey'" : "");
        # Check for a complex column name. Note that during run-time expansion, the
        # column names will have been simplified (that is, the colon will have been
        # removed and the parameter attached to the incoming run-time key.
        if ($colName =~ /(\S+):(.+)/) {
            $colName = $1;
            $realRunTimeKey = $2;
        }
        # Get the result helper type.
        my $rhType = $self->{type};
        # Create the string for returning the desired results.
        my $expression = "${rhType}::$colName($type => \$self$realRunTimeKey)";
        # Evaluate to get the result. Note we automatically translate undefined results to
        # an empty string.
        Trace("Evaluating: $expression") if T(4);
        $retVal = eval($expression) || "";
        # Check for an error.
        if ($@) {
            Trace("Evaluation failed in Compute of $expression") if T(1);
            Confess("$self->{type} column request failed: $@");
        }
    }
    # Return the computed result.
    return $retVal;
}

=head3 ColumnDownload

C<< my $flag = $rhelp->ColumnDownload($colName); >>

Return the type of data in the column, or an empty string if it should
not be downloaded. In general, all columns are downloaded except those
that are graphic representations of something.

=over 4

=item colName

Name of the column in question.

=item RETURN

Returns one of the download data types discussed in L</Column Processing>.

=back

=cut

sub ColumnDownload {
    # Get the parameters.
    my ($self, $colName) = @_;
    # Compute the result.
    my $retVal = $self->Compute(download => $colName);
    # Return it.
    return $retVal;
}

=head3 ColumnTitle

C<< my $titleHtml = $rhelp->ColumnTitle($colName); >>

Return the title to be used in the result table for the specified column.

=over 4

=item colName

Name of the relevant column.

=item RETURN

Returns the html to be used for the column title.

=back

=cut

sub ColumnTitle {
    # Get the parameters.
    my ($self, $colName) = @_;
    # Compute the result.
    my $retVal = $self->Compute(title => $colName);
    # Return it.
    return $retVal;
}

=head3 ColumnValue

C<< my $htmlValue = $rhelp->ColumnValue($colName); >>

Return the display value for a column. This could be HTML text or it
could be a run-time value specification. The column value is computed
using the data record currently stored in the result helper.

=over 4

=item colName

Name of the column whose value is desired.

=item RETURN

Returns the value to be stored in the result cache.

=back

=cut

sub ColumnValue {
    # Get the parameters.
    my ($self, $colName) = @_;
    # Compute the return value.
    my $retVal = $self->Compute(value => $colName);
    # Return it.
    return $retVal;
}

=head3 ColumnStyle

C<< my $className = $rhelp->ColumnStyle($colName); >>

Return the display style for the specified column. This must be a classname
defined for C<TD> tags in the active style sheet.

=over 4

=item colName

Name of the relevant column.

=item RETURN

Returns the name of the style class to be used for this column's cells.

=back

=cut

sub ColumnStyle {
    # Get the parameters.
    my ($self, $colName) = @_;
    # Compute the return value.
    my $retVal = $self->Compute(style => $colName);
    # Return it.
    return $retVal;
}

=head3 GetRunTimeValues

C<< my @valueHtml = $rhelp->GetRunTimeValues(@cols); >>

Return the run-time values of a row of columns. The incoming values contain
the actual column contents. Run-time columns will be identified by the
leading C<%%> marker. The run-time columns are converted in sequence
using methods in the base class.

=over 4

=item cols

A list of columns. Runtime columns will be of the format C<%%>I<colName>C<=>I<key>,
where I<colName> is the actual column name and I<key> is the key to be passed to
the evaluator. Columns that do not have this format are unchanged.

=item RETURN

Returns a list of the final values for all the run-time columns.

=back

=cut

sub GetRunTimeValues {
    # Get the parameters.
    my ($self, @cols) = @_;
    # Declare the return value.
    my @retVal = ();
    # Clear the cache. The run-time value methods can store stuff
    # in here to save computation time.
    $self->{cache} = {};
    # Loop through the columns.
    for my $col (@cols) {
        # Declare a holding variable.
        my $retVal;
        # Parse the column data.
        if ($col =~ /^%%(\w+)=(.+)/) {
            # It parsed as a run-time value, so call the Compute method.
            $retVal = $self->Compute(runTimeValue => $1, $2);
        } else {
            # Here it's a search-time value, so we leave it unchanged.
            $retVal = $col;
        }
        # Add this column to the result list.
        push @retVal, $retVal;
    }
    # Return the result.
    return @retVal;
}

=head3 SetColumns

C<< $rhelp->SetColumns(@cols); >>

Store the specified object columns. These are the columns computed by the search
framework, and should generally be specified first. If the search itself is
going to generate additional data, the columns for displaying this additional
data should be specified by a subsequent call to L</AddExtraColumn>.

=over 4

=item cols

A list of column names. These must correspond to names defined in the result
helper subclass (see L</Column Processing>).

=back

=cut

sub SetColumns {
    # Get the parameters.
    my ($self, @cols) = @_;
    # Store the columns in the column list. Note that this erases any
    # previous column information.
    $self->{columns} = \@cols;
}

=head3 AddExtraColumn

C<< $rhelp->AddExtraColumn($name => $loc, %data); >>

Add an extra column to the column list at a specified location.

=over 4

=item name

The name of the column to add.

=item loc

The location at which the column should be displayed. The column is added
at the specified column location in the column list. It may be moved,
however, if subsequent column additions are placed at or before its
specified location. To put a column at the beginning, specify C<0>;
to put it at the end specify C<undef>.

=item data

A hash specifying the title, style, and download flag for the extra
column. The download flag (key C<download>) should specify the type
of data in the column. The title (key C<title>) should be the name
displayed for the column in the result display table. The style
(key C<style>) should be the style class used for displaying the cells
in the column.

=back

=cut

sub AddExtraColumn {
    # Get the parameters.
    my ($self, $name, $loc, %data) = @_;
    # Add the name to the column hash.
    $data{name} = $name;
    # Store the result in the column list.
    $self->_StoreColumnSpec(\%data, $loc);
}

=head3 AddOptionalColumn

C<< $rhelp->AddOptionalColumn($name => $loc); >>

Store the specified column name in the column list at the
specified location. The column name must be one that
is known to the result helper subclass. This method
allows ordinary columns (as opposed to extra columns)
to be added after the initial L</SetColumns> call.

=over 4

=item name

Name of the desired column.

=item location

Location at which the desired column should be stored.

=back

=cut

sub AddOptionalColumn {
    # Get the parameters.
    my ($self, $name => $loc) = @_;
    # Currently, there is no extra work required here, but that
    # may change.
    $self->_StoreColumnSpec($name, $loc);
}

=head3 PutExtraColumns

C<< $rhelp->PutExtraColumns(name1 => value1, name2 => value2, ...); >>

Store the values of one or more extra columns. If a search produces extra columns (that is,
columns whose data is determined by the search instead of queries against the database), then
for each row of output, the search must call this method to specify the values of the various
extra columns. Multiple calls to this method are allowed, in which case each call either
overrides or adds to the values specified by the prior call.

=over 4

=item extraColumnMap

A hash keyed on extra column name that maps the column names to the column's values for the current
row of table data.

=back

=cut

sub PutExtraColumns {
    # Get the parameters.
    my ($self, %extraColumnMap) = @_;
    # Copy the hash values into the extra column hash.
    my $counter = 0;
    for my $name (keys %extraColumnMap) {
        $self->{extras}->{$name} = $extraColumnMap{$name};
        Trace("Extra column $name has value $extraColumnMap{$name}.") if T(4);
    }
}

=head2 Internal Utilities

=head3 StoreColumnSpec

C<< $rhelp->_StoreColumnSpec($column, $location); >>

Store the specified column information at the specified location in the column name list.
The information is a string for an ordinary column and a hash for an extra column. The
actual location at which the column is stored will be adjusted so that there are no
gaps in the list. If the location is undefined, it defaults to the end. Thus, C<0>
will always store at the beginning and C<undef> will always store at the end.

=over 4

=item column

A column name or extra-column hash to be stored in the column list.

=item location

The index at which the column name should be stored, or C<undef> to store it
at the end.

=back

=cut

sub _StoreColumnSpec {
    # Get the parameters.
    my ($self, $column, $location) = @_;
    # Compute the current column count.
    my $columnCount = scalar @{$self->{columns}};
    # Adjust the location.
    if (! defined($location) || $location > $columnCount) {
        $location = $columnCount;
    }
    # Insert the column into the list.
    splice @{$self->{columns}}, $location, 0, $column;
}


=head2 Virtual Methods

The following methods can be overridden by the subclass. In some cases, they
must be overridden.

=head3 DefaultResultColumns

C<< my @colNames = $rhelp->DefaultResultColumns(); >>

Return a list of the default columns to be used by searches with this
type of result. Note that the actual default columns are computed by
the search helper. This method is only needed if the search helper doesn't
care.

The columns returned should be in the form of column names, all of which
must be defined by the result helper class.

=cut

sub DefaultResultColumns {
    # This method must be overridden.
    Confess("Pure virtual call to DefaultResultColumns.");
}

=head3 MoreDownloadFormats

C<< $rhelp->MoreDownloadFormats(\%dlTypes); >>

Add additional supported download formats to the type table. The table is a
hash keyed on the download type code for which the values are the download
descriptions. There is a special syntax that allows the placement of text
fields inside the description. Use square brackets containing the name
for the text field. The field will come in to the download request as
a GET-type field.

=over 4

=item dlTypes

Reference to a download-type hash. The purpose of this method is to add more
download types relevant to the particular result type. Each type is described
by a key (the download type itself) and a description. The description can
contain a single text field that may be used to pass a parameter to the
download. The text field is of the format C<[>I<fieldName>C<]>,
where I<fieldName> is the name to give the text field's parameter in the
generated download URL.

=back

=cut

sub MoreDownloadFormats {
    Trace("Pure virtual call to MoreDownloadFormats.") if T(3);
    # Take no action.
}

=head3 MoreDownloadDataMethods

C<< my @lines = $rhelp->MoreDownloadDataMethods($objectID, $dlType, \@cols, \@colHdrs); >>

Create one or more lines of download data for a download of the specified type. Override
this method if you need to process more download types than the default C<tbl> method.

=over 4

=item objectID

ID of the object for this data row.

=item dlType

Download type (e.g. C<fasta>, etc.)

=item cols

Reference to a list of the data columns from the result cache, or alternatively
the string C<header> (indicating that header lines are desired) or C<footer>
(indicating that footer lines are desired).

=item colHdrs

The list of column headers from the result cache.

=item RETURN

Returns an array of data lines to output to the download file.

=back

=cut

sub MoreDownloadDataMethods {
    # Get the parameters.
    my ($self, $objectID, $dlType, $cols, $colHdrs) = @_;
    # If we need to call this method, then the subclass should have overridden it.
    Confess("Invalid download type \"$dlType\" specified for result class $self->{type}.");
}

1;

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3