## no critic (ProhibitMixedCaseSubs)
# $Id: /local/CPAN/Mango/lib/Mango/Catalyst/Controller/Form.pm 1644 2008-06-02T01:46:53.055259Z claco  $
package Mango::Catalyst::Controller::Form;
use strict;
use warnings;

BEGIN {
    use base
      qw/Catalyst::Controller Catalyst::Component::ACCEPT_CONTEXT Class::Accessor::Grouped/;
    use Catalyst::Utils ();
    use Path::Class     ();
    use File::Basename  ();
    use Scalar::Util qw/blessed/;

    __PACKAGE__->mk_group_accessors( 'simple',          qw/forms/ );
    __PACKAGE__->mk_group_accessors( 'inherited',       qw/form_directory/ );
    __PACKAGE__->mk_group_accessors( 'component_class', qw/form_class/ );
}
__PACKAGE__->form_class('Mango::Form');

sub _parse_Form_attr {
    my ( $self, $c, $name, $value ) = @_;

    if ( my $form = $self->forms->{$value} ) {
        $form->action( '/' . $self->path_prefix . '/' );
        return Form => $form;
    }

    return;
}

sub _parse_FormFile_attr {
    my ( $self, $c, $name, $value ) = @_;

    if ( my $form = $self->_load_form_from_file( $c, $value ) ) {
        return Form => $form;
    }

    return;
}

sub COMPONENT {
    my $class  = shift;
    my $self   = $class->NEXT::COMPONENT(@_);
    my $c      = shift;
    my $prefix = Catalyst::Utils::class2prefix($class);

    $self->forms( {} );

    if ( !$self->form_directory ) {
        $self->form_directory(
            Path::Class::Dir->new( Mango->share, 'forms', $prefix ) );
    }

    my @files = glob Path::Class::File->new( $self->form_directory, '*.yml' );

    foreach my $file (@files) {
        my $filename = Path::Class::file($file)->basename;
        my ( $name, $directories, $suffix ) =
          File::Basename::fileparse( $filename, '.yml' );
        my $action = Path::Class::dir( $prefix, $name )->as_foreign('Unix');

        my $form = $self->_load_form_from_file( $c, $file );

        if ( $form->{'action'} ) {
            $form->action("$_");
        } else {
            $form->action("/$action/");
        }

        if ( $c->debug ) {
            $c->log->debug("Form $filename attached to action '$action'");
        }
        $self->forms->{$name}   = $form;
        $self->forms->{$action} = $form;
    }

    return $self;
}

sub _load_form_from_file {
    my ( $self, $c, $file ) = @_;

    if ( $c->debug ) {
        $c->log->debug("Loading form '$file'");
    }

    my $form = $self->form_class->new( { source => $file } );

    $c->add_form($form);

    return $form;
}

sub form {
    my ( $self, $name ) = @_;
    my $c = $self->context;
    my $form;

    $name ||= $c->action;

    if ( exists $c->action->attributes->{'Form'} ) {
        $form = $c->action->attributes->{'Form'}->[-1];
    }

    if ( !$form ) {
        $form = $self->forms->{$name};
    }

    if ($form) {
        $form->action( $c->request->uri->as_string );
        $form->params( $c->request );
        $form->localizer( sub { $c->localize(@_) } );

        $c->stash->{'form'} = $form;

        return $form;
    }

    return;
}

sub submitted {
    my $self = shift;
    my $form = $self->form;

    return $form ? $form->submitted : undef;
}

sub validate {
    my $self    = shift;
    my $c       = $self->context;
    my $form    = $self->form;
    my $results = $form->validate(@_);

    $c->stash->{'errors'} = $results->errors;

    return $results;
}

1;
__END__

=head1 NAME

Mango::Catalyst::Controller::Form - Catalyst controller for form based activity.

