# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be 
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: App.pm 3043 2008-03-31 14:00:38Z josh $

package BigMed::App;
use Carp;
$Carp::Verbose = 1;
use strict;
use utf8;
use base qw(BigMed::Error);
use BigMed;
use BigMed::Plugin;
use BigMed::Trigger; #all apps can have callback hooks
use BigMed::Log; #all apps can write to logs

my $singleton;
my %parse_or_prompt_handler;

my $DATE_FORMAT = '%b %e, %Y';
my $TIME_FORMAT = '%r';

###########################################################
# CONSTRUCTORS
###########################################################

sub new {    #in most cases, the subclass will override this
    my $class   = shift;
    my $app     = bless {}, $class;
    my %options = @_;
    if ( $options{params} && ref $options{params} eq 'HASH' ) {
        my %params = %{ $options{params} };    #clone it
        $app->{_PARAMS} = \%params;
    }
    $app->register_app;
    $app;
}

sub register_app {
    my $app = shift;
    ref $app && $app->isa('BigMed::App')
      or croak "Usage: \$app_object->register_app()";

    #every time we register an app, reset the singleton
    #for both the app and BigMed. Will help make sure
    #nothing funny happens under persistent environments
    #like mod_perl, but not exactly well optimized for that
    #(and still untested).
    $singleton = $app;

    my %options = ( app => ref $app );
    $options{config} = $app->param('CONFIG') if $app->param('CONFIG');
    $options{no_config_ok} = $app->param('NO_CONFIG_OK')
      if $app->param('NO_CONFIG_OK');
    BigMed->reload(%options);

    #initialize the parse and prompt handler hash refs
    $app->register_all_app_parsers();      #should be provided by subclass
    $app->register_all_app_prompters();    #should be provided by subclass

    #register plugins
    $app->init_plugins($app->bigmed);
}

###########################################################
# OBJECT AND ENVIRONMENT ACCESSORS
###########################################################

sub app_instance {
    $singleton = $_[0]->new() unless $singleton;
    $singleton;
}

sub bigmed {
    BigMed->bigmed();
}

sub env {
    my $app = shift;
    BigMed->bigmed()->env(@_);
}

###########################################################
# LANGUAGE LOCALIZATION
###########################################################

sub language {
    my $app = shift;
    $app->bigmed->language(@_);
}

sub language_list {
    my $app = shift;
    $app->bigmed->language_list(@_);
}

###########################################################
# STUB METHODS
###########################################################

# subclasses should replace this stub method with a method
# that triggers the functionality of the module. (The
# run method is frequently supplied by application frameworks
# like CGI::Application).

sub run { }

# subclasses should replace this method with a param accessor
# that lets you get the value of a named parameter.

sub param {    #simple getter for a stub method
    my $app = shift;
    my $param_name = shift or return undef;
    $app->{_PARAMS}->{$param_name};
}

# subclasses should replace these stub methods with methods
# to escape/unescape a string appropriately for its context.
# a web application's escape method, for example, should
# escape " > < &

sub escape   { $_[1] }
sub unescape { $_[1] }

sub strip_html_tags {
    my $app = shift;
    my $string = shift;
    return '' if !defined $string;
    $string =~ s/<[^>]+>//gs;
    return $string;
}

#subclasses should almost always override this basic_message routine
#with their own routine for piping messages out to the user
sub basic_message {
    my ( $app, %message ) = @_;
    foreach my $text ( @message{ 'head', 'text' } ) {
        $text =~ s/<a href="([^"]+)"[^>]*>([^<]*)<\/a>/$2 ($1)/g;
        $text =~ s/<li>/\n\* /gi;
        $text =~ s/<[^>]+>//g;
    }
    my $result = $message{head} ? $message{head} . "\n" : '';
    $result .= $message{text} . "\n\n";
    return $message{give_back} ? $result : print $result;
}

my %TFORMAT = (
    'a' => sub { $_[0]->language( $_[1]->wkday_abbr ) },
    'A' => sub { $_[0]->language( $_[1]->wkday_full ) },
    'b' => sub { $_[0]->language( $_[1]->month_abbr ) },
    'B' => sub { $_[0]->language( $_[1]->month_full ) },
    'd' => sub { sprintf( '%02d', $_[1]->day ) },
    'e' => sub { $_[1]->day },
    'h' => sub { $_[0]->language( $_[1]->month_abbr ) },
    'H' => sub { sprintf( '%02d', $_[1]->hour ) },
    'I' => sub {
        my $h = $_[1]->hour;
        return $h > 12 ? $_[1]->hour - 12
          : $h == 0    ? 12
          : $h;
    },
    'm' => sub { sprintf( '%02d', $_[1]->month ) },
    'M' => sub { $_[1]->minute },
    'n' => sub { $_[1]->month },
    'p' => sub { $_[1]->hour >= 12 ? 'pm' : 'am' },
    'r' => sub { $_[1]->time_ampm },
    'R' => sub { $_[1]->time_24 },
    'S' => sub { $_[1]->second },
    'T' => sub { $_[1]->time_24 . q{:} . $_[1]->second },
    'y' => sub { sprintf( '%02d', ( $_[1]->year % 100 ) ) },
    'Y' => sub { $_[1]->year },
);

