HTML::...okbook(3pm)

πŸ“¦
version 2.07000 Β· 5 versions
, in debian bookworm, ubuntu jammy, ubuntu noble

πŸ’– HTML::FormFu::Manual::Cookbook πŸ’–

okay bestie, so this is HTML::FormFu::Manual::Cookbook β€” it lives in the User Contributed Perl Documentation section of the manual (the 3pm part, iykyk). It's basically the group chat of FormFu recipes. Grab a snack, we're cooking πŸ“


🌸 NAME 🌸

HTML::FormFu::Manual::Cookbook β€” Cooking with HTML::FormFu πŸ‘©β€πŸ³βœ¨

🌸 VERSION 🌸

version 2.07 β€” the numbers, they're giving current πŸŽ€

🌸 DESCRIPTION 🌸

A lil grab-bag of miscellaneous useful recipes for using HTML::FormFu. That's it, that's the description. Short king energy, but the recipes below are iconic.


πŸ¦‹ GETTING STARTED πŸ¦‹

Some genuinely useful info for beginners. No gatekeeping here, we're all just vibing with forms πŸ’œ

✨ Default search paths for config files ✨

The current working directory (aka cwd) β€” peep "load_config_file" in HTML::FormFu for the deets.

If you're using the FormConfig action attribute from Catalyst::Controller::HTML::FormFu, then your forms should be saved in root/forms. Want more? Go read "SYNOPSIS" in Catalyst::Controller::HTML::FormFu and "config_file_path" in Catalyst::Controller::HTML::FormFu. Slay πŸ’…

✨ YAML ✨

Most examples in the HTML::FormFu docs use YAML syntax. You can use any configuration file type that Config::Any supports, but the author's preferred format is β€” drumroll β€” YAML. Respect the author's taste, it's valid 🫢

You can populate a form from a config file by calling "load_config_file" in HTML::FormFu with the filename as an argument. That config file gets converted into a perl data-structure, and then it's handed off to "populate" in HTML::FormFu. Cute pipeline, no notes.

The config file must contain a hash-ref, where the keys match up to the form's method-names, and the values are the method arguments. For example, these two are literally the same thing:

---
auto_fieldset: 1
elements:
  - name: foo
  - name: bar
# the above YAML is equivalent to the following perl code

$form->auto_fieldset(1);

$form->elements([
    { name => 'foo' },
    { name => 'bar' },
]);
⚠️ When you're writing your config file, remember: perl hashes are unordered and cannot have multiple keys with the same name. That's just the rules, bestie.

See "load_config_file" in HTML::FormFu and "populate" in HTML::FormFu for more details.

And if you want the YAML spec itself: <http://www.yaml.org/spec/> β€” go off πŸ“œ


πŸŽ€ BUILDING A FORM πŸŽ€

✨ Quick single-file prototypes ✨

You can run the script below to quickly peep a form's markup β€” just swap out whatever's inside the "__DATA__" section with your own YAML config. Prototype era πŸ’…

#!/usr/bin/perl
    use warnings;
use HTML::FormFu;
use YAML::XS qw( LoadFile );

my $form = HTML::FormFu->new;
my $data = LoadFile(\*DATA);

$form->populate($data);

print $form;

__DATA__
---
auto_fieldset: 1
elements:
  - type: Text
    name: foo

✨ Unsupported HTML tags ✨

Got a tag FormFu doesn't natively do? Use the HTML::FormFu::Element::Block element and set the tag to create any arbitrary pair of tags you want. Main character energy, honestly.

---
elements:
  - type: Block
    tag: span
    content_xml: "<b>Hi!</b>"

You can use "content" in HTML::FormFu::Element::Block, "content_xml" in HTML::FormFu::Element::Block or "content_loc" in HTML::FormFu::Element::Block to add whatever content your heart desires, or use "element" in HTML::FormFu::Element::Block to add elements. So many options, all of them valid πŸ¦‹


🌈 Application-wide default values 🌈

You can set defaults automatically with "default_args" in HTML::FormFu β€” and if you set this in a Catalyst application config file, it'll take effect across your entire application. It's giving "set it and forget it", fr fr:

myapp.yml
---
'Controller::HTML::FormFu':
  constructor:
    default_args:
      elements:
        Textarea:
          rows: 10

