Form basics

Learn the Formo ropes.

To create a form, use Formo::form():

$form = Formo::form();

You can also name your form by giving it an alias:

$form = Formo::form(['alias' => 'myform']);

To add a field to the form, use Formo::add(). Here’s a practical example:

$Form = Formo::form()
    // The field added here is $form->first
    ->add(‘first’)
    ->add(‘last’, ‘input’)
    ->add(‘email’, ‘input|email’)
    ->add(‘notes’, ‘textarea’)
    ->add(‘save’, ‘input|submit’, ‘Save’);

This will create the following:

  1. Form object containing:
    1. input named ‘first’
    2. input named ‘last’
    3. input named ‘email’ with ‘type=“email”’ and validation rule that entered text is a valid email
    4. textare named ‘notes’
    5. input named ‘save’ with ‘type=“submit”’ and value ‘Save’

Every field has an alias that the field is accessible by. Also, by default, the field name is the same as its alias (if no name is specified).

The second parameter for Formo::add() is the field driver name. Drivers tell Formo how to process each field since different field types behave differently in HTML (think about ways a select field differs from a textarea, for instance).

Thus, when using Formo::add(), you specify:

  1. The field alias
  2. Driver name
  3. Default value
  4. Array of other attributes and options

Field creation with an array

Though Formo::add() supports separate parameters that define the new field added to its parent, you can also pass an array as a field construct. Here's an example:

$form->add([
    'alias' => 'first_name',
    'driver' => 'input',
    'val' => NULL,
    'rules' => [
        ['not_empty'],
    ],
    'filters' => [
        function(str) {
            return trim($str);
        }
    ],
]);

If array keys are not specified, Formo expects your array to be an exact representation of params for Formo::add(). Thus, the following three examples are equivelent.

$form->add('email', 'input|email', NULL, ['attr' => ['placeholder' => 'john@doe.com']]);

$form->add([
    'email',
    'input|email',
    NULL,
    ['attr' => [
        'placeholder' => 'john@doe.com',
    ]]
]);

$form->add([
    'alias' => 'email',
    'driver' => 'input|email',
    'val' => NULL,
    'attr' => [
        'placeholder' => 'john@doe.com',
    ]
]);

For more detailed instructions on validation, see the full validation documentation.

Loading posted

The Formo::load() method tells the form that whatever array is passed as a parameter is what was posted. If no parameter is specified, Formo will use Request::$current->post() as the posted array.

Validation

To validate your form, use Formo::validate(). This returns TRUE or FALSE whether the form passed validation rules or not.

Here’s a practical validation example:

if ($form->load()->validate())
{

}

What this is saying is, if the form loaded with Request::$current->post() passed validation rules attached to the form.

To render your form using the built-in html templates, use Formo::render():

echo $formo->render();

Note that a Formo object's __toString() method also runs $form->render().

echo $form;

There are three variables Formo uses to decide which view file to use as a field's template:

  • template_dir
  • template
  • template_ext

The default template_dir is defined in the Formo config.php file.

The template variable is defined per-driver (and can also be customized per field or form). When the field renders, Formo finds a view file named $template_dir/$template.

The template_ext is defined in the Formo config.php file, and can optionally add an extension to the template filenames. This is in addition to the extension Kohana adds, defined by the EXT constant (typically php). By default this is set to FALSE, so Formo will search for template filenames like form_template.php. If set to html, for example, it will search for filenames like form_template.html.php.

Render a field

You can also render an individual field.

To render a full individual field including label:

echo $form->$field->render();

Or you can render just the input part of the Formo field using Formo::input():

echo $form->$field->input();

Setting stuff

In a Formo object, you cannot directly access object properties except for fields. So, if you have a form object with a first_name field and want to set its label (which is the $form->_label protected property), you have to use the Formo::set() method.

It’s simple. Do $form->set($property_name, $value):

$form->first_name->set(‘label’, ‘First Name’);

You can also set an array of stuff. When you pass an array of stuff, you need to specify the alias for each group of values you are setting.

$form->set([
    ‘first_name’ => [
        ‘label’ => ‘First Name’,
    ],
    ‘last_name’ => [
        ‘label’ => ‘Last Name’,
    ],
]);

Setting extra variables

You can set any variable to any form object whenever you need. Let’s say you have a special variable named $foo you need to pass into your form. No problem:

$form->first_name->set(‘foo’, $bar);

Setting the value

You can set a field's value two ways

$form->val($new_val);
$form->set('val', $new_val);

$form->add([
    ‘alias’ => ‘foo’,
    ‘val’ => $value,
]);

Setting using array path

One awesome thing you can do is directly set a property inside an array using Kohana’s Arr::path method.

In this example, I am setting a custom config parameter for label_message_file for a particular form.