my $RRELATIVE_PARAMS = {
    dformat      => '%n-%e-%Y',
    tformat      => $TIME_FORMAT,
    no_time      => 1,
    not_relative => 1,
};

sub format_time {
    my $app    = shift;
    my $time = shift || undef;
    my $rparam = shift;
    my %param  = ref $rparam eq 'HASH' ? %$rparam : ();
    my ( $user, $site, $offset, $dformat, $tformat, $not_relative, $no_time ) =
      @param{qw(user site offset dformat tformat not_relative no_time)};
    if ($user) {  #TO DO: Optional user-specific offset and date/time format
    }
    
    if ($site) {
        $offset  ||= $site->time_offset;
        $dformat ||= $site->date_format
          || $site->default_value('date_format');
        $tformat ||= $site->time_format
          || $site->default_value('time_format');
    }
    my $time_obj =
      ref $time
      ? $time
      : BigMed::Time->new( bigmed_time => $time, offset => $offset );

    $dformat ||= $DATE_FORMAT;
    $tformat ||= $TIME_FORMAT;
    my $relative =
      $not_relative ? '' : $no_time ? 'relative' : "relative, $tformat";

    my $format = $no_time ? $dformat : "$dformat, $tformat";

    if ($relative) {
        my $now_obj = BigMed::Time->new( offset => $offset );
        my %check   = (
            Today     => $now_obj,
            Yesterday => $now_obj->clone->shift_time('-1d'),
            Tomorrow  => $now_obj->clone->shift_time('+1d'),
        );
        my $this = $app->format_time( $time_obj, $RRELATIVE_PARAMS );
        foreach my $d qw(Today Yesterday Tomorrow) {
            if ( $this eq $app->format_time( $check{$d}, $RRELATIVE_PARAMS ) ) {
                $relative =~ s/relative/$app->language($d)/msxeg;
                $format = $relative;
                last;
            }
        }
    
    }
    $format
      =~ s/\%([aAbBdehHImMnprRSTyY])/$TFORMAT{$1}->($app,$time_obj)/msxeg;
    return $format;
}

###########################################################
# INITIALIZATION ROUTINES
###########################################################

# sub-classes can override this method to load their own custom
# plugin routine (e.g. an app-specific subclass of BigMed::Plugin)

sub init_plugins {
    BigMed::Plugin->init_plugins();
}


###########################################################
# REGISTRATION OF PARSE AND PROMPT ROUTINES
###########################################################

# Every application subclass has its own routines for
# prompting for field values and for parsing them on
# return.

# The register_all_app_parsers and register_all_app_prompters
# methods should be defined/overriden by each subclass and
# should respectively include several register_parser and
# register_prompt calls to register the routines for each
# type of element. See e.g., BigMed::App::Web,
# BigMed::App::Web::Prompt and BigMed::App::Web::Parse.

sub register_all_app_parsers   { };    #should be provided by subclass
sub register_all_app_prompters { };    #should be provided by subclass

sub register_parser {
    _register_parse_or_prompt( 'parse', 'register_parser', @_ );
}

sub register_prompter {
    _register_parse_or_prompt( 'prompt', 'register_prompter', @_ );
}

sub _register_parse_or_prompt {
    my ( $action, $routine_name, $app, @register ) = @_;
    my $class = ref $app || $app;

    #update the routine map for this class: element_type => \&routine
    my $r_routine_map = $parse_or_prompt_handler{$class}->{$action};
    while ( my ( $type, $coderef ) = splice( @register, 0, 2 ) ) {
        $type && ref $coderef eq "CODE"
          or croak 'Usage: $app->'
          . $routine_name
          . '($element_type => \&routine)';
        $r_routine_map->{$type} = $coderef;
    }
    $parse_or_prompt_handler{$class}->{$action} = $r_routine_map;
}

###########################################################
# HANDLING PARSE AND PROMPT REQUESTS
###########################################################

#parse and panel methods always requested like so:
#$app->parse('element type', 'field name', \%options)
sub parse {
    my $coderef = $_[0]->_element_handler( 'parse', $_[1] );
    $coderef->(@_);
}

sub prompt {
    my $coderef = $_[0]->_element_handler( 'prompt', $_[1] );
    $coderef->(@_);
}

sub _element_handler {
    my ( $app, $action, $element_type ) = @_;
    my $class = ref $app || $app;

    #if we have a registered handler for this app class, use it.
    #otherwise, see if BigMed::Elements has a default handler
    my $registered =
        $element_type
      ? $parse_or_prompt_handler{$class}->{$action}->{$element_type}
      : undef;

    #prompt and parse both receive:
    #$app, $element_type, $fieldname, $roptions
    my $default;
    if ($registered) {
        return sub { $registered->( @_[0, 2, 3] ) };
    }
    elsif ( $default =
        $parse_or_prompt_handler{$class}->{$action}->{_default} )
    {
        return sub { $default->( @_[0, 2, 3] ) };
    }
    else {
        croak "No $action handler registered for the $element_type type";
    }
}

