1package Rose::HTML::Form::Repeatable; 2 3use strict; 4 5use Rose::HTML::Form; 6 7use base 'Rose::HTML::Object::Repeatable'; 8 9our $VERSION = '0.616'; 10 11__PACKAGE__->default_form_class('Rose::HTML::Form'); 12 13# 14# Class methods 15# 16 17sub default_form_class { shift->default_prototype_class(@_) } 18 19# 20# Object methods 21# 22 23sub prototype_form { shift->prototype(@_) } 24sub prototype_form_spec { shift->prototype_spec(@_) } 25sub prototype_form_class { shift->prototype_class(@_) } 26sub prototype_form_clone { shift->prototype_clone(@_) } 27 28sub form_class { shift->prototype_form_class(@_) } 29 30sub is_repeatable_form { 1 } 31 32sub prepare 33{ 34 my($self) = shift; 35 my(%args) = @_; 36 37 my $fq_form_name = quotemeta $self->fq_form_name; 38 39 my $re = qr(^$fq_form_name\.(\d+)\.); 40 41 my %have_num; 42 43 foreach my $param (keys %{$self->params}) 44 { 45 if($param =~ $re) 46 { 47 my $num = $1; 48 $have_num{$num}++; 49 my $form = $self->form($num) || $self->make_form($num); 50 } 51 } 52 53 unless(%have_num) 54 { 55 if($self->default_count) 56 { 57 foreach my $num (1 .. $self->default_count) 58 { 59 $self->form($num) || $self->make_form($num); 60 $have_num{$num}++; 61 } 62 } 63 else 64 { 65 $self->delete_forms; 66 } 67 } 68 69 if(%have_num) 70 { 71 foreach my $form ($self->forms) 72 { 73 unless($have_num{$form->form_name}) 74 { 75 $self->delete_form($form->form_name); 76 } 77 } 78 } 79 80 $self->SUPER::prepare(@_) unless(delete $args{'init_only'}); 81} 82 83sub init_fields 84{ 85 my($self) = shift; 86 $self->prepare(init_only => 1); 87 $self->SUPER::init_fields(@_); 88} 89 90sub make_form 91{ 92 my($self, $num) = @_; 93 94 Carp::croak "Missing form nubmer argument" unless(@_ > 1); 95 Carp::croak "Form number argument must be greater than 0" unless($num > 0); 96 97 my $form = $self->prototype_form_clone; 98 99 $form->rank($num); 100 101 $self->form_rank_counter($num + 1) if($num >= $self->form_rank_counter); 102 103 $form->prepare; 104 105 $self->add_form($num => $form); 106 107 return $form; 108} 109 110sub make_next_form 111{ 112 my ($self) = shift; 113 $self->increment_form_rank_counter; # XXX: Remove when form_rank_counter is removed 114 return $self->make_form($self->next_form_rank); 115} 116 117sub objects_from_form 118{ 119 my($self) = shift; 120 121 my $method = 'object_from_form'; 122 123 if(@_ > 1) 124 { 125 my %args = @_; 126 $method = $args{'method'} if($args{'method'}); 127 } 128 129 my @objects = map { $_->$method(@_) } $self->forms; 130 131 return wantarray ? @objects : \@objects; 132} 133 134sub init_with_objects 135{ 136 my($self) = shift; 137 138 my $method = 'init_with_object'; 139 140 my $objects; 141 142 if(@_ > 1) 143 { 144 my %args = @_; 145 $method = $args{'method'} if($args{'method'}); 146 $objects = $args{'objects'}; 147 } 148 149 unless($objects) 150 { 151 $objects = \@_; 152 } 153 154 foreach my $form ($self->forms) 155 { 156 $form->$method(shift(@$objects)); 157 } 158} 159 1601; 161 162__END__ 163 164=head1 NAME 165 166Rose::HTML::Form::Repeatable - Repeatable sub-form automation. 167 168=head1 SYNOPSIS 169 170 package Person; 171 172 use base 'Rose::Object'; 173 174 use Rose::Object::MakeMethods::Generic 175 ( 176 scalar => [ 'name', 'age' ], 177 array => 'emails', 178 ); 179 180 ... 181 182 package Email; 183 184 use base 'Rose::Object'; 185 186 use Rose::Object::MakeMethods::Generic 187 ( 188 scalar => 189 [ 190 'address', 191 'type' => { check_in => [ 'home', 'work' ] }, 192 ], 193 ); 194 195 ... 196 197 package EmailForm; 198 199 use base 'Rose::HTML::Form'; 200 201 sub build_form 202 { 203 my($self) = shift; 204 205 $self->add_fields 206 ( 207 address => { type => 'email', size => 50, required => 1 }, 208 type => { type => 'pop-up menu', choices => [ 'home', 'work' ], 209 required => 1, default => 'home' }, 210 save_button => { type => 'submit', value => 'Save Email' }, 211 ); 212 } 213 214 sub email_from_form { shift->object_from_form('Email') } 215 sub init_with_email { shift->init_with_object(@_) } 216 217 ... 218 219 package PersonEmailsForm; 220 221 use base 'Rose::HTML::Form'; 222 223 sub build_form 224 { 225 my($self) = shift; 226 227 $self->add_fields 228 ( 229 name => { type => 'text', size => 25, required => 1 }, 230 age => { type => 'integer', min => 0 }, 231 save_button => { type => 'submit', value => 'Save Person' }, 232 ); 233 234 ## 235 ## The important part happens here: add a repeatable form 236 ## 237 238 # A person can have zero or more emails 239 $self->add_repeatable_form(emails => EmailForm->new); 240 241 # Alternate ways to add the same repeatable form: 242 # 243 # Name/hashref pair: 244 # $self->add_repeatable_form(emails => { form_class => 'EmailForm' }); 245 # 246 # Using the generic add_form() method: 247 # $self->add_form 248 # ( 249 # emails => 250 # { 251 # form_class => 'EmailForm', 252 # default_count => 0, 253 # repeatable => 1, 254 # } 255 # ); 256 # 257 # See the documentation for Rose::HTML::Form's add_forms() and 258 # add_repeatable_forms() methods for more information. 259 } 260 261 sub init_with_person 262 { 263 my($self, $person) = @_; 264 265 $self->init_with_object($person); 266 267 # Delete any existing email forms and create 268 # the appropriate number for this $person 269 270 my $email_form = $self->form('emails'); 271 $email_form->delete_forms; 272 273 my $i = 1; 274 275 foreach my $email ($person->emails) 276 { 277 $email_form->make_form($i++)->init_with_email($email); 278 } 279 } 280 281 sub person_from_form 282 { 283 my($self) = shift; 284 285 my $person = $self->object_from_form(class => 'Person'); 286 287 my @emails; 288 289 foreach my $form ($self->form('emails')->forms) 290 { 291 push(@emails, $form->email_from_form); 292 } 293 294 $person->emails(@emails); 295 296 return $person; 297 } 298 299=head1 DESCRIPTION 300 301L<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. 302 303L<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. 304 305The 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.). 306 307Each 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>. 308 309A 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. 310 311Repeated 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. 312 313On 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. 314 315This 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. 316 317=head1 CONSTRUCTOR 318 319=over 4 320 321=item B<new PARAMS> 322 323Constructs 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. 324 325=back 326 327=head1 CLASS METHODS 328 329=over 4 330 331=item B<default_form_class [CLASS]> 332 333Get 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>. 334 335=back 336 337=head1 OBJECT METHODS 338 339=over 4 340 341=item B<default_count [INT]> 342 343Get 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. 344 345=item B<empty_is_ok [BOOL]> 346 347Get 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. 348 349=item B<init_fields> 350 351In 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. 352 353=item B<init_with_objects [ OBJECTS | PARAMS ]> 354 355Given 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.) 356 357Valid parameters are: 358 359=over 4 360 361=item B<objects ARRAYREF> 362 363A reference to an array of objects with which to initialize the form(s). This parameter is required if PARAMS are passed. 364 365=item B<method NAME> 366 367The name of the method to call on each sub-form. The default value is C<init_with_object>. 368 369=back 370 371=item B<make_form INT> 372 373Given an integer argument greater than zero, create, add to the form, and return a new numbered L<prototype form clone|/prototype_form_clone> object. 374 375=item B<make_next_form> 376 377Create, 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. 378 379=item B<objects_from_form [PARAMS]> 380 381Return 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: 382 383=over 4 384 385=item B<method NAME> 386 387The name of the method to call on each sub-form. The default value is C<object_from_form>. 388 389=back 390 391=item B<prepare> 392 393This 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. 394 395=item B<prototype_form [FORM]> 396 397Get or set the L<Rose::HTML::Form>-derived object used as the prototype for each repeated form. 398 399=item B<prototype_form_class [CLASS]> 400 401Get 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. 402 403=item B<prototype_form_spec [SPEC]> 404 405Get 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>. 406 407=item B<prototype_form_clone> 408 409Returns 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. 410 411=back 412 413=head1 AUTHOR 414 415John C. Siracusa (siracusa@gmail.com) 416 417=head1 LICENSE 418 419Copyright (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. 420