$form = Formo::form()
    ->set(‘config.label_message_file’, $new_file);

That sets $form->_config['label_message_file'] = $new_file.

Setting stuff for a bunch of fields at once.

Use Formo::set_fields() for setting variables for a bunch of fields at once.

Note that Formo::set_fields() does not throw an exception if a field doesn't exist in the form object. It is intended to be used in general setup methods to define a bunch of defaults, like in Model::formo() for instance.

$form->set_fields([
    'email' => [
        'driver' => 'input|email',
        'rules' => [
            ['unique_email']
        ],
        'attr.placeholder' => 'john@doe.com',
    ],
    'foo' => [
        'driver' => 'select',
        'rules' [
            ['bar', ':model']
        ],
        'attr.placeholder' => 'foobar',
        'attr.class' => 'myclass',
    ].
]);

By default, Formo::set_fields() does not recursively look for fields to set options across. You can search recursively by passing FALSE as the second parameter:

$form->set_fields([
    'email' => [
        'driver' => 'input|email',
        'rules' => [
            ['unique_email']
        ],
        'attr.placeholder' => 'john@doe.com',
    ],
    'foo' => [
        'driver' => 'select',
        'rules' [
            ['bar', ':model']
        ],
        'attr.placeholder' => 'foobar',
        'attr.class' => 'myclass',
    ].
], FALSE);

You can set for all fields using the * syntax:

$form->set_fields([
    '*' => [
        'attr.class' => 'form-control'
    ]
]);

Getting stuff

The only properties you can access directly in a Formo object are fields. Thus, if you have a field named first_name and want to retrieve the Formo::$_label variable, you cannot use $form->first_name->_label. Instead you need to use Formo::get().

Formo::get($property, [$default = null]) takes two parameters:

  1. The property you are retrieving
  2. The default value if the property isn’t found.

Retrieving custom variables

You retrieve custom variables the same as any others. Let’s say you passed a $foo variable into the first_name field:

$form->first_name->set(‘foo’, $bar);

$foo_value = $form->first_name->get(‘foo’);

That’s it.

Getting the value

You can set a field's value two ways

$value = $form->val();
$value = $form->get('val');

Getting using array path

Formo lets you get a property using Kohana’s Arr::path() method.

Here’s an example of retrieving $form->_config[‘label_message_file’] (which is a protected property) in one fell swoop:

$file = $form->get(‘config.label_message_file’);

Sometimes you don’t want to just set a variable value, but rather you need to merge an array of values with a new array of values.

For instance, a field’s filters are an array of filters inside a field.

Let’s say I created a form from a model, but I want to add a new filter to the bunch.

It’s dirt-simple using Formo::merge().

$user = ORM::factory(‘User’, $id);
$other_email_filters = [
    ‘remove_fugly_chars’,
];

$form = $user->get_form();

$form->email
    ->merge(‘filters’, $other_email_filters);

You can also pass an array of stuff into Formo::merge_fields() and merge all sorts of stuff at once.

$form->merge_fields([
    ‘:self’ => [
        ‘config’ => $new_config_values,
    ],
    ‘email’ => [
        ‘filters’ => $new_filters,
        ‘rules’ => $new_rules,
        ‘callbacks’ => $new_callbacks,
    ],
    ‘first_name’ => [
        ‘rules’ => $new_rules,
    ],
    ‘hobbies’ => [
        ‘opts’ => $new_opts,
    ],
]);

Use Formo::order() to reorder field and subform elements. The arguments:

  • Field alias
  • New order (int, 'before', or 'after')
  • Relative field (default NULL)

If you know the numeric order the field should be in, leave the relative field param empty and specify the direct order of the field (starting with 0).

$form
    ->add('confirm_password')
    ->add('password')
    ->add('last_name')
    ->add('first_name');

$form->order([
    'first_name' => [0],
    'password' => ['after', 'first_name'],
    'last_name' => ['before', 'password'],
]);

$form->order('confirm_password', 'after', 'password');

// The order of fields afterwords
// 'first_name', 'last_name', 'password', 'confirm_password'

A subform is a form within a form.

Formo supports deep form objects and allows as many forms to exist within other forms as necessary.

Only the parent-most form will have the form driver. Others added to it will be converted to group by default.

How to add a subform

Since a subform is just a form within a form, it's creation is like a form. Then it's added to a parent form

$address = Formo::form(['alias' => 'address'])
    ->add('street')
    ->add('city')
    ->add('zip');

$user_form = Formo::form()
    ->add('first name')
    ->add('last name')
    ->add($address);

In the example above, the address form is added to $user_form after last_name. It is given the alias address and is using the driver group to handle it.

If you wanted to access street, to set its value for instance, you could do this:

$user_form->address->street->val();