###########################################################
# PARSE AND PROMPT HELPERS
###########################################################

#formats arguments for passing to prompt. returns an
#array reference (should be dereferenced when passing
#to prompt method
sub prompt_field_ref {
    my $app = shift;
    croak 'odd number of hash arguments: ' . join( ', ', @_ ) if ( @_ % 2 );
    my %options = @_;
    my ($query, $label,   $column,    $data_class,
        $type,  $id,      $field_msg, $description,
        $value, $options, $labels,    $multiple,
      )
      = @options{
        (   'query',     'label',   'column',    'data_class',
            'prompt_as', 'id',      'field_msg', 'description',
            'value',     'options', 'labels',    'multiple'
        )
      };
    $id ||= '';

    my $temp_labels;
    if ( $column && $data_class ) {    #load class and any missing properties
        BigMed->load_required_class($data_class);
        my %properties = $data_class->properties;
        my $rcol_props = $properties{$column}
          or croak
          "prompt_data_element: Unknown column '$column' in $data_class";

        my $data_label = $data_class->data_label;
        $id ||= $column eq 'id' ? $data_label . '_id' : $column;
        $type  ||= $rcol_props->{type};
        $label ||= "$data_label:$column";
        $value = $rcol_props->{default} if !defined $value;
        if ( !defined $options && $rcol_props->{options} ) {
            my @options = @{ $rcol_props->{options} };
            $options = \@options;
        }
        if ( !defined $labels && $rcol_props->{labels} ) {
            $temp_labels = $rcol_props->{labels};
        }
        $multiple = $rcol_props->{multiple} if !defined $multiple;
    }
    $type
      or croak "prompt_field_ref: Specify data_class/column or prompt_as";
    $options ||= BigMed::Elements->property($type,'options');
    $temp_labels ||= BigMed::Elements->property($type,'labels') if !$labels;
    if ($temp_labels) {
        my %labels = %$temp_labels;
        while ( my ($key,$value) = each (%labels) ) {
            $labels{$key} = $app->language($value);
        }
        $labels = \%labels;
    }

    #determine validation type
    my $validate_as =
      !$options{no_validate}
      ? ( $options{validate_as} || $type )
      : undef;

    #localize and escape user-visible strings, if we have them
    $field_msg   &&= $app->language($field_msg);
    $label       &&= $app->language($label);
    $description &&= $app->language($description);
    $value       &&= $app->escape($value) if $options{escape};

    delete @options{
        (   'id',         'label',       'value',       'column',
            'data_class', 'prompt_as',   'no_validate', 'query',
            'status',     'description', 'validate_as', 'labels',
            'options',    'multiple',
        )
      };
    my %prompt_options = (
        %options,
        validate_as => $validate_as,
        label       => $label || '',
        value       => $value,
        field_msg   => $field_msg || '',
        description => $description || '',
        query       => $query,
        multiple    => $multiple,
    );
    $prompt_options{options} = $options if $options;
    $prompt_options{labels} = $labels if $labels;
    return [$type, $id, \%prompt_options];
}

sub parse_submission {
    my $app    = shift;
    my @fields = @_;
    my %ERROR;
    my %results;

    foreach my $field (@fields) {
        my %options = %$field;
        my ( $id, $data_class, $column, $type ) = @options{ (
          'id', 'data_class', 'column', 'parse_as'
        ) };

        if ( $column && $data_class ) { #load class for missing properties
            BigMed->load_required_class($data_class);
            my %properties      = $data_class->properties;
            my $rcol_properties = $properties{$column}
              or croak
              "prompt_data_element: Unknown column '$column' in $data_class";
            $type ||= $rcol_properties->{type};
            $options{multiple} = $rcol_properties->{multiple}
              if !defined $options{multiple}; 
            if ( !defined $options{options} && $rcol_properties->{options} ) {
                my @values = @{ $rcol_properties->{options} };
                $options{options} = \@values;
            }
            $id
              ||= $column eq 'id' ? $data_class->data_label . '_id' : $column;
        }
        $type ||= $options{prompt_as};
        $id or croak "Field id or column required for parse request";
        $type
          or croak 'parse_submission: Missing data_class/column or parse_as';
        $options{options} ||= BigMed::Elements->property($type,'options');
        delete @options{qw(id data_class column parse_as)};
        my @parse = $app->parse( $type, $id, \%options );
        $results{$id} = $parse[0];
        if ( $parse[1] ) {
            $results{_ERROR}->{$id} = $parse[1];
            $results{_ERROR}->{_FIRST_ERR} ||= $id;
        }
    }
    %results;
}

1;
__END__

=head1 BigMed::App

Base class for Big Medium applications