=head1 SYNOPSIS

    package MyApp::Controller::Stuff;
    use base qw/Mango::Catalyst:Controller::Form/;

    sub edit : Local {
        my ($self, $c) = @_;
        if ($self->submitted) {
            my $results = $self->validate;
            if ($results->success) {
                ...
            };
        };
    };

=head1 DESCRIPTION

Mango::Catalyst::Controller::Form is a base Catalyst controller that
automatically loads forms based on the current class/action using Mango::Form.

By default, this controller loads all forms from a directory named after the
current controller in the root/forms directory and assigns them to the
actions in that controller matching the name of the form file themselves.

For example:

    ## controller
    MyApp::Controller::Foo::Bar;

    ## directory/files
    root/forms/foo/bar/create.yml
    root/forms/foo/bar/edit.yml

    ## actions
    sub create : Local {
        $self->form; ## create.yml form
    };

    sub edit : Local {
        $self->form; ## edit.yml form
    };

If you would like to load forms from a different directory, specify that
directory using the C<form_directory> configuration option below.

=head1 CONFIGURATION

The following configuration options are used directly by this controller:

=over

=item form_class

The form class used to parse/validate forms. The default class is Mango::Form.

    __PACKAGE__->config(
        form_class => 'MyApp::Form'
    );

=item form_directory

The directory to load forms from. If no directory is specified, forms in the
current class2prefix are loaded instead.

    __PACKAGE__->config(
        form_directory => '/path/to/this/classes/forms'
    );

=back

=head1 ATTRIBUTES

The following method attribute are available:

=head2 Form

=over

=item Arguments: $name

=back

Set the name of the form to use for the current method/action.

    sub create : Form('myform') Local {
        my $self = shift;
        my $form = $self->form;  # returns myform.yml form
    };

=head2 FormFile

=over

=item Arguments: $file

=back

Sets the file name of the form to use for the current method/action.

    sub create : FormFile('/path/to/myform.yml') Local {
        my $self = shift;
        my $form = $self->form;  # returns myform.yml form
    };

=head1 METHODS

=head2 COMPONENT

Creates an instance of the current controller and loads all available forms
from the appropriate directory.

=head2 ACCEPT_CONTEXT

Accepts the current context and stores a weakened reference to it. This is
just a hack so $self->form can access context without having to call
$c->forward('form') all the time.

=head2 form

=over

=item Arguments: $name

=back

Gets the form for the current action.

    sub edit : Local {
        $self->form; ## root/forms/controller/name/edit.yml
    };

If you wish to work with a specific form, simply pass the name of that file,
without the .yml extension, to C<form>:

    sub foo : Local {
        $self->form('edit'); ## root/forms/controller/name/edit.yml
    };

When a form is returns, the following are set automatically:

=over

=item params are set to $c->request

=item action is set to $c->action

=item localizer is set to $c->localize

=back

=head2 form_class

=over

=item Arguments: $class

=back

Gets/sets the form class to be used when loading/validating forms. The default
class is Mango::Form.

    __PACKAGE__->form_class('MyApp::Form');

=head2 form_directory

=over

=item Arguments: $directory

=back

Gets/sets the form_directory where forms should be loaded from.

    __PACKAGE__->form_directory('/path/to/controllers/forms/');

=head2 submitted

Returns true if the current form has been submitted, false otherwise.

    sub edit : Local {
        my ($self, $c) = @_;
        if ($self->submitted) {
            ...
        };
    };

=head2 validate

Returns a Mango::Results object containing the status of the current
form validation.

    sub edit : Local {
        my ($self, $c) = @_;
        if ($self->submitted) {
            my $results = $self->validate;
            if ($results->success) {
                ...
            } else {
                print @{$results->errors};
            };
        };
    };

=head1 SEE ALSO

L<Mango::Form>, L<Mango::Form::Results>

=head1 AUTHOR

    Christopher H. Laco
    CPAN ID: CLACO
    claco@chrislaco.com
    http://today.icantfocus.com/blog/