πŸ’… MODIFYING A FORM πŸ’…

✨ Insert a new field before existing form fields ✨

Look at "insert_before" in HTML::FormFu and "insert_after" in HTML::FormFu. They do exactly what they say on the tin.

my $fieldset = $form->get_element({ type => 'Fieldset' });

$fieldset->insert_before(
    $form->element(\%specs),
    $form->get_field($name)
);

Another way to approach the problem is to use multiple config files and decide which ones to load at runtime. Honestly? Obsessed with this approach.

# user_edit.yml
---
elements:
  - type: Text
    name: email
# user_username.yml
---
elements:
  - type: Text
    name: username
# user_register.yml
---
load_config_file:
 - user_username.yml
 - user_edit.yml
# create a user edit form, with only the email field

$form->load_config_file( 'user_edit.yml' );

# create a user registration form with username and email fields

$form->load_config_file( 'user_register.yml' );

✨ Form and Field attributes ✨

You can slap any arbitrary attributes onto a form with "attributes" in HTML::FormFu, or onto any element with "attributes" in HTML::FormFu::Element. Go feral, the attributes are yours πŸŽ€

---
attributes_xml:
  onsubmit: "js_function()"
elements:
  - type: Text
    name: foo
    attributes_xml:
      onchange: "js_function()"

πŸ”₯ FORM VALIDATION πŸ”₯

✨ Check valid dates ✨

Use HTML::FormFu::Inflator::DateTime. When the inflator gets processed, it'll try to create a DateTime object. If the supplied values don't make a valid date, you get an error back. No fake dates on this form's watch πŸ’€

✨ Check valid URI / URLs ✨

Go look at HTML::FormFu::Element::URL or HTML::FormFu::Constraint::Regex. Pick your fighter πŸ₯Š

✨ Implement a custom constraint / validator ✨

If HTML::FormFu::Constraint::Callback or HTML::FormFu::Validator::Callback isn't enough for your needs, you can absolutely build your own class. Just inherit from HTML::FormFu::Constraint or HTML::FormFu::Validator respectively β€” like, whichever one you need.

It should implement a validate_value method, which returns true if the value is valid, or false otherwise. Simple as that, bestie.

package My::Custom::Validator;
use Moose;
extends 'HTML::FormFu::Validator';

sub validate_value {
  my ( $self, $value, $params ) = @_;

  return 1 if value_is_valid( $value );

  return;
}

1;

Then add your custom validator to the form β€” look at you go πŸ¦‹

---
elements:
  - type: Text
    name: foo
    validators:
      - '+My::Custom::Validator'

✨ Constrain one form field based on the value of another ✨

Say you've got a radiogroup and several text fields, and which text fields are required depends on what the radiogroup is set to. Very "it depends" of you, and honestly same.

You do this with the when attribute of a constraint:

constraints:
  - type: Length
    min: 8
    when:
      field: bar
      values: [ 1, 3, 5 ]

In the example above, the Length constraint is only processed when the form field named bar has a value of either 1, 3 or 5. Periodt.

You can also test for a negative condition using the not attribute:

constraints:
  - type: Length
    min: 8
    when:
      field: bar
      values: [ 1, 3, 5 ]
      not: 1

Now the constraint only gets processed if the value of field bar is NOT 1, 3 or 5. Sneaky little inversion, love that for you πŸ’…

πŸ“Œ Note: if you rely on the value of a checkbox for a when-restricted constraint, you might wanna consider setting "default_empty_value" for that checkbox. Go peek at HTML::FormFu::Role::Element::Field to learn more β€” it's worth it, no cap.

Please read HTML::FormFu::Constraint for further information. It's the source of truth, bestie πŸ“š

✨ Constrain one form field based on the return value of a callback ✨

You can also use the when attribute of a constraint to decide β€” via a callback β€” whether the constraint should even be applied. The callback is the one in charge now πŸ‘‘

For instance, this (code) example shows a constraint being applied only if the value of another field contains a pattern:

my $apply_if_pattern = sub {
    my $params = shift;
    return 1 if $params->{other_field} =~ m/\A ice_cream \z/xms;
    return 0;
};

$field->{constraints} = {
    type    => 'Required',
    when    => {
        callback    => $apply_if_pattern,
    }
}