=head1 SYNOPSIS

    # In "MyApp.pm"...
    package MyApp;
    use base 'BigMed::App';
    
    # BigMed::App.pm does provide a basic new() constructor, but this can
    # and often should be overrided by the subclass so that it can be
    # blessed as an object of another application framework class
    # (CGI::Application, for example). If you do override the built-in
    # new constructor, it should contain a call to the register_app
    # method.
    
    sub new {
        my $class = shift;
        #create the $app object, and do more stuff ...
        
        $app->register_app;   #plug into Big Medium
    }
    
    sub register_all_app_parsers {
        #register routines to parse user-supplied values for each
        #of the BigMed::Element values
        
        my $app = shift;
        $app->register_parser(
            '_default' => \&_default_parser,
            'simple_text' => \&parse_simple_text
            'email' => sub { parse_simple_text(@_, 'email') },
            
            # ... etc. ...
        );
    }
   
    sub register_all_app_prompters {
        #register routines to prompt user for input. (In a Web context,
        #that means form inputs)
        
        my $app = shift;
        $app->register_prompter(
            '_default' => \&_default_prompt,
            'simple_text' => \&prompt_simple_text
            'email' => sub { prompt_simple_text(@_, 'email') },
            
            # ... etc. ...
        );
    }
    
    #create a basic_message method to display message to the user
    sub basic_message {
        my ($app, %message) = @_;
        print $message{head}, "\n", $message{text}, "\n\n";
    }
    
    # if the web application is in a context where strings should be escaped
    # (html, xml, etc), it should replace the BigMed::App->escape stub method
    # with a method that escapes strings appropriately.
    sub escape {
        my $string = $_[1];
        $string =~ s/&/&amp;/g;
        $string =~ s/>/&gt;/g;
        $string =~ s/</&lt;/g;
        $string =~ s/"/&quot;/g;
        $string;
    }
    
    # implement a param method for your application. at a minimum,
    # param should be a getter that allows you to retrieve parameter
    # values for the object. for example...
    sub param {
        my $app = shift;
        my $param_name = shift;
        return $app->{_params}->{$param_name};
    }
    
    # subclasses should replace the BigMed::App->run stub method with
    # a method that triggers the functionality of the module. (The
    # run method is frequently supplied by application frameworks
    # like CGI::Application).
    sub run {
        my $app = shift;
        
        #run routines based on user input
        my $query = CGI->new();
        if ( $query->param('action') eq "screen1" ) {
            $app->screen1()
        }
        
        # ... etc. ...
    }
    
    # create the routines that actually run the application
    sub screen1 {
        my $app = shift;
        
        #read values submitted by user
        my $value = $app->parse('simple_text', 'field_name', \%options);
        
        #prompt user for values
        my $field_prompt = $app->prompt_field_ref(
            data_class => 'BigMed::User',
            column => 'name',
            id => 'username',
            required => 1,
        );
        $app->prompt(@$field_prompt);
    }
    

    ### In myapp.cgi
    use MyApp;
    my $app = MyApp->new();
    $app->run();

=head1 DESCRIPTION

BigMed::App is an abstract class that allows applications to be built
upon the Big Medium system by enabling them to plug into BigMed objects
and get access to BigMed element definitions. BigMed::App is not
intended to be used directly but instead that your application module
will be implemented as a sub-class of BigMed::App.