Since PHP passes objects by reference, you could access the same value from the orinal subform object too:

$address->street->val();

Subforms and validation

Subforms can still validate separately from the rest of the form.

$form->add($subform);

if ($form->load()->validate())
{

}
elseif ($form->subform->validate())
{

}

Create a subform on the fly from existing fields

You may want to namespace a part of your form or turn a few fields into a subform on the fly. There are many reasons you may want to do this, but one is to allow the "group" or other specific driver for formatting reasons.

Doing this always appends your subform to the bottom of its parent.

To do this, use Formo::subform(). The arguments are:

  • Group alias
  • Fields to add to group (array)
  • Order for new subform to be placed (array, default NULL)
  • Subform's driver (default group)
$form = Formo::form()
    ->add('username')
    ->add('email')
    ->add('password')
    ->add('street')
    ->add('city')
    ->add('state');

$form->subform('address', ['street', 'city', 'state'], ['before', 'username']);

You can also specify a numeric order for the subform

$form->subform('address', ['street', 'city', 'state'], [1]);

You can reset any form or field's values using the Formo::reset() method.

This recursively resets any field values within the field also.

// Reset entire form's values recursively
$form->reset();

// Reset individual field
$form->first_name->reset();

Formo fields use static drivers to handle all field-specific functionality. Here is a list of the default drivers that ship with Formo.

Form driver

Forms begin and end with a <form> tag. There can only be one Formo object with a form driver inside a Formo object. Thus subforms are converted by default to the group driver when added to a form as a subform.

A field with the form driver is returned from Formo::form().

Input driver

The input driver is the default Formo field driver. If you add a field without a driver specified, it will be a field with the input driver.

Since input is used for so many types of field in HTML, Formo allows a short syntax for specifying an input field with a special type tag. Don't use these with types checkbox or radio as they function much different from regular input type=text fields.

Group driver

Used for subforms. The group acts like the form driver without the opening and closing form tags.

Button driver

Used for <button>

Don't forget to set the field's html that goes inside the </button><button> tags.

Checkbox driver

For single <input type="checkbox">

Checkboxes driver

For multiple checkboxes

Datalist driver

Used for HTML5 datalists

File driver

For file uploads

Radio driver

For single <input type="radio">

Radios driver

For multiple radios

Select driver

For select dropdowns

Textarea driver

For textarea fields

Form and field objects have HTML helper methods that make working with objects as html simpler.

add_class()

Add a class or classes to a field/form object

$form->add_class('new-class');

$form->add_class('new-class1 new-class2');

$form->add_class(['new-class1', 'new-class2']);

attr()

Get or set html tag attribute

$field->attr('placeholder', 'john@doe.com');

$field->attr([
    'placeholder' => 'john@doe.com',
    'disabled' => true,
    'style' => 'width: 300px',
]);

$placeholder = $field->attr('placeholder');

attr_fields()

Set attributes for a set of fields

$form->attr_fields([
    'email' => [
        'placeholder' => 'john@doe.com',
    ],
    'first_name' => [
        'disabled' => true',
        'style' => 'background: purple',
    ],
]);

close();

Return the field's closing tag

echo $form->close();

html()

Get or set HTML part of the tag

$button->html('Click me to submit');

$html = $button->html();

label()

Get field's label

$label = $field->label();

name()

Return a field's HTML 'name' tag value

$name = $field->name();

open()

Return a field's HTML opening tag

echo $form->open();

remove_class()

Remove class or classes from a field/form object

$form->removeClass('foo');

$form->removeClass('foo1 foo2');

