File Coverage

blib/lib/Form/Factory.pm
Criterion Covered Total %
statement 35 39 89.7
branch 6 10 60.0
condition 2 12 16.6
subroutine 10 10 100.0
pod 5 5 100.0
total 58 76 76.3


line stmt bran cond sub pod time code
1             package Form::Factory;
2             $Form::Factory::VERSION = '0.022';
3 1     1   818 use Moose;
  1         369072  
  1         6  
4              
5 1     1   5341 use Carp ();
  1         2  
  1         17  
6 1     1   4 use Class::Load;
  1         1  
  1         449  
7              
8             # ABSTRACT: a general-purpose form handling API
9              
10              
11             sub new_interface {
12 14     14 1 538984 my $class = shift;
13 14         29 my $name = shift;
14 14         50 my $class_name = $class->interface_class($name);
15 14         515 return $class_name->new(@_);
16             }
17              
18              
19 14     14 1 48 sub interface_class { _load_class_from_name(Interface => $_[1]) }
20              
21              
22 108     108 1 15449 sub control_class { _load_class_from_name(Control => $_[1]) }
23              
24              
25 8     8 1 4870 sub feature_class { _load_class_from_name(Feature => $_[1]) }
26              
27              
28 68     68 1 5271 sub control_feature_class { _load_class_from_name('Feature::Control' => $_[1]) }
29              
30              
31             sub _class_name_from_name {
32 520     520   709 my ($prefix, $name) = @_;
33              
34             # Remove anything like #Foo, which is used to differentiate between features
35             # added by different classes in get_all_features()
36 520         911 $name =~ s/\#(.*)$//;
37              
38             # Turn a foo_bar_baz name into FooBarBaz
39 520         4000 $name =~ s/(?:[^A-Za-z]+|^)([A-Za-z])/\U$1/g;
40              
41 520         2418 return join('::', 'Form::Factory', $prefix, ucfirst $name);
42             }
43              
44             sub _load_class_from_name {
45 198     198   293 my ($given_type, $name) = @_;
46 198         192 my $ERROR;
47              
48 198         443 my $custom_type = join('::', $given_type, 'Custom');
49 198         307 for my $type ($given_type, $custom_type) {
50 206         383 my $class_name = _class_name_from_name($type, $name);
51              
52 206 100       251 if (not eval { Class::Load::load_class($class_name) }) {
  206 100       593  
53 8 50 33     6335 $ERROR ||= $@ if $@;
54 8   33     34 $ERROR ||= "failed to load $type class named $name";;
55             }
56             elsif ($type eq $custom_type) {
57 8         359 $class_name = $class_name->register_implementation;
58              
59 8 50       30 if (eval { Class::Load::load_class($class_name) }) {
  8         24  
60 8         193 return $class_name;
61             }
62             else {
63 0         0 undef $ERROR;
64 0 0 0     0 $ERROR ||= $@ if $@;
65 0   0     0 $ERROR ||= "failed to load $type class named $name";;
66             }
67             }
68             else {
69 190         11883 return $class_name;
70             }
71             }
72              
73 0           Carp::croak($ERROR);
74             }
75              
76              
77             __PACKAGE__->meta->make_immutable;
78              
79             __END__
80              
81             =pod
82              
83             =encoding UTF-8
84              
85             =head1 NAME
86              
87             Form::Factory - a general-purpose form handling API
88              
89             =head1 VERSION
90              
91             version 0.022
92              
93             =head1 SYNOPSIS
94              
95             ### CGI, HTML example
96             my $interface = Form::Factory->new_interface('HTML');
97             my $action = $interface->new_action('MyApp::Action::Login');
98              
99             ### Drawing the form contents
100             $action->unstash('login');
101             $action->globals->{after_login} = '/index.html';
102             $action->stash('login');
103             $action->render;
104             $action->render_control(button => {
105             name => 'submit',
106             label => 'Login',
107             });
108             $action->results->clear_all;
109            
110             ### Processing the form result
111             my $q = CGI->new;
112             $action->unstash('login');
113             $action->consume_and_clean_and_check_and_process( request => $q->Vars );
114              
115             if ($action->is_valid and $action->is_success) {
116             $action->stash('login');
117             print $q->redirect($action->globals->{after_login});
118             }
119             else {
120             print q{<p class="errors">};
121             print $action->error_messages;
122             print q{</p>};
123             }
124              
125             =head1 DESCRIPTION
126              
127             B<ALPHA API>. This code is not fully tested (if you look in the test files you will see a long list of tests planned, but no yet implemented). It is currently being employed on a non-production project. The API I<will> change. See L</TODO> for more.
128              
129             This API is designed to be a general purpose API for showing and processing forms. This has been done before. I know. However, I believe this provides some distinct advantages.
130              
131             You should check out the alternatives because this might be more complex than you really need. That said, why would you want this?
132              
133             =head2 MODULAR AND EXTENSIBLE
134              
135             This forms processor makes heavy use of L<Moose>. Nearly every class is replaceable or extensible in case it does not work the way you need it to. It is initially implemented to support HTML forms and command-line interfaces, but I would like to see it support XForms, XUL, PDF forms, GUI forms via Wx or Curses, etc.
136              
137             =head2 ENCAPSULATED ACTIONS
138              
139             The centerpiece of this API is the way an action is encapsulated in a single object. In a way, a form object is a glorified functor with a C<run> method responsible for taking the action. Wrapped around that is the ability to describe what kind of inputs are expected, how to clean up and verify the inputs, how to report errors so that they can be used, how entered values can be sent back to the orignal user, etc.
140              
141             The goal here is to create self-contained actions that specify what they are in fairly generic terms, take specific action when the input checks out, to handle exceptions in a way that is convenient in forms processing (where exceptions are often more common than not) and send back output cleanly.
142              
143             =head2 MULTIPLE IMPLEMENTATIONS
144              
145             An action presents a blueprint for the data it needs to run. A form interface takes that blueprint and builds the UI to present to the user and consume input from the user and notify the action.
146              
147             A form interface could be any kind of UI. The way the form interface and action is used will depend on the form interface implementation. The action itself should not need to care (much) about what interface it is used in.
148              
149             =head2 CONTROLS VERSUS WIDGETS
150              
151             So far, the attempt has been made to keep controls pretty generic. A control specifies the kind of inputs an action expects for some input, but the interface is responsible for rendering that control as a suitable widget and consuming data from that widget.
152              
153             =head2 FORM AND CONTROL FEATURES
154              
155             Forms and controls can be extended with common features. These features can clean up the input, check the input for errors, and provide additional processing to forms. Features can be added to an action class or even to a specific instance to modify the form on the fly.
156              
157             =head1 METHODS
158              
159             =head2 new_interface
160              
161             my $interface = Form::Factory->new_interface($name, \%options);
162              
163             This creates a L<Form::Factory::Interface> object with the given options. This is, more or less, a shortcut for:
164              
165             my $interface_class = Form::Factory->interface_class($name);
166             my $interface = $interface_class->new(\%options);
167              
168             =head2 interface_class
169              
170             my $class_name = Form::Factory->interface_class('HTML');
171              
172             Returns the interface class for the named interface. This loads the interface class from the L<Form::Factory::Interface> namespace.
173              
174             See L</CLASS LOADING>.
175              
176             =head2 control_class
177              
178             my $class_name = Form::Factory->control_class('full_text');
179              
180             Returns the control class for the named control. This loads the control class from the L<Form::Factory::Control> namespace.
181              
182             See L</CLASS LOADING>.
183              
184             =head2 feature_class
185              
186             my $class_name = Form::Factory->feature_class('functional');
187              
188             Returns the feature class for the named feature. This loads the feature class from the L<Form::Factory::Feature> namespace.
189              
190             See L</CLASS LOADING>.
191              
192             =head2 control_feature_class
193              
194             my $class_name = Form::Factory->control_feature_class('required');
195              
196             Returns the control feature class for the named control feature. This loads the control feature class from the L<Form::Factory::Feature::Control> namespace.
197              
198             See L</CLASS LOADING>.
199              
200             =head1 CLASS LOADING
201              
202             This package features a few class loading methods. These methods each load a type of class. The type of class depends on the namespace they are based upon (which is mentioned in the documentation for each class loading method).
203              
204             Each namespace is divided into two segments: the reserved namespace and the custom namespace. The reserved namespace is reserved for use by the L<Form::Factory> library itself. These will be any class directly under the namespace given.
205              
206             For example, interface classes will always be directly under L<Form::Factory::Interface>, such as L<Form::Factory::Interface::HTML> and L<Form::Factory::Interface::CLI>.
207              
208             The custom namespaces are implemented as an alias under the C<Custom> package namespace. You first define a custom package, which contains a C<register_implementation>, which returns the name of a package that actually implements that class.
209              
210             For example, you might create an interface class specific to your app. You might define a class as follows:
211              
212             package Form::Factory::Interface::Custom::MyAppHTML;
213             sub register_implementation { 'MyApp::Form::Factory::Interface::HTML' }
214              
215             package MyApp::Form::Factory::Interface::HTML;
216             use Moose;
217              
218             extends qw( Form::Factory::Interface::HTML );
219              
220             # implementation here...
221              
222             Any custom name is similar. You could then retrieve your custom name via:
223              
224             my $class = Form::Factory->interface_class('MyAppHTML');
225              
226             Though, you probably actually want:
227              
228             my $interface = Form::Factory->new_interface('MyAppHTML');
229              
230             =head1 TODO
231              
232             This is not definite, but some things I know as of right now I'm not happy with:
233              
234             =over
235              
236             =item *
237              
238             There are lots of tweaks coming to controls.
239              
240             =item *
241              
242             Features do not do very much yet, but they must do more, especially control features. I want features to be able to modify control construction, add interface-specific functionality for rendering and consuming, etc. They will be bigger and badder, but this might mean who knows what needs to change elsewhere.
243              
244             =item *
245              
246             The interfaces are kind of stupid at this point. They probably need a place to put their brains so they can some more interesting work.
247              
248             =back
249              
250             =head1 CODE REPOSITORY
251              
252             If you would like to take a look at the latest progress on this software, please see the Github repository: L<http://github.com/zostay/FormFactory>
253              
254             =head1 BUGS
255              
256             Please report any bugs you find to the Github issue tracker: L<http://github.com/zostay/FormFactory/issues>
257              
258             If you need help getting started or something (the documentation was originally thrown together over my recent vacation, so it's probably lacking and wonky), you can also contact me on Twitter (L<http://twitter.com/zostay>) or by L<email|/AUTHOR>.
259              
260             =head1 SEE ALSO
261              
262             L<Form::Factory::Interface::CLI>, L<Form::Factory::Interface::HTML>
263              
264             =head1 AUTHOR
265              
266             Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
267              
268             =head1 COPYRIGHT AND LICENSE
269              
270             This software is copyright (c) 2015 by Qubling Software LLC.
271              
272             This is free software; you can redistribute it and/or modify it under
273             the same terms as the Perl 5 programming language system itself.
274              
275             =cut