File Coverage

blib/lib/Rose/HTML/Form/Repeatable.pm
Criterion Covered Total %
statement 52 72 72.2
branch 18 32 56.2
condition 2 3 66.6
subroutine 13 16 81.2
pod 12 13 92.3
total 97 136 71.3


line stmt bran cond sub pod time code
1              
2             use strict;
3 2     2   11  
  2         4  
  2         66  
4             use Rose::HTML::Form;
5 2     2   12  
  2         4  
  2         23  
6             use base 'Rose::HTML::Object::Repeatable';
7 2     2   9  
  2         3  
  2         987  
8             our $VERSION = '0.616';
9              
10             __PACKAGE__->default_form_class('Rose::HTML::Form');
11              
12             #
13             # Class methods
14             #
15              
16              
17 2     2 1 14 #
18             # Object methods
19             #
20              
21              
22              
23 9     9 1 107  
24 3     3 1 45 {
25 4     4 1 47 my($self) = shift;
26 32     32 1 118 my(%args) = @_;
27              
28 0     0 1 0 my $fq_form_name = quotemeta $self->fq_form_name;
29              
30 4     4 0 17 my $re = qr(^$fq_form_name\.(\d+)\.);
31              
32             my %have_num;
33              
34 33     33 1 61 foreach my $param (keys %{$self->params})
35 33         81 {
36             if($param =~ $re)
37 33         90 {
38             my $num = $1;
39 33         380 $have_num{$num}++;
40             my $form = $self->form($num) || $self->make_form($num);
41 33         60 }
42             }
43 33         48  
  33         91  
44             unless(%have_num)
45 134 100       599 {
46             if($self->default_count)
47 75         151 {
48 75         154 foreach my $num (1 .. $self->default_count)
49 75   66     186 {
50             $self->form($num) || $self->make_form($num);
51             $have_num{$num}++;
52             }
53 33 100       102 }
54             else
55 9 100       35 {
56             $self->delete_forms;
57 8         56 }
58             }
59 14 100       75  
60 14         50 if(%have_num)
61             {
62             foreach my $form ($self->forms)
63             {
64             unless($have_num{$form->form_name})
65 1         17 {
66             $self->delete_form($form->form_name);
67             }
68             }
69 33 100       83 }
70              
71 32         82 $self->SUPER::prepare(@_) unless(delete $args{'init_only'});
72             }
73 73 100       149  
74             {
75 11         33 my($self) = shift;
76             $self->prepare(init_only => 1);
77             $self->SUPER::init_fields(@_);
78             }
79              
80 33 100       190 {
81             my($self, $num) = @_;
82              
83             Carp::croak "Missing form nubmer argument" unless(@_ > 1);
84             Carp::croak "Form number argument must be greater than 0" unless($num > 0);
85 28     28 1 51  
86 28         83 my $form = $self->prototype_form_clone;
87 28         116  
88             $form->rank($num);
89              
90             $self->form_rank_counter($num + 1) if($num >= $self->form_rank_counter);
91              
92 31     31 1 84 $form->prepare;
93              
94 31 50       65 $self->add_form($num => $form);
95 31 50       66  
96             return $form;
97 31         80 }
98              
99 31         554 {
100             my ($self) = shift;
101 31 100       120 $self->increment_form_rank_counter; # XXX: Remove when form_rank_counter is removed
102             return $self->make_form($self->next_form_rank);
103 31         283 }
104              
105 31         270 {
106             my($self) = shift;
107 31         142  
108             my $method = 'object_from_form';
109              
110             if(@_ > 1)
111             {
112 3     3 1 7 my %args = @_;
113 3         16 $method = $args{'method'} if($args{'method'});
114 3         9 }
115              
116             my @objects = map { $_->$method(@_) } $self->forms;
117              
118             return wantarray ? @objects : \@objects;
119 0     0 1   }
120              
121 0           {
122             my($self) = shift;
123 0 0          
124             my $method = 'init_with_object';
125 0            
126 0 0         my $objects;
127              
128             if(@_ > 1)
129 0           {
  0            
130             my %args = @_;
131 0 0         $method = $args{'method'} if($args{'method'});
132             $objects = $args{'objects'};
133             }
134              
135             unless($objects)
136 0     0 1   {
137             $objects = \@_;
138 0           }
139              
140 0           foreach my $form ($self->forms)
141             {
142 0 0         $form->$method(shift(@$objects));
143             }
144 0           }
145 0 0          
146 0           1;
147              
148              
149 0 0         =head1 NAME
150              
151 0           Rose::HTML::Form::Repeatable - Repeatable sub-form automation.
152              
153             =head1 SYNOPSIS
154 0            
155             package Person;
156 0            
157             use base 'Rose::Object';
158              
159             use Rose::Object::MakeMethods::Generic
160             (
161             scalar => [ 'name', 'age' ],
162             array => 'emails',
163             );
164              
165             ...
166              
167             package Email;
168              
169             use base 'Rose::Object';
170              
171             use Rose::Object::MakeMethods::Generic
172             (
173             scalar =>
174             [
175             'address',
176             'type' => { check_in => [ 'home', 'work' ] },
177             ],
178             );
179              
180             ...
181              
182             package EmailForm;
183              
184             use base 'Rose::HTML::Form';
185              
186             sub build_form
187             {
188             my($self) = shift;
189              
190             $self->add_fields
191             (
192             address => { type => 'email', size => 50, required => 1 },
193             type => { type => 'pop-up menu', choices => [ 'home', 'work' ],
194             required => 1, default => 'home' },
195             save_button => { type => 'submit', value => 'Save Email' },
196             );
197             }
198              
199             sub email_from_form { shift->object_from_form('Email') }
200             sub init_with_email { shift->init_with_object(@_) }
201              
202             ...
203              
204             package PersonEmailsForm;
205              
206             use base 'Rose::HTML::Form';
207              
208             sub build_form
209             {
210             my($self) = shift;
211              
212             $self->add_fields
213             (
214             name => { type => 'text', size => 25, required => 1 },
215             age => { type => 'integer', min => 0 },
216             save_button => { type => 'submit', value => 'Save Person' },
217             );
218              
219             ##
220             ## The important part happens here: add a repeatable form
221             ##
222              
223             # A person can have zero or more emails
224             $self->add_repeatable_form(emails => EmailForm->new);
225              
226             # Alternate ways to add the same repeatable form:
227             #
228             # Name/hashref pair:
229             # $self->add_repeatable_form(emails => { form_class => 'EmailForm' });
230             #
231             # Using the generic add_form() method:
232             # $self->add_form
233             # (
234             # emails =>
235             # {
236             # form_class => 'EmailForm',
237             # default_count => 0,
238             # repeatable => 1,
239             # }
240             # );
241             #
242             # See the documentation for Rose::HTML::Form's add_forms() and
243             # add_repeatable_forms() methods for more information.
244             }
245              
246             sub init_with_person
247             {
248             my($self, $person) = @_;
249              
250             $self->init_with_object($person);
251              
252             # Delete any existing email forms and create
253             # the appropriate number for this $person
254              
255             my $email_form = $self->form('emails');
256             $email_form->delete_forms;
257              
258             my $i = 1;
259              
260             foreach my $email ($person->emails)
261             {
262             $email_form->make_form($i++)->init_with_email($email);
263             }
264             }
265              
266             sub person_from_form
267             {
268             my($self) = shift;
269              
270             my $person = $self->object_from_form(class => 'Person');
271              
272             my @emails;
273              
274             foreach my $form ($self->form('emails')->forms)
275             {
276             push(@emails, $form->email_from_form);
277             }
278              
279             $person->emails(@emails);
280              
281             return $person;
282             }
283              
284             =head1 DESCRIPTION
285              
286             L<Rose::HTML::Form::Repeatable> provides a convenient way to include zero or more copies of a nested form. See the L<nested forms|Rose::HTML::Form/"NESTED FORMS"> section of the L<Rose::HTML::Form> documentation for some essential background information.
287              
288             L<Rose::HTML::Form::Repeatable> works like a wrapper for an additional level of sub-forms. The L<Rose::HTML::Form::Repeatable> object itself has no fields. Instead, it has a list of zero or more sub-forms, each of which is named with a positive integer greater than zero.
289              
290             The L<synopsis|/SYNOPSIS> above contains a full example. In it, the C<PersonEmailsForm> contains zero or more L<EmailForm> sub-forms under the name C<emails>. The C<emails> name identifies the L<Rose::HTML::Form::Repeatable> object, while C<emails.N> identifies each L<EmailForm> object contained within it (e.g., C<emails.1>, C<emails.2>, etc.).
291              
292             Each repeated form must be of the same class. A repeated form can be generated by cloning a L<prototype form|/prototype_form> or by instantiating a specified L<prototype form class|/prototype_form_class>.
293              
294             A repeatable form decides how many of each repeated sub-form it should contain based on the contents of the query parameters (contained in the L<params|Rose::HTML::Form/params> attribute for the parent form). If there are no L<params|Rose::HTML::Form/params>, then the L<default_count|/default_count> determines the number of repeated forms.
295              
296             Repeated forms are created in response to the L<init_fields|/init_fields> or L<prepare|/prepare> methods being called. In the L<synopsis|/SYNOPSIS> example, the C<person_from_form> method does not need to create, delete, or otherwise set up the repeated email sub-forms because it can sensibly assume that the L<init_fields|/init_fields> and/or L<prepare|/prepare> methods have been called already. On the other hand, the C<init_with_person> method must configure the repeated email forms based on the number of email addresses contained in the C<Person> object that it was passed.
297              
298             On the client side, the usual way to handle repeated sub-forms is to make an AJAX request for new content to add to an existing form. The L<make_form|/make_form> method is designed to do exactly that, returning a correctly namespaced L<Rose::HTML::Form>-derived object ready to have its fields serialized (usually through a template) into HTML which is then inserted into the existing form on a web page.
299              
300             This class inherits from and follows the conventions of L<Rose::HTML::Form>. Inherited methods that are not overridden will not be documented a second time here. See the L<Rose::HTML::Form> documentation for more information.
301              
302             =head1 CONSTRUCTOR
303              
304             =over 4
305              
306             =item B<new PARAMS>
307              
308             Constructs a new L<Rose::HTML::Form::Repeatable> object based on PARAMS, where PARAMS are name/value pairs. Any object method is a valid parameter name.
309              
310             =back
311              
312             =head1 CLASS METHODS
313              
314             =over 4
315              
316             =item B<default_form_class [CLASS]>
317              
318             Get or set the name of the default L<Rose::HTML::Form>-derived class of the repeated form. The default value is L<Rose::HTML::Form>.
319              
320             =back
321              
322             =head1 OBJECT METHODS
323              
324             =over 4
325              
326             =item B<default_count [INT]>
327              
328             Get or set the default number of repeated forms to create in the absence of any L<parameters|Rose::HTML::Form/params>. The default value is zero.
329              
330             =item B<empty_is_ok [BOOL]>
331              
332             Get or set a boolean value that indicates whether or not it's OK for a repeated form to be empty. (That is, validation should not fail if the entire sub-form is empty, even if the sub-form has required fields.) Defaults to false.
333              
334             =item B<init_fields>
335              
336             In addition to doing all the usual things that the L<base class implementation|Rose::HTML::Form/init_fields> does, this method creates or deletes repeated sub-forms as necessary to make sure they match the query L<parameters|Rose::HTML::Form/params>, if present, or the L<default_count|/default_count> if there are no L<parameters|Rose::HTML::Form/params> that apply to any of the sub-forms.
337              
338             =item B<init_with_objects [ OBJECTS | PARAMS ]>
339              
340             Given a list of OBJECTS or name/value pairs PARAMS, initialize each sub-form, taking one object from the list and passing it to a method called on each sub-form. The first object is passed to the first form, the second object to the second form, and so on. (Form order is determined by the the order forms are returned from the L<forms|Rose::HTML::Form/forms> method.)
341              
342             Valid parameters are:
343              
344             =over 4
345              
346             =item B<objects ARRAYREF>
347              
348             A reference to an array of objects with which to initialize the form(s). This parameter is required if PARAMS are passed.
349              
350             =item B<method NAME>
351              
352             The name of the method to call on each sub-form. The default value is C<init_with_object>.
353              
354             =back
355              
356             =item B<make_form INT>
357              
358             Given an integer argument greater than zero, create, add to the form, and return a new numbered L<prototype form clone|/prototype_form_clone> object.
359              
360             =item B<make_next_form>
361              
362             Create, add to the form, and return a new numbered L<prototype form clone|/prototype_form_clone> object whose L<rank|Rose::HTML::Form/rank> is one greater than the the highest-ranking existing sub-form.
363              
364             =item B<objects_from_form [PARAMS]>
365              
366             Return a list (in list context) or reference to an array (in scalar context) of objects corresponding to the list of repeated sub-forms. This is done by calling a method on each sub-form and collecting the return values. Name/value parameters may be passed. Valid parameters are:
367              
368             =over 4
369              
370             =item B<method NAME>
371              
372             The name of the method to call on each sub-form. The default value is C<object_from_form>.
373              
374             =back
375              
376             =item B<prepare>
377              
378             This method does the same thing as the L<init_fields|/init_fields> method, but calls through to the L<base class prepare|Rose::HTML::Form/prepare> method rather than the L<base class init_fields|Rose::HTML::Form/init_fields> method.
379              
380             =item B<prototype_form [FORM]>
381              
382             Get or set the L<Rose::HTML::Form>-derived object used as the prototype for each repeated form.
383              
384             =item B<prototype_form_class [CLASS]>
385              
386             Get or set the name of the L<Rose::HTML::Form>-derived class used by the L<prototype_form_clone|/prototype_form_clone> method to create each repeated sub-form. The default value is determined by the L<default_form_class|/default_form_class> class method.
387              
388             =item B<prototype_form_spec [SPEC]>
389              
390             Get or set the specification for the L<Rose::HTML::Form>-derived object used as the prototype for each repeated form. The SPEC can be a reference to an array, a reference to a hash, or a list that will be coerced into a reference to an array. In the absence of a L<prototype_form|/prototype_form>, the SPEC is dereferenced and passed to the C<new()> method called on the L<prototype_form_class|/prototype_form_class> in order to create each L<prototype_form_clone|/prototype_form_clone>.
391              
392             =item B<prototype_form_clone>
393              
394             Returns a clone of the L<prototype_form|/prototype_form>, if one was set. Otherwise, creates and returns a new L<prototype_form_class|/prototype_form_class> object, passing the L<prototype_form_spec|/prototype_form_spec> to the constructor.
395              
396             =back
397              
398             =head1 AUTHOR
399              
400             John C. Siracusa (siracusa@gmail.com)
401              
402             =head1 LICENSE
403              
404             Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.