$form->removeClass(['foo1', 'foo2']); ```

Formo gives a convenient way to convert your form and field definitions to an array, recursively.

You can use this to pass around entire form definitions that you can use to create entirely new forms, send to your client, or whatever you want.

To cast a form to an array, use Formo::to_array():

$array = $form->to_array();

You can do the same thing for just a field or subform:

$array1 = $form->email->to_array();

$array2 = $form->my_subform->to_array();

You can also specify which attributes you want to pull out of each object:

$array = $form->to_array(['alias', 'driver', 'rules', 'val']);

How Formo looks for the config setting

For flexibility, when Formo retrieves a config setting, it follows this pattern looking for the setting:

  1. Field itself
  2. Field's parent
  3. Inside the Formo config.php file

Here's a rather complicated example:

$form = Formo::form()
    ->add('first_name', 'input', NULL, ['label_message_file' => 'mymessages2']);

$form2 = Formo::form([
    'alias' => 'form2',
    'label_message_file' => 'mymessages3',
])
->add('last_name', 'input', NULL, ['label_message_file' => FALSE])
->add('middle_initial', 'input', NULL);

$form->add($form2);

Based on the form definition above, here are what each field would use for its label messages file:

$form - Kohana::$config->load('formo.label_message_file');
$form->first_name - mymessages2
$form2 - 'mymessages3'
$form2->last_name - FALSE
$form2->middle_initial - 'mymessages3'

Config setting explanations

Variable Explanation
label_message_file The file used for label messages. You can set this to FALSE to not use Kohana messages at all.
validation_message_file The file used for Kohana validation messages.
translate Set to TRUE or FALSE whether to run labels and errors through Kohana's translation __() function.
close_single_html_tags Set to TRUE or FALSE whether to add a closing / to single tags. For instance, when TRUE, you would get <input type="text"> and set to FALSE you get the HTML 5 styled <input type="text">
auto_id Set to TRUE or FALSE whether fields should that don't have manually specified ID tags set will auto-generate them.
template_dir The directory to look for template view files
namespaces Boolean setting whether to namespace field names. For example <input type="text" name="parent_alias[field_alias]">
orm_driver The driver to use for your ORM
model_base_rules When using ORM driver, whether to automatically add mysql field base rules to formo field
input_rules These are pre-defined rules that get added to input elements with HTML 5 type attributes that are supposed to have built-in validation. These pre-defined rules are supposed to mimic those that are built into the browser with these type tags.
ftype Pre-defined defaults for forms and fields. See ftype docs

You may want to create a set of default form and field definitions that get reused everywhere. For instance, you may want to add a class attribute to all your inputs to match Twitter Bootstrap or another css framework markup.

Whatever the reason, it's simple to pre-define form defaults.

ftype config format

The format of the ftype config param looks like this:

'ftype' => [
    // This applies to all forms that don't match an ftype in this config array
    ':all' => [
        ':self' => [
            'attr.class' => 'someclass'
        ],
        // These apply to fields with specific drivers that are within the form
        'driver' => [
            'input' => [
                'attr.class' => 'form-control',
            ],
            'input|email' => [
                'attr.class' = 'form-control',
            ]
            'textarea' => [
                'attr.class' => 'form-control',
            ],
        ],
        // These apply to fields based on the ftype. (This overrides the driver-based definition)
        'ftype' => [
            'special' => [
                'attr.class' => 'special',
                'rules' => $rules_array,
            ],
        ],
    ],
    // This refers to a parent with the ftype 'inline'
    'inline' = [
        // The form default settings with the ftype 'inline'
        ':self' => [
            'attr.class' => 'form-horizontal',
        ],
        // Look for fields based on drivers within the form with ftype 'inline'
        'driver' => [
            'input' => [
                'attr.class' => 'form-control',
            ],
        ],
        // Look for fields based on ftype within the form with ftype 'inline'
        'ftype' => [
            'street' => [
                'attr.class' => 'special-street-class',
                'rules' => $street_rules_array,
            ],
        ],
    ],
]

Formo allows a config option called ftype to determine which pre-defined attributes to load into your form and field objects. These definitions look for form and field ftype to decide whether the options appy to that field. Consider the following form and subform.

To once your form is set up, you can inject all these definitions into your form object with the method Formo::ftype().

$form = Formo::form([
        'alias' => 'myform'
    ])
    ->add('first', 'input')
    ->add('email', 'input|email')
    ->add('notes', 'textarea');

$form->address = Formo::form([
        'ftype' => 'inline',
    ])
    ->add([
        'alias' => 'street',
        'ftype' => 'street',
    ])
    ->add('city')
    ->add('state', 'select', $states);

$form->ftype();

That's a typical form definition. There's a form and a subform. The suborm has the ftype of address and its street.

In the form above, the parent form matches the ':all' ftype definitions, so the appropriate defaults are applied within.

The subform 'address' matches the 'inline' ftype definitions, so the appropriate definitions are all from within that. The field 'street' matches the 'street' ftype within the 'inline' ftype, so it is used instead of the 'input' driver definition.

Formo's ftype offers a powerful way to create reusable, base configs for all your forms and subforms.

While using Formo, it's important to understand Formo's terminology.

Field

A field is a form field attached to a form. You attach fields using Formo::add(). Fields are accessed directly from its form.

In this example, email is the field's alias, and it can be accessed as $form->email.

$form->add('email', 'input|email');

Driver

The driver handles field-specific functionality. For instance, a checkbox field functions totally different from an input type="text" field.

Formo drivers handle every field-specific function from setting the value correctly to getting the field's label.

In this example, the driver is select, and a select driver requires an extra array of opts (options).

$form->add('hobbies', 'select', NULL, ['opts' => $options]);

Subform

A group of fields added to a form.

In this example, $f2 is the subform.

$form = Formo::form();

$f2 = Formo::form(['alias' => 'mysubform']);

$form->add($f2);

Rules

Rules are validation rules

Filters

Filters are run on field values before validation