BigMed::App is not itself an application framework but is designed
to work with application frameworks like CGI::Application.
BigMed::App provides methods for registering the application along
with accessors for retrieving configuration/environment settings and
localization strings. But it is assumed that your application module (or
its application framework) will provide constructor methods and
routines for handing I/O to the user (prompting the user for data
and parsing the user's submissions).

=head1 USAGE

=head2 Sub-classing BigMed::App

To inherit from BigMed::App, the following code should go at
the beginning of your application module, after your package declaration:

    use base 'BigMed::App';

It's anticipated that most subclasses will also use an application
framework, which in most cases will provide constructor and run()
methods. In that case, the application will usually be a sub-class
of the application framework superclass, too. Your mileage may
vary, but you will typically want to inherit from the application
framework first. Using CGI::Application as an example, that would
change this C<use> statement to:

    use base qw(CGI::Application BigMed::App);

=head2 Registering your application

BigMed::App does provide a very simple C<new()> constructor method,
but if you're using an application framework, you should use its
constructor instead. Assuming that you are indeed overriding
BigMed::App's simple constructor method, you should always call
the C<register_app> method when creating a new application object.

    sub new {
        my $class = shift;
        #create the $app object, and do more stuff ...
        
        $app->register_app;   #plug into Big Medium
    }

The C<register_app> method instantiates a new BigMed system object,
initializes the system, registers the application as the method to
use for communication with the user, and registers all of the
application's prompt and parse routines.

=head2 Registering parse and prompt routines

Your application module is responsible for prompting users for
input and for parsing the resulting submissions. In a nutshell,
"prompt" routines present input fields (in HTML forms, for example),
presenting Big Medium data in an appropriate form for the channel
being used (e.g., HTML for web applications). "Parse" routines
accept these submissions, formatting the submissions back into
Big Medium's internal data formats.

The BigMed::Elements module describes the various data formats that
Big Medium accepts.  Your application should register a prompt and
parse routine for each element for which it requires user I/O.

When the C<register_app> method is called, it calls the application's
C<register_all_app_parsers> and C<register_all_app_prompters>
methods, which should be used in the subclass to register all of the
parse and prompt routines:

    sub register_all_app_parsers {
        $app->register_parser(%parser_map);
    }

    sub register_all_app_prompters {
        $app->register_prompter(%prompter_map);
    }

=over 4

=item * $app->register_parser(%routine_map)

=item * $app->register_prompter(%routine_map)

=back

These methods register the applications parse and prompt routines.
Both arguments accept a hash argument where the element names
are keys and the values are references to the code to run:

    $app->register_parser(
        '_default' => \&_default_parser,
        'simple_text' => \&parse_simple_text
        'email' => sub { parse_simple_text(@_, 'email') },
        
        # ... etc. ...
    );

    $app->register_prompter(
        '_default' => \&_default_prompter,
        'simple_text' => \&prompt_simple_text
        'email' => sub { prompt_simple_text(@_, 'email') },
        
        # ... etc. ...
    );

When called, the referenced routines receive two arguments via the
C<parse> or C<prompt> methods (see the documentation for those
methods below):

=over 4

=item * field name

The name of the user-submitted field to parse

=item * options

A reference to a hash of options.

=back

In addition to registering parse and prompt routines for BigMed::Elements
element types, it's often useful to register some custom values, too:

=over 4

=item * _default

The routine for the _default key will be used by C<prompt> or C<parse>
requests for element types that do not themselves have a registered routine.

=item * custom presentation

There may be instances when you don't want to display an element in its
standard data format. For example, web applications might include a
prompt routine for displaying hidden fields:

    $app->register_prompter( 'hidden' => \&hidden_field );

=item * non-data fields

You can use C<prompt> or C<parse> routines to generate input/output
for fields that are not necessarily Big Medium data. For example, you
might create a custom prompt field to generate a submit button for
web forms:

    $app->register_prompter( 'submit' => \&make_submit_button );


=item * groups of fields

You can use a C<prompt> routine to create a prompter or parser for a set of
fields at once. For example, a single "image" prompt might present
web input fields for the title, caption, and upload form for an image
all at once.

=back

=head2 Override/replace BigMed::App stub methods

Big Medium expects applications to provide certain methods. Because these
methods are often channel-specific (e.g., web vs soap vs xml-rpc etc),
BigMed::App provides only placeholder stub methods for these, so your
application module should define these methods.

=head3 C<< $app->param() >>

BigMed expects to be able to call the C<param> method on application objects.
This is used to retrieve specific preference settings (described below).
BigMed::App provides a stub method for this but you will likely want
to override this with something more specific to your application module.

BigMed::App specifically makes use of two param settings when it registers
the application.

=over 4

=item * C<< $app->param('NO_CONFIG_OK') >>

If true, BigMed will not generate an error message if no configuration file
can be loaded. This is useful for setup wizards and other contexts when
the configuration file may not yet exist.

=item * C<< $app->param('CONFIG') >>

An alternate location for the configuration file. If this returns no
value, then BigMed will try to use a file named C<bm-setup.pl> in the
current working directory.

=back

=head3 C<< $app->escape('string to escape') >>

This method should be overridden by your application module to escape
a string according to the context in which it will be displayed. Web
applications, for example, should escape &, >, < and " characters into
their html entities.

BigMed::App supplies a stub C<escape> method that simply returns the
unchanged string.

=head3 C<< $app->unescape('string to unescape') >>

Likethe C<escape> method, this method should also be overridden by your application module. It should do the reverse of the C<escape> method
and should return the "unescaped" version of a string. Web applications,
for example, should convert the html entities &amp;, &gt;, &lt; and
&quot; into their plain-text versions.

BigMed::App supplies a stub C<unescape> method that simply returns the
unchanged string.

=head3 C<< $app->basic_message(head=>'headline', text=>'body text') >>

The BigMed system object relies on the application's C<basic_message>
method to send messages to the user (typically fatal error messages).
BigMed::App has a simple C<basic_message> method that prints the
message to STDOUT, but it's a good idea to create a custom
C<basic_message> method for the application, tailored to the particular
channel. For example, the C<basic_message> method for a web application
should include printing a http header.

C<basic_message> should accept a hash argument which, at a minimum,
should expect these key/value pairs (you can of course extend this
range of options for calls from within your application):

=over 4

=item * head => 'headline text'

This is summary or headline text to be displayed along with the main
body text. The head content should be ready to be displayed "as is"
(that is, it should already have been processed by the C<language>
method).

Headline text may contain HTML formatting and should be processed
if it needs to have HTML formatting removed (in the case of a
command-line interface) or further processed (in the case of a XML
interface).

=item * text => 'main message text'

This is the main text of the message to be displayed. As with the
head text, this text should be processed via C<language> before
being sent to C<basic_message>, and it may contain HTML formatting.

=item * giveback => 1

If true, basic_message should return the text to the caller without
printing the message. This is useful for testing.

=item * exit => 1

If true, C<basic_message> should call C<exit> after displaying the message.

=back

=head2 Running the application

Create a C<run> method in your application module. This is typically
inherited from an application framework, but you can also supply your own.
This method will be called from a script to run the application's
functions. In a nutshell, it should be a dispatcher that reads
the user's input, determines what action is being requested, and
runs the appropriate routine.

The script then simply uses the application module, creates an
application object, and calls the run method on it:

    use MyApp;
    
    my $app = MyApp->new();
    $app->run();
    exit;

=head2 Application methods

=head3 Objects and environment

=over 4

=item * MyApp->app_instance

Returns the singleton instance of the MyApp application object.

=item * MyApp->bigmed

Returns the singleton instance of the BigMed object. May be used as an
object method or a static class method.

=item * $app->env('VAR_NAME')

The same as calling $BigMed->env('VAR_NAME'). Retrieves the value of the
system/environment variable(s) in the argument.

=back

=head3 Language localization

=over4

=item * $app->language('string to localize');

Returns a localized/escaped version of the string.
This is a straight wrapper to the BigMed C<language> method. It's
the same as calling C<< $app->bigmed->language($string) >>.
See the BigMed documentation for details.

=item * $app->language_list(@strings_to_localize);

Returns string containing a html-formatted unordered list of
the localized/escaped strings in the array argument.

This is a straight wrapper to the BigMed C<language> method. It's
the same as calling C<< $app->bigmed->language($string) >>.
See the BigMed documentation for details.

=back

=head3 Prompting user for info

=over 4

=item * $app->prompt($element_type, $field_name, \%options)

Calls the prompt routine registered for the element type, sending
$field_name and \%options as parameters. So, for example, if you
registered \&prompt_simple_text as the code reference for the
'simple_text' element type, then this:

    $app->prompt( 'simple_text', 'myfield', { label=>'Enter a value' } );

...would be the same as calling:

    &prompt_simple_text( $app, 'myfield', { label=>'Enter a value' } );

The options that your application's C<prompt> routines use are entirely up
to you. The C<prompt_field_ref> method generates a common set of
options and can help you to automate prompt requests.

=item * $app->prompt_field_ref(%options)

Returns a reference to an array that can be passed to the C<prompt>
method to generate a prompt for a single field/entry (don't forget
to dereference the array when passing it to C<prompt>). Using the
C<prompt_field_ref> method can help to speed and automate prompt
requests.

The resulting array reference is formatted like so:
    [   $element_type,
        $field_name,
        {   value          => $field_value,
            validate_as    => 'client_validation_type',
            query          => $query_object,
            label          => 'field label',                #localized/escaped
            field_msg      => 'status or error message',    #localized/escaped
            description    => 'field description',          #localized/escaped
            %options    #other custom options are passed along
        }
    ]

This method is particularly useful for generating C<prompt>
parameters from the built-in element definitions in Big Medium
data classes so that you don't have to construct them manually each time.

For example, to build a prompt for the BigMed::User name field,
you can just do this:

    my $username_field = $app->prompt_field_ref(
        data_class => 'BigMed::User',
        column => 'name'
    );
    $prompt->(@$username_field);

You can, however, customize an entry by providing additional
options (by providing enough info, you don't even need to provide
the data_class and column values):

    my $username_field = $app->prompt_field_ref(
        prompt_as => 'simple_text',
        id => 'username',
        value => 'my user name',
        label => 'User Name',
    );
    $prompt->(@$username_field);

The C<prompt_field_ref> method accepts a hash argument with the
following key/value pairs (any additional key/value pairs are included
as-is in the generated %options parameter and will be included in
the call to the C<prompt> routine for the element type):

=over 4

=item * data_class => 'Class::Name'

The Big Medium data class to use to retrieve the element type information.

=item * column => 'column_name'

The column name of the class named by data_class to use to retrieve the
element type information.

=item * prompt_as => 'prompt_key'

Determines which C<prompt> routine to use
to generate the prompt (these keys are defined by the C<register_prompter>
method, see above). If you do not specify a prompt_as value, but the
data_class and column are both provided, then the prompt_as will be the
element type specified for this column of the data_class class. Otherwise,
the method will throw an exception complaining that it doesn't know what
type of prompt you want to build.

=item * id => 'field_name'

The field name to use to build the prompt. If no id is provided, but
the data_class and column are both provided, then the column name
will be used as the field name. If data_class or column are not
specified, then the id will be the empty string ''.

=item * label => 'field label'

The name to use to label the field for the user. C<prompt_field_ref>
will send this label to the C<language> and C<escape> methods, so this
should be the "raw," unlocalized, unescaped string. If no label is
provided, but the data_class and column are both provided, then the label
will be the concatenation of the class's display label, a colon, and the
column name.

For example, if the data_class is "BigMed::User" (whose display label is
"user") and the column is "name," the resulting label would be:
C<user:name>

Otherwise, if no label is provided, the label value in the \%options
hash reference will be the empty string ''.

=item * value => $field_value

The value to use to populate the prompt fields if appropriate (e.g.,
"value" attributes in web input fields). This value can be a string,
array reference, hash reference, object, etc -- whatever type of
value the C<prompt> application expects.

If value is not defined, but the data_class and column are both provided,
then the value will be the default value specified for that column in
the data class.

The value is not escaped before being placed into the prompt field
(except in the case of the query object, see below) unless the escape
parameter is set. Otherwise, any escaping must
be done elsewhere; either before being passed to C<make_field_ref>
or in the application's C<prompt> routine for the field. See the
C<prompt> documentation for the subclass.

Note that if a query object is supplied in the query parameter
and if it contains a defined value for the field, the query value will
always be used as the value instead of the value parameter.

=item * escape => 1

If set to a true value, prompt_field_ref will escape the value.

=item * query => $query_object

A CGI object (or other object that supplies the C<param> method). If
the object contains a defined value for the field name --
$query->param('field name') -- then that value will be used
to pre-populate the field value, in preference over the value parameter.
This is useful when you want to re-display a user's form submission
to them in case of validation errors, for example.

As with the value parameter above, the query value is not escaped
by C<prompt_field_ref>. The query object is simply passed along as-is
for processing by the C<prompt> method.

=item * description => 'A descriptive caption for the field'

An optional text string describing what the field is for.
C<prompt_field_ref> will send this label to the C<language> method,
so this should be the "raw," unlocalized, unescaped string.

=item * field_msg => 'alert message to display with this field'

A string to display as an error or alert message along with the field.
Useful for including server-side validation messages, for example.

C<prompt_field_ref> will send this label to the C<language> and
C<escape> methods, so this should be the "raw," unlocalized,
unescaped string.

=item * validate_as => 'field_type'

Optional value to specify what type of client-side validation that you
would like to do on the field. If no value is supplied, this will be
the same as the C<prompt_as> value.

=item * no_validate => 1

If true, this indicates that you do not want client-side validation
for this field (ignored if you supply a V<validate_as> value).

=item * options => [$value1, $value2 ... ]

For prompts that offer a choice from a set of predefined values (e.g.
C<< <select> >> tags), this parameter provides a reference to the array
of values.

If the options parameter is omitted, but the data_class and column
are both provided, then the options parameter defined for the column in
the BigMed::Data class will be used, if any. If none are defined there,
the default options for the BigMed::Element type are retrieved, if any.

=item * labels => { $value1 => $label1, $value2 => $label2 ... }

For prompts that offer a choice from a set of predefined values (e.g.
C<< <select> >> tags), this parameter provides the display labels
for each value. The keys are the values from the C<options> parameter
above, and the values are the localized labels to display.

If the labels parameter is omitted, but the data_class and column
are both provided, then the labels parameter defined for the column in
the BigMed::Data class will be used, if any. If none are defined there,
the default labels for the BigMed::Element type are retrieved, if any.

=item * other key/value pairs

Other key/value pairs will be passed along as-is in the options hash
in case your application's C<prompt> routines require additional options.

=back

=back

=head3 Parsing user submissions

=over 4

=item * $app->parse($element_type, $field_name, \%options)

Following the same format as the C<prompt> method, C<parse>
calls the parse routine registered for the element type, sending
the application object, $field_name and \%options as parameters.
So, for example, if you registered \&parse_simple_text as the code
reference for the 'simple_text' element type, then this:

    $app->parse( 'simple_text', 'myfield', \%options );

...would be the same as calling:

    &parse_simple_text( $app, 'myfield', \%options );

It's recommended that you build your C<parse> routines to return
the value as the return value or, if there's a validation error,
to return two values: undef as the first value and the error
message in the second value. This format allows you to make best
use of the C<parse_submission> method below to batch-process
submissions.

=item * C<< $app->parse_submission(@field_info) >>

Parses several user submissions at once and returns a hash of key/value
pairs with field names as keys and the value returned from the C<parse>
routine for that field as the value. There is also a special value with
key _ERROR which contains validation error information.

The method accepts an array of hash references, where each hash ref
represents a single field to parse.

    my %field = $app->parse_submission(
        {   data_class => 'BigMed::User',
            column     => 'name',
            required   => 1,
        },
        {   data_class => 'BigMed::User',
            column     => 'password',
            required   => 1,
        },
        {   id       => 'license_number',
            required => 1,
            parse_as => 'simple_text',
        },
    );
    
    #if you find a validation error, call some routine to prompt
    #the user again, along with the error messages
    show_form(field_msg=>$field{_ERROR}) if $field{_ERROR};

The parameters for each hash reference may optionally refer to a Big
Medium data class and associated column, and Big Medium will use that
information to figure out which parser routine to use and the ID of the
field name to select. Or you can provide that information yourself using
the id and parse_as parameters.

Each hash reference accepts the following parameters; you may include
other parameters, too, and they will be passed along to the C<prompt>
method as part of the \%options hash reference.

=over 4

=item * data_class => 'Class::Name'

The Big Medium data class to use to retrieve the element type information.

=item * column => 'column_name'

The column name of the class named by data_class to use to retrieve the
element type information.

=item * parse_as => 'parse_key'

Determines which C<parse> routine to use to read the user's submission
(these keys are defined by the C<register_parser> method, see above).
If you do not specify a parse_as value, but the data_class and column are
both provided, then the prompt_as will be the element type specified for
this column of the data_class class. Otherwise, the method will throw an
exception complaining that it doesn't know what parser to use.

=item * id => 'field_name'

The id of the field name to parse. If no id is provided, but
the data_class and column are both provided, then the column name
will be used as the field name. If data_class or column are not
specified, then the method will throw an exception.

=item * options => \@values

An optional set of values to which the response should be constrained.
If this option is left undefined but the data_class and column are
both provided, then the options value (if any) defined for that column
and data class will be used.

=back

The method returns a hash value with the field names as keys and the field's
parsed value as the value.

If there is a validation problem with the field, the field's
value will be undef. Also, when there are validation problems, a hash
reference is placed in the _ERROR key. This hash reference contains
(unlocalized/unescaped) error messages, keyed to the field names with
errors. The _ERROR hash reference also has a key named _FIRST_ERR which
has the field name of the first error encountered (useful, for example,
if you want to give focus to the first error in a web form).

=item * $app->format_time($bigmed_time_or_obj, \%param)

Returns a formated timestamp string based on the argument parameters.
The routine accepts a Big Medium system time string or BigMed::Time
object, followed by an optional hash of parameter values:

=over 4

=item * offset => $offset

If supplying a time string instead of a time object, this value will be
used as the time zone offset of the local time
(see the documentation for BigMed::Time for details about the expected
format of the offset value).

=item * dformat => $date_format

If supplying a time string instead of a time object, this value
specifies the date format to use. The syntax of the date format string
supports most of the syntax options provided by the Unix strftime library
(see below for the supported syntax).

Default value is '%b %e, %Y' (Jan. 14, 2006).

=item * tformat => $time_format

If supplying a time string instead of a time object, this value specifies
the time format to use. Default value is '%r' (2:47PM).

=item * site => $site_obj

A site object to use for date/time format preferences and timezone offset
if those values are not explicitly provided in the parameters described
above.

=item * not_relative => 1

Boolean value indicating that the date should not be relative ('yesterday,'
'today,' or 'tomorrow'). Default is false, which means relative dates
are displayed by default.

=item * no_time => 1

Boolean value indicating that the time should not be included, only the date.
Default value is false, which means times are included by default.

=back

=back

=head4 Time/Date Format Strings

The time and date format strings for format_time support most of the syntax
options provided by the Unix strftime library.

=over 4

=item %a

Abbreviated weekday name (localized and escaped)

=item %A

Full weekday name (localized and escaped)

=item %b

Abbreviated month name (localized and escaped)

=item %B

Full month name (localized and escaped)

=item %d

Day of the month as a decimal number (01-31)

=item %e

Day of the month, no leading zero (1-31)

=item %h

Same as %b, abbreviated month name

=item %H

The hour for a 24-hour clock (00-23).

=item %I

The hour for a 12-hour clock, no leading zero (1-12)

=item %m

The month as a number (01-12)

=item %M

The minute (00-59). 

=item %n

The month as a number, no leading zero (1-12)

=item %p

"am" or "pm"

=item %r

The time in am or pm notation; this is equivalent to: %I:%M%p

=item %R

The time in 24-hour notation; this is equivalent to: %H:%M

=item %S

The second (00-60). 

=item %T

The time, with seconds, in 24-hour notation: %H:%M:%S

=item %y

The last two digits of the year (00-99) 

=item %Y

The full four-digit number

=back

=head3 Logging Methods

BigMed::App uses BigMed::Log to add and record to Big Medium's system logs.
See the BigMed::Log module for documentation of its exported methods.

=head3 Callback/Trigger Methods

BigMed::App uses BigMed::Trigger to manage callbacks. See the BigMed::Trigger
module for documentation of its exported methods (add_trigger and
call_trigger).

=head3 Error Methods

BigMed::App is a subclass of BigMed::Error and inherits all of that class's
error methods, allowing the application class and objects to set and
retrieve error values shared by other BigMed modules. The methods:

=over 4

=item * MyApp->set_error(head=>$headline_key, text=>$text_key)

=item * MyApp->set_io_error($fh, $action, $filepath, $error)

=item * MyApp->error()

=item * MyApp->display_error()

=item * MyApp->error_html_hash()

=item * MyApp->clear_error()

=back

For more, see the BigMed::Error documentation.


=head1 Author & Copyrights

This module and all Big Medium modules are copyright Josh Clark
and Global Moxie. All rights reserved.

Use of this module and the Big Medium content
management system are governed by Global Moxie's software licenses
and may not be used outside of the terms and conditions outlined
there.

For more information, visit the Global Moxie website at
L<http://globalmoxie.com/>.

Big Medium and Global Moxie are service marks of Global Moxie
and Josh Clark. All rights reserved.

=cut

