package Catalyst::Plugin::StatusMessage;
  $Catalyst::Plugin::StatusMessage::VERSION = '1.002000';

use strictures 1;
use Sub::Name ();

=head1 NAME

Catalyst::Plugin::StatusMessage - Handle passing of status (success and error)
messages between screens of a web application.



    use Catalyst qr/

In controller where you want to save a message for display on the next page
(here, once the "delete" action taken is complete, we are redirecting to a
"list" page to show the status [we don't want to leave the delete action in the
browser URL]):

        {mid => $c->set_status_msg("Deleted widget")}));

Or, to save an error message:

        {mid => $c->set_error_msg("Error deleting widget")}));

Then, in the controller action that corresponds to the redirect above:

    sub list :Path {
        my ($self, $c) = @_;

And, to display the output (here using L<Template Toolkit|Template>):

    <span class="message">[% status_msg %]</span>
    <span class="error">[% error_msg %]</span>


There are a number of ways people commonly use to pass "status messages"
between screens in a web application.

=over 4

=item *

Using $c->stash: The stash only exists for a single request, so this
approach can leave the wrong URL in the user's browser.

=item *

Using $c->flash: The "flash" feature does provide a mechanism where the
application can redirect to an appropriate URL, but it can also lead to
a race condition where the wrong status message is displayed in the
wrong browser window or tab (and can therefore be confusing to the users
of your application).

=item *

Query parameters in the URL: This suffers from issues related to
long/ugly URLs and leaves the message displayed even after a browser


This plugin attempts to address these issues through the following mechanisms:

=over 4

=item *

Stores messages in the C<$c-E<gt>session> so that the application is free
to redirect to the appropriate URL after an action is taken.

=item *

Associates a random 8-digit "token" with each message, so it's completely
unambiguous what message should be shown in each window/tab.

=item *

Only requires that the token (not the full message) be included in the
redirect URL.

=item *

Automatically removes the message after the first time it is displayed.
That way, if users hit refresh in their browsers they only see the
messages the first time.


=head1 METHODS

=head2 load_status_msgs

Load both messages that match the token parameter on the URL (e.g., into the stash
for display by the viewer.

In general, you will want to include this in an C<auto> or "base" (if
using Chained dispatch) controller action.  Then, if you have a
"template wrapper page" that displays both "C<status_msg>" and
"C<error_msg>", you can automatically and safely send status messages to
any related controller action.


=head2 session_prefix

The location inside $c->session where messages will be stored.  Defaults
to "C<status_msg>".

=head2 token_param

The name of the URL param that holds the token on the page where you
want to retrieve/display the status message.  Defaults to "C<mid>".

=head2 status_msg_stash_key

The name of the stash key where "success" status messages are loaded
when C<$c-E<gt>load_status_msgs> is called.  Defaults to C<status_msg>.

=head2 error_msg_stash_key

The name of the stash key where error messages are loaded when
C<$c-E<gt>load_status_msgs> is called.  Defaults to C<error_msg>.

=head2 Configuration Example

Here is a quick example showing how Catalyst::Plugin::StatusMessage
can be configured in 

    # Configure Catalyst::Plugin::StatusMessage
        'Plugin::StatusMessage' => {
            session_prefix          => 'my_status_msg',
            token_param             => 'my_mid',
            status_msg_stash_key    => 'my_status_msg',
            error_msg_stash_key     => 'my_error_msg',


Note: You normally shouldn't need any of the information in this section
to use L<Catalyst::Plugin::StatusMessage>.

=head2 get_error_msg

A dynamically generated accessor to retrieve saved error messages


=head2 get_status_msg

A dynamically generated accessor to retrieve saved status messages


=head2 set_error_msg

A dynamically generated accessor to save error messages


=head2 set_status_msg

A dynamically generated accessor to save status messages


=head2 _get_cfg

Subref that handles default values and lets them be overriden from the MyApp


my $_get_cfg = sub {
    my ($self) = @_;

    my %config = (
        session_prefix       =>  'status_msg',
        token_param          =>  'mid',
        msg_types            =>  [ qw(status error) ],
        status_msg_stash_key =>  'status_msg',
        error_msg_stash_key  =>  'error_msg',
        %{$self->config->{"Plugin::StatusMessage"} || {}}

=head2 get_status_message_by_type

Fetch the requested message type from the user's session


sub get_status_message_by_type {
    my ($self, $token, $conf, $type) = @_;

    return delete($self->session->{$conf->{session_prefix}}{$type}{$token})||'';

=head2 set_status_message_by_type

Save a message to the user's session


sub set_status_message_by_type {
    my ($self, $conf, $type, $value) = @_;

    my $token = int(rand(90_000_000))+10_000_000;
    $self->session->{$conf->{session_prefix}}{$type}{$token} = $value;
    return $token;

=head2 load_status_msgs

Load both messages that match the token param (mid=###) into the stash
for display by the view.


sub load_status_msgs {
    my ($self) = @_;

    my $conf = $self->$_get_cfg;

    my $token  = $self->request->params->{$conf->{token_param}} || return;

        map +(
                =>  $self->get_status_message_by_type($token, $conf, $_)
        ), @{$conf->{msg_types}}

=head2 make_status_message_get_set_methods_for_type

Called at startup to install getters and setters for each type of
message (status & error)


sub make_status_message_get_set_methods_for_type {
    my ($pkg, $type) = @_;

    # Make getter for messages of $type
    my $get_name = "${pkg}::get_${type}_msg";
    my $get = Sub::Name::subname($get_name, sub {
        my ($self, $token) = @_;
        $self->get_status_message_by_type($token, $self->$_get_cfg, $type);
    # Make getter for messages of $type
    my $set_name = "${pkg}::set_${type}_msg";
    my $set = Sub::Name::subname($set_name, sub {
        my ($self, $value) = @_;
        $self->set_status_message_by_type($self->$_get_cfg, $type, $value);
    # Install getter and setter into class
        no strict 'refs';
        *{$get_name} = $get;
        *{$set_name} = $set;

# Add class methods to save/retrieve messages for status & error message types 
__PACKAGE__->make_status_message_get_set_methods_for_type($_) for qw(status error);

=head1 AUTHOR

Kennedy Clark,

With many thanks to Matt Trout (MST) for coaching on the details of Catalyst
Plugins and for most of the magic behind the current implementation.


This library is free software. You can redistribute it and/or modify it under
the same terms as Perl itself.