Please read HTML::FormFu::Constraint for further information. Again. It's that important πŸ“


πŸ’œ HTML MARKUP πŸ’œ

✨ Indented HTML ✨

Use HTML::FormFu::OutputProcessor::Indent β€” your markup will be all neat and pretty, it's giving organised πŸŽ€

---
output_processors:
  - Indent

✨ Add a blank div (e.g. for AJAX purposes) ✨

Just drop a Block element in the relevant place β€” it defaults to a DIV tag, no extra config needed. Chef's kiss πŸ‘¨β€πŸ³πŸ’‹

---
elements:
  - type: Text
    name: user

  - type: Block
    id: foo

  - type: Text
    name: email

πŸŽ€ DISPLAY πŸŽ€

πŸ’– Custom error messages

okay bestie, so sometimes you need to throw an error that comes from your own code β€” like a database check, or literally anything that isn't already a Constraint or Validator. The move here? A Callback Constraint. ✨

And here's the tea: if you don't give it your own callback routine, the default callback will always pass, no matter what the user typed. So you can weaponise that β€” set force_errors and its error message shows up whenever you need it. πŸ‘‘

Example config:

---
elements:
  - type: Text
  - name: email
  - constraints:
    type: Callback
    message: 'Email address already in use'

Example usage:

if ( $@ =~ m/duplicate entry for key 'email'/i ) {

    $form->get_field('email')
         ->get_constraint({ type => 'Callback' })
         ->force_errors(1);

    $form->process;
    # then redisplay the form as normal
}

🌸 Highlight required fields (or fields with certain types of constraint)

Bestie, you can pull this off with the form's auto_constraint_class method:

$form->auto_constraint_class( 'constraint_%t' );

After that, every container div wrapping a form field that has a constraint gets extra CSS classes β€” which tell you what kind of constraint it is and let you style it however you want with CSS:

/* change background of labels for fields with a Required constraint */
fieldset .constraint_required label {
    background: #f00;
}

And you can also use this trick to add content before or after the fields in question β€” though heads up, this won't work in older browsers with more limited CSS support, like IE6:

/* add an asterisk at the end of the label for required fields */
fieldset .constraint_required label:after {
    content: '*'
}

πŸ¦‹ Add a popup hint to a field

Most browsers show a tooltip when a user hovers over an HTML element that has a title tag. Aural browsers might even try to turn that content into speech. πŸ—£οΈ You can totally use this to give the user a hint about how to fill in a form field.

elements:
  - type: URL
    name: url
    label: Website
    title: 'Must start with http:// or https://'

The above gives a hint whenever the url field gets focus. Or, if you'd rather, you can put the hint on the container tag that wraps both the field and the label:

elements:
  - type: URL
    name: url
    label: Website
    container_attributes:
        title: 'Must start with http:// or https://'

πŸ’… Display filtered values

If a field has a Filter on it β€” say, HTML::FormFu::Filter::Whitespace stripping leading / trailing whitespace β€” then when you redisplay the form, the field normally gets repopulated with whatever the user originally typed.

If you'd prefer the field to hold the filtered value instead, just use render_processed_value in HTML::FormFu. ✨


πŸ‘‘ Multiple forms using Catalyst::Controller::HTML::FormFu

Sometimes you need multiple forms on a single page. And if you try to use FormConfig on several actions in a chain, or something similar β€” plot twist β€” they all use $c->stash->{form} to store the form, so you only end up with the last one. 😭

One way around this is to do a little of the work yourself:

In this example we have a login_form that we want on every page

# root/forms/login.yml:
---
    indicator: username
    elements:
        -
            type: Text
            name: username
            constraints:
                - Required
...

We also have an edit-form

# root/forms/foo/edit.yml
---
    indicator: foo
    elements:
    -
        type: Text
        name: foo
        constraints:
            - Required
...

In this example, we want the login form to appear on every page, so we load this in the top-most auto action:

package MyApp::Controller::Root;

BEGIN { extends 'Catalyst::Controller::HTML::FormFu'; }

