NAME

Rose::HTML::Form::Repeatable - Repeatable sub-form automation.

SYNOPSIS

package Person;

use base 'Rose::Object';

use Rose::Object::MakeMethods::Generic
(
  scalar => [ 'name', 'age' ],
  array  => 'emails',
);

...  

package Email;

use base 'Rose::Object';

use Rose::Object::MakeMethods::Generic
(
  scalar => 
  [
    'address',
    'type' => { check_in => [ 'home', 'work' ] },
  ],
);

...

package EmailForm;

use base 'Rose::HTML::Form';

sub build_form 
{
  my($self) = shift;

  $self->add_fields
  (
    address     => { type => 'email', size => 50, required => 1 },
    type        => { type => 'pop-up menu', choices => [ 'home', 'work' ],
                     required => 1, default => 'home' },
    save_button => { type => 'submit', value => 'Save Email' },
  );
}

sub email_from_form { shift->object_from_form('Email') }
sub init_with_email { shift->init_with_object(@_) }

...

package PersonEmailsForm;

use base 'Rose::HTML::Form';

sub build_form 
{
  my($self) = shift;

  $self->add_fields
  (
    name        => { type => 'text',  size => 25, required => 1 },
    age         => { type => 'integer', min => 0 },
    save_button => { type => 'submit', value => 'Save Person' },
  );

  ##
  ## The important part happens here: add a repeatable form
  ##

  # A person can have zero or more emails
  $self->add_repeatable_form(emails => EmailForm->new);

  # Alternate ways to add the same repeatable form:
  #
  # Name/hashref pair:
  # $self->add_repeatable_form(emails => { form_class => 'EmailForm' });
  #
  # Using the generic add_form() method:
  # $self->add_form
  # (
  #   emails => 
  #   {
  #     form_class    => 'EmailForm',
  #     default_count => 0,
  #     repeatable    => 1,
  #   }
  # );
  #
  # See the documentation for Rose::HTML::Form's add_forms() and 
  # add_repeatable_forms() methods for more information.
}

sub init_with_person
{
  my($self, $person) = @_;

  $self->init_with_object($person);

  # Delete any existing email forms and create 
  # the appropriate number for this $person

  my $email_form = $self->form('emails');
  $email_form->delete_forms;

  my $i = 1;

  foreach my $email ($person->emails)
  {
    $email_form->make_form($i++)->init_with_email($email);
  }
}

sub person_from_form
{
  my($self) = shift;

  my $person = $self->object_from_form(class => 'Person');

  my @emails;

  foreach my $form ($self->form('emails')->forms)
  {
    push(@emails, $form->email_from_form);
  }

  $person->emails(@emails);

  return $person;
}

DESCRIPTION

Rose::HTML::Form::Repeatable provides a convenient way to include zero or more copies of a nested form. See the nested forms section of the Rose::HTML::Form documentation for some essential background information.

Rose::HTML::Form::Repeatable works like a wrapper for an additional level of sub-forms. The 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.

The synopsis above contains a full example. In it, the PersonEmailsForm contains zero or more EmailForm sub-forms under the name emails. The emails name identifies the Rose::HTML::Form::Repeatable object, while emails.N identifies each EmailForm object contained within it (e.g., emails.1, emails.2, etc.).

Each repeated form must be of the same class. A repeated form can be generated by cloning a prototype form or by instantiating a specified prototype form class.

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 params attribute for the parent form). If there are no params, then the default_count determines the number of repeated forms.

Repeated forms are created in response to the init_fields or prepare methods being called. In the synopsis example, the 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 init_fields and/or prepare methods have been called already. On the other hand, the init_with_person method must configure the repeated email forms based on the number of email addresses contained in the Person object that it was passed.

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 make_form method is designed to do exactly that, returning a correctly namespaced 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.

This class inherits from and follows the conventions of Rose::HTML::Form. Inherited methods that are not overridden will not be documented a second time here. See the Rose::HTML::Form documentation for more information.

CONSTRUCTOR

new PARAMS

Constructs a new Rose::HTML::Form::Repeatable object based on PARAMS, where PARAMS are name/value pairs. Any object method is a valid parameter name.

CLASS METHODS

default_form_class [CLASS]

Get or set the name of the default Rose::HTML::Form-derived class of the repeated form. The default value is Rose::HTML::Form.

OBJECT METHODS

default_count [INT]

Get or set the default number of repeated forms to create in the absence of any parameters. The default value is zero.

empty_is_ok [BOOL]

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.

init_fields

In addition to doing all the usual things that the base class implementation does, this method creates or deletes repeated sub-forms as necessary to make sure they match the query parameters, if present, or the default_count if there are no parameters that apply to any of the sub-forms.

init_with_objects [ OBJECTS | PARAMS ]

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 forms method.)

Valid parameters are:

objects ARRAYREF

A reference to an array of objects with which to initialize the form(s). This parameter is required if PARAMS are passed.

method NAME

The name of the method to call on each sub-form. The default value is init_with_object.

make_form INT

Given an integer argument greater than zero, create, add to the form, and return a new numbered prototype form clone object.

make_next_form

Create, add to the form, and return a new numbered prototype form clone object whose rank is one greater than the the highest-ranking existing sub-form.

objects_from_form [PARAMS]

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:

method NAME

The name of the method to call on each sub-form. The default value is object_from_form.

prepare

This method does the same thing as the init_fields method, but calls through to the base class prepare method rather than the base class init_fields method.

prototype_form [FORM]

Get or set the Rose::HTML::Form-derived object used as the prototype for each repeated form.

prototype_form_class [CLASS]

Get or set the name of the Rose::HTML::Form-derived class used by the prototype_form_clone method to create each repeated sub-form. The default value is determined by the default_form_class class method.

prototype_form_spec [SPEC]

Get or set the specification for the 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 prototype_form, the SPEC is dereferenced and passed to the new() method called on the prototype_form_class in order to create each prototype_form_clone.

prototype_form_clone

Returns a clone of the prototype_form, if one was set. Otherwise, creates and returns a new prototype_form_class object, passing the prototype_form_spec to the constructor.

AUTHOR

John C. Siracusa (siracusa@gmail.com)

LICENSE

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.