sub auto : Private {
    my ($self, $c) = @_;

    # We want to utilize a lot of the magic that the controller
    # gives us, so therefore we call $self->form like this

    my $login_form = $self->form;
    $login_form->load_config_file('login.yml');

    # Notice how we put it into another stash var, not 'form'
    $c->stash->{login_form} = $login_form;
    unless ($c->user_exists) {

        $login_form->process();

        if ($login_form->submitted_and_valid) {

            # Since we set indicator, we should only end up here if we
            # have a username in the form
            $c->authenticate({
                username => $login_form->param_value('username'),
                password => $login_form->param_value('password'),
            });
        }

    }
}

Any other page that wants to load another form can now do so freely:

package MyApp::Controller::Foo;

sub edit : Local FormConfig {
    my ( $self, $c ) = @_;

    my $form = $c->stash->{form};
    if ($form->submitted_and_valid) {
        # Do whatever you want with it :p
    }
}

In the view we now have two stash-variables:

In root/foo/edit.tt:

[% login_form %]
<h2>edit</h2>
[% form %]

🌈 ADVANCED CUSTOMISATION 🌈

🧚 Installing the TT templates

Real talk: it only makes sense to use the template files if you're actually planning on customising them, because the default string render-method is faster. ⚑

As of HTML::FormFu v1.00, TT is no longer listed as a required prerequisite β€” so you'll need to install it manually if you want to use the template files. (Yes, "if you with to" β€” that's the original's typo, we keep the lore. πŸ’€)

If you're using the Catalyst web framework, install Catalyst::Controller::HTML::FormFu and run this command:

$ script/myapp_create.pl HTML::FormFu

This creates a directory, root/formfu, containing the HTML::FormFu template files. πŸŽ€

If you extend Catalyst::Controller::HTML::FormFu and you don't set HTML::FormFu's INCLUDE_PATH yourself, it'll automatically be set to root/formfu β€” but only if that directory exists.

If you're not using Catalyst, you can create the template files by running this command instead:

  $ html_formfu_deploy.pl <target-directory>
Take note, bestie: if you choose to customise your own copy of HTML::FormFu's template files, you'll need to keep track of the Changes file whenever you update HTML::FormFu, so you can update your own templates if the core templates get updated. πŸ‘€

⭐ PERFORMANCE ⭐

πŸ”₯ Catalyst::Plugin::StackTrace

If you're using Catalyst::Plugin::StackTrace, make sure you're on at least version 0.09 β€” earlier versions had performance problems with HTML::FormFu. No cap.

πŸ’œ Template::Alloy

You can also use Template::Alloy instead of Template::Toolkit! It's mostly compatible, and in many cases gives a reasonable speed increase. 🏎️ Do it either by setting the HTML_FORMFU_TEMPLATE_ALLOY environment variable to a true value, or by passing TEMPLATE_ALLOY to tt_args in HTML::FormFu:

tt_args:
  TEMPLATE_ALLOY: 1
  COMPILE_DIR: /tmp
  COMPILE_PERL: 1

Template::Alloy's caching is off by default. Switch it on by setting either COMPILE_EXT or COMPILE_DIR. And if you're running under a persistent environment like modperl or fastcgi, you should also set COMPILE_PERL to compile the cached templates down to perl code.

Of cource β€” yes, that's the original spelling, iconic β€” if you wish, you can still use Template::Toolkit to process your own application templates, and let Template::Alloy handle just the HTML::FormFu templates. 🫢

🌟 HTML:FormFu::Preload

To reduce the runtime for each form that uses a previously unused element or processor β€” at the cost of greater memory usage β€” you can preload all FormFu modules. This is only recommended for persistent environments such as modperl or fastcgi:

use HTML::FormFu::Preload;

πŸ“ FAQs πŸ“

πŸŽ€ Force an element to always have a certain value

See the following:

retain_default in HTML::FormFu::Role::Element::Field, force_default in HTML::FormFu::Role::Element::Field


✨ AUTHORS ✨

Will Hawes wdhawes@gmail.com

Carl Franks cfranks@cpan.org

This document is free, you can redistribute it and/or modify it under the same terms as Perl itself.

🌸 AUTHOR 🌸

Carl Franks <cpan@fireartist.com>

This software is copyright (c) 2018 by Carl Franks.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


and that's the whole page, bestie!! 🫢 this one's from perl v5.28.1, dated 2019-01-12 β€” yes it's giving vintage, but it's still serving HTML::...okbook(3pm) realness. go build something iconic with it πŸ’…βœ¨

πŸ“¦ where this page comes from