CGI::A...emplate(3pm)

okay bestie, welcome to the CGI::A...emplate(3pm) page from User Contributed Perl Documentation πŸ’– this is the manual for CGI::Application::Plugin::AnyTemplate, and it's literally giving unified-interface realness. no cap. πŸ’…

✨ NAME ✨

CGI::Application::Plugin::AnyTemplate β€” use any templating system from within CGI::Application using a unified interface. periodt. πŸŽ€

🌈 VERSION 🌈

Version 0.18 ✨

πŸŽ€ SYNOPSIS πŸŽ€

In your CGI::Application-based webapp, you go:

use base 'CGI::Application';
use CGI::Application::Plugin::AnyTemplate;

sub cgiapp_init {
    my $self = shift;

    # Set template options
    $self->template->config(
        default_type => 'TemplateToolkit',
    );
}

Later on, in a runmode:

sub my_runmode {
    my $self = shift;

    my %template_params = (
        name     => 'Winston Churchill',
        age      => 7,
    );

    $self->template->fill('some_template', \%template_params);
}

πŸ’– DESCRIPTION πŸ’–

πŸ¦‹ Template-Independence πŸ¦‹

CGI::Application::Plugin::AnyTemplate lets you use any supported Perl templating system through one single consistent interface. iconic. πŸ”₯

Currently supported templating systems include HTML::Template, HTML::Template::Expr, HTML::Template::Pluggable, Template::Toolkit, and Petal.

You can access any of these templating systems using the same interface. That way, you can use the same code and switch templating systems on the fly. πŸ’«

This approach has many uses. For instance, it can be useful in migrating your application from one templating system to another. lowkey genius.

🌸 Embedded Components 🌸

In addition to template abstraction, AnyTemplate also provides an embedded component mechanism. Like, you might include a header component at the top of every page and a footer component at the bottom of every page. 🫢

These components are actually full CGI::Application run modes, and they can do anything a normal run mode can do, including processing form parameters and filling in their own templates. See below under "EMBEDDED COMPONENTS" for details. ✨

πŸ’œ Multiple Named Template Configurations πŸ’œ

You can set up multiple named template configurations and select between them at run time. main character energy.

sub cgiapp_init {
    my $self = shift;

    # Can't use Template::Toolkit any more -
    # The boss wants everything has to be XML,
    # so we switch to Petal

    # Set old-style template options (legacy scripts)
    $self->template('oldstyle')->config(
        default_type => 'TemplateToolkit',
        TemplateToolkit => {
            POST_CHOMP => 1,
        }
    );
    # Set new-style template options as default
    $self->template->config(
        default_type => 'Petal',
        auto_add_template_extension => 0,
    );
}

sub old_style_runmode {
    my $self = shift;

    # ...

    # use TemplateToolkit to fill template edit_user.tmpl
    $self->template('oldstyle')->fill('edit_user', \%params);

}

sub new_style_runmode {
    my $self = shift;

    # ...

    # use Petal to fill template edit_user.xhml
    $self->template->fill('edit_user.xhtml', \%params);

}

✨ Flexible Syntax ✨

The syntax is pretty flexible. Pick a style that's most comfortable for you. πŸ’…

CGI::Application::Plugin::TT style syntax:

$self->template->process('edit_user', \%params);

or (with slightly less typing):

$self->template->fill('edit_user', \%params);

CGI::Application load_tmpl style syntax:

my $template = $self->template->load('edit_user');
$template->param('foo' => 'bar');
$template->output;

Verbose syntax (for complete control):

my $template = $self->template('named_config')->load(
    file              => 'edit_user'
    type              => 'TemplateToolkit'
    add_include_paths => '.',
);

$template->param('foo' => 'bar');
$template->output;

See also below under "CHANGING THE NAME OF THE 'template' METHOD". πŸŽ€

πŸ’… METHODS πŸ’…

πŸ“ config πŸ“

Initialize the AnyTemplate system and provide the default configuration. 🫢

$self->template->config(
    default_type => 'HTMLTemplate',
);

You can keep multiple configurations handy at the same time by passing a value to template:

$self->template('oldstyle')->config(
    default_type => 'HTML::Template',
);
$self->template('newstyle')->config(
    default_type => 'HTML::Template::Expr',
);

Then in a runmode you can mix and match configurations:

$self->template('oldstyle')->load  # loads an HTML::Template driver object
$self->template('newstyle')->load  # loads an HTML::Template::Expr driver object

The configuration passed to config is divided into three areas: plugin configuration, driver configuration, and native configuration:

  • Plugin Config β€” configures AnyTemplate itself
  • Driver Config β€” configures the AnyTemplate Driver (e.g. HTMLTemplate)
  • Native Config β€” configures the actual template module (e.g. HTML::Template)

These are described in more detail below. ✨

πŸŽ€ Plugin Configuration πŸŽ€

These configuration params are specific to the CGI::Application::Plugin::AnyTemplate itself. They are included at the top level of the configuration hash passed to config. For instance:

$self->template->config(
    default_type                => 'HTMLTemplate',
    auto_add_template_extension => 0,
);

The plugin configuration parameters and their defaults are as follows:

  • default_type, type β€” the default type of template for this named configuration. Should be the name of a driver in the CGI::Application::Plugin::AnyTemplate::Driver namespace:
    • HTMLTemplate β†’ CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplate
    • HTMLTemplateExpr β†’ CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplateExpr
    • TemplateToolkit β†’ CGI::Application::Plugin::AnyTemplate::Driver::TemplateToolkit
    • Petal β†’ CGI::Application::Plugin::AnyTemplate::Driver::Petal
  • include_paths β€” Include Paths (sometimes called search paths) are used by the various template backends to find filenames that aren't fully qualified by an absolute path. Each directory is searched in turn until the template file is found. Can be a single string or a reference to a list. πŸ¦‹
  • auto_add_template_extension β€” Add a template-system specific extension to template filenames. So, if this feature is enabled and you provide the filename myfile, then the actual filename will depend on the current template driver:
    • HTMLTemplate β†’ myfile.html
    • HTMLTemplateExpr β†’ myfile.html
    • TemplateToolkit β†’ myfile.tmpl
    • Petal β†’ myfile.xhtml

The per-type extension is controlled by the driver config for each AnyTemplate driver (see below under "Driver and Native Configuration" for how to set this).

The auto_add_template_extension feature is on by default. To disable it, pass a value of zero:

$self->template->config(
    auto_add_template_extension => 0,
);

The automatic extension feature is not just there to save typing β€” it's actually there so you can have templates of different types sitting in the same directory. 🌈

sub my_runmode {
    my $self = shift;
    $self->template->fill;
}

Then in your template path you can have three files:

my_runmode.html
my_runmode.tmpl
my_runmode.xhtml

Then you can change which templates is used by changing the value of type that you pass to $self->template->config.

For applications that want to dynamically choose their template system without changing app code, it's a cleaner solution to use the extensions than trying to swap template paths at runtime. Even if you keep each type of template in its own directory, it's simpler to include all the directories all the time and use different extensions for different template types. πŸ’…

  • template_filename_generator β€” If you don't pass a filename to load, one will be generated for you based on the current run mode. If you want to customize this process, you can pass a reference to a subroutine to do the translation. This subroutine will be passed a reference to the CGI::Application $self object. πŸŽ€

Here is a subroutine that emulates the built-in behaviour of AnyTemplate:

$self->template->config(
    template_filename_generator => sub {
        my ($self, $calling_method_name) = @_;
            return $self->get_current_runmode;
        }
    }
);

Here is an example of using a template filename generator to make full templates with full paths based on the module name as well as the current run mode (this is similar to how CGI::Application::Plugin::TT generates its template filenames):

package My::WebApp;
use File::Spec;

sub cgiapp_init {
    my $self = shift;

    $self->template->config(
        template_filename_generator => sub {
            my $self     = shift;
            my $run_mode = $self->get_current_runmode;
            my $module   = ref $self;

            my @segments = split /::/, $module;

            return File::Spec->catfile(@segments, $run_mode);
        }
    );
}

sub run_mode {
    my $self = shift;
    $self->template->load;  # loads My/WebApp/run_mode.html
}

sub other_run_mode {
    my $self = shift;
    $self->template->load;  # loads My/WebApp/other_run_mode.html
}

Note that if the auto_add_template_extension option is on (which it is by default), then the extension will be added to your generated filename after you return it. If you do not want this to happen, then set auto_add_template_extension to a false value. 😭

  • component_handler_class β€” Normally, component embedding is handled by CGI::Application::Plugin::AnyTemplate::ComponentHandler. If you want to use a different class for this purpose, specify the class name as the value of this parameter. πŸ‘‘

It still has to provide the same interface as CGI::Application::Plugin::AnyTemplate::ComponentHandler. Bestie, just go read the source code of that module for the deets, ok? πŸ‘€

  • return_references β€” when this is true (and it is the default, no cap), output hands you back a reference to a string instead of a copy. Usually you literally cannot tell the difference. Like, CGI::Application does not care even a little whether your run mode returns a string or a reference to a string. πŸ’β€β™€οΈ

But β€” and here's the tea β€” if you wanna mess with the $html that came out of the template, you might find it way more convenient to make output return an actual string instead of a ref. Especially if you're porting over old code built on HTML::Template, which expects output to return a string. So flip it when you need it. ✨


🌈 Driver and Native Configuration 🌈

You can configure all the drivers at once with a single call to config, just by dropping in a subsection for each driver type:

$self->template->config(
    default_type => 'HTMLTemplate',
    HTMLTemplate => {
        cache              => 1,
        global_vars        => 1,
        die_on_bad_params  => 0,
        template_extension => '.html',
    },
    HTMLTemplateExpr => {
        cache              => 1,
        global_vars        => 1,
        die_on_bad_params  => 0,
        template_extension => '.html',
    },
    HTMLTemplatePluggable => {
        cache              => 1,
        global_vars        => 1,
        die_on_bad_params  => 0,
        template_extension => '.html',
    },
    TemplateToolkit => {
        POST_CHOMP         => 1,
        template_extension => '.tmpl',
    },
    Petal => {
        error_on_undef     => 0,
        template_extension => '.xhtml',
    },
);

Every driver knows how to sort its own config from the config that actually belongs to the underlying template system. It's giving ✨boundaries✨.

So like, in that example up there, the HTMLTemplate driver knows template_extension is a driver config parameter, but cache_global_vars and die_on_bad_params are all HTML::Template configuration parameters. 🧠

Same energy for TemplateToolkit β€” that driver knows template_extension is a driver config param, but POST_CHOMP is a Template::Toolkit configuration parameter.

Wanna know the nitty-gritty on driver config? Go read the docs for each individual driver, bestie:

  • CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplate
  • CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplateExpr
  • CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplatePluggable
  • CGI::Application::Plugin::AnyTemplate::Driver::TemplateToolkit
  • CGI::Application::Plugin::AnyTemplate::Driver::Petal

πŸ’Œ Copying Query data into Templates πŸ’Œ

Heads up: this feature is deprecated now and will be removed in a future release. It's on its way out, bestie. 😭

When you turn this feature on, everything in $self->query gets copied into the template object before the template is processed. All of it. Every last param. 🫣

For the HTMLTemplate, HTMLTemplateExpr and HTMLTemplatePluggable drivers, this is done with the associate feature of HTML::Template and HTML::Template::Expr, respectively:

my $template = HTML::Template->new(
    associate => $self->query,
);

For the other systems, this feature is emulated β€” the query params get copied into the template params before the template is processed. Fake it till you make it, I guess. πŸ’…

To enable this feature, pass a true value to associate_query or emulate_associate_query (whichever one your template system wants):

$self->template->config(
    default_type => 'HTMLTemplate',
    HTMLTemplate => {
        associate_query => 1,
    },
    HTMLTemplateExpr => {
        associate_query => 1,
    },
    HTMLTemplatePluggable => {
        associate_query => 1,
    },
    TemplateToolkit => {
        emulate_associate_query => 1,
    },
    Petal => {
        emulate_associate_query => 1,
    },
);

Okay so why is this off by default now? Because it poses a potential XSS (Cross Site Scripting) security risk. Do not play with that. 🚨

And why is it deprecated? Because in an ideal world, developers shouldn't have to flatten objects and hashes just to make them available to their templates. Like, imagine. You should be able to pass the query object (or another object, say a config object) straight into the template:

$template->param(
    'query' => $self->query,
    'cfg'   => $self->cfg,
    'ENV'   => $ENV,
);

And then in the template, grab those params directly:

your username: [% query.param('username') %]
administrator: [% cfg.admin %]
hostname:      [% ENV.SERVER_NAME %]

This approach is iconic and it works with Template::Toolkit, Petal, and HTML::Template::Pluggable (via the HTML::Template::Plugin::Dot plugin). πŸ¦‹

Important: associate and associate_query are not compatible. So if you want to associate the query and an additional object, pass a list to associate:
$template->config(
    HTMLTemplate => {
        associate => [$self->query, $self->conf]
    }
);

🌸 load 🌸

Creates a new template object and configures it. New template, who dis. πŸ’–

It can be as simple (and magical ✨) as:

my $template = $self->template->load;

When you call load with no parameters at all, it uses the default template type, the default template configuration, and it figures out the template name from the name of the current run mode. How? By calling $self->get_current_runmode. Slay. πŸ‘‘

Now β€” if you want the current runmode to actually update when you hand control off to another runmode, use the CGI::Application::Plugin::Forward module:

use CGI::Application::Plugin::Forward;

sub first_runmode {
    my $self = shift;
    return $self->forward('second_runmode');
}
sub second_runmode {
    my $self = shift;
    my $template = $self->template->load;  # loads 'second_runmode.html'
}

But if you instead call $self->other_method directly? Yeah, $self->get_current_runmode does not get updated. Caught in 4k. πŸ™ˆ

sub first_runmode {
    my $self = shift;
    return $self->other_method;
}
sub other_method {
    my $self = shift;
    my $template = $self->template->load;  # loads 'first_runmode.html'
}

If you wanna override how that default template filename gets generated, you totally can β€” just use the template_filename_generator configuration parameter.

Call load with one parameter and it's taken to be either the filename, or a reference to a string containing the template text:

my $template = $self->template->load('somefile');
my $template = $self->template->load(\$some_text);

If the parameter auto_add_template_exension is true, then the appropriate extension gets added for this template type. ✨ (Yes, we're spelling it exactly like the original does. Periodt.)

Call load with more than one parameter and you can specify filename and configuration parameters directly:

my $template = $self->template->load(
    file                        => 'some_file.tmpl',
    type                        => 'HTMLTemplate',
    auto_add_template_extension => 0,
    add_include_paths           => '..',
    HTMLTemplate => {
        die_on_bad_params => 1,
    },
);

And to initialize the template from a string instead of a file, do this:

my $template = $self->template->load(
    string =>  \$some_text,
);

The configuration parameters you pass to load get merged with whatever config you already passed to config. Merged, bestie. Not replaced. 🫢

You can include any of the configuration parameters you can pass to config, plus these extra ones:

  • file β€” if you're loading the template from a file, then file holds the template's filename. Simple as that. πŸ“
  • string β€” if you're loading the template from a string, then string holds the text of the template. It can be either a scalar or a reference to a scalar. Both of these work:
# passing a string
my $template = $self->template->load(
    string => $some_text,
);

# passing a reference to a string
my $template = $self->template->load(
    string => \$some_text,
);
  • add_include_paths β€” extra include paths. These get merged with include_paths before being passed to the template driver. πŸ›£οΈ

The load method returns a template driver object. Scroll down to DRIVER METHODS to see how to use that object, bestie.


πŸŽ€ fill πŸŽ€

fill is a convenience method that does the whole glow-up in one step: it creates the template, fills it with the template parameters, and returns the output. One call, three jobs. Multitasking queen. πŸ‘‘

You can call it with or without a filename (or a string ref).

This code:

$self->template->fill('filename', \%params);

is equivalent to:

my $template = $self->template->load('filename');
$template->output(\%params);

And this code:

$self->template->fill(\$some_text, \%params);

is equivalent to:

my $template = $self->template->load(\$some_text);
$template->output(\%params);

And this code:

$self->template->fill(\%params);

is equivalent to:

my $template = $self->template->load;
$template->output(\%params);

And this code:

$self->template->fill('filename');

is equivalent to:

my $template = $self->template->load('filename');
$template->output;

And this code:

$self->template->fill(\$some_text);

is equivalent to:

my $template = $self->template->load(\$some_text);
$template->output;

And finally, this code:

$self->template->fill;

is equivalent to:

my $template = $self->template->load;
$template->output;

⭐ process ⭐

process is an alias for fill. That's it. That's the whole vibe. Two names, one icon. πŸ’…

✨ APPLICATION METHODS ✨

These ones? You call them directly on your application's $self object. No middleman, no nothing. πŸ’…


πŸŽ€ load_tmpl

Okay so this is basically a glow-up emulation of CGI::Application's own built-in load_tmpl method. Like, same energy, but with ✨extra✨. For instance:

$self->load_tmpl('some_template.html');

It is not exported by default, bestie. If you want it, you gotta ask nicely:

use CGI::Application::Plugin::AnyTemplate qw/:load_tmpl/;

Then you can call it exactly the way it's documented in CGI::Application and it'll have the same effect. BUT β€” and this is the slay part β€” it respects the current template type, so you can still use it to fill templates from different backends. Versatility? Iconic. πŸ‘‘

The whole idea is that you can take an existing CGI::Application-based webapp that's running HTML::Template templates, and just drop this into it:

use CGI::Application::Plugin::AnyTemplate qw/:load_tmpl/;

sub setup {
    my $self = shift;
    $self->template->config(type => TemplateToolkit);
}

And boom πŸ’₯ β€” every existing call to load_tmpl in your app now uses Template::Toolkit based templates. No cap.

So when you call:

my $template = $self->load_tmpl('some_template.html');

that's literally the equivalent of calling:

my $template = $self->template->load(
    file => 'some_template.html',
    auto_add_template_extension => 0,
);

If you toss extra options into load_tmpl, they get assumed to be HTML::Template specific options β€” with one exception β€” the path option, which gets extracted and used as add_include_paths:

my $template = $self->load_tmpl('some_template.html',
    cache => 0,
    path  => '/path/to/templates',
);

And that gets translated into:

my $template = $self->template->load(
    file => 'some_template.html',
    auto_add_template_extension => 0,
    add_include_paths => '/path/to/templates',
    HTMLTemplate => {
        cache => 0,
    }
);
Heads up bestie: if you specify any HTML::Template-specific options here, they will completely overwrite any options that you passed to config. Like, gone. Poof. πŸ’€

Some notes and caveats about using the load_tmpl method, because we love informed consent 🫢:

  • This method only works for the default template configuration (i.e. $self->template()). If you set up a named configuration (e.g. $self->template('myconfig')) there is no way to access it with load_tmpl. And since plugins should be using named configurations, this means that the load_tmpl method should not be used by plugins. See "NOTES FOR AUTHORS OF PLUGINS AND REUSABLE APPLICATIONS", below.
  • The load_tmpl method does not automatically add an extension to the filename you pass to it β€” even if you have auto_add_template_extension set to a true value in your call to $self->template->config. It just doesn't, okay? πŸ™ˆ
  • The load_tmpl method always returns a string, not a reference to a string. It ignores the setting of the returns_references option. Periodt.

🌸 tmpl_path

You can set the template include_paths by calling:

$self->tmpl_path('/path/to/templates')

You can also do it by passing a value to the TMPL_PATH parameter to your application's new method:

my $webapp = App->new(
    TMPL_PATH => '/path/to/templates',
);

Paths that you set via tmpl_path/TMPL_PATH get put last in the list of include paths β€” after add_include_paths and include_paths. So like, they're at the back of the line, but they're still invited to the party. πŸŽ€


πŸ¦‹ DRIVER METHODS πŸ¦‹

These are the most commonly used methods of the AnyTemplate driver object. And the driver is what you get back from calling $self->template->load. It's giving main character energy, fr fr.


πŸ’œ param

The param method gets and sets values within the template. Double threat.

my $template = $self->template->load;

my @param_names = $template->param();

my $value = $template->param('name');

$template->param('name' => 'value');
$template->param(
    'name1' => 'value1',
    'name2' => 'value2'
);

It's designed to behave similarly to the param method in other modules like CGI and HTML::Template. Consistent vibes across the board, iykyk. ✨


πŸ“ get_param_hash

Returns the template variables as a hash of names and values.

my %params     = $self->template->get_param_hash;

In a scalar context, it returns a reference to the hash used internally to contain the values:

my $params_ref = $self->template->get_param_hash;

$params_ref->{'foo'} = 'bar';  # directly change parameter 'foo'

Ooh, direct access. Living dangerously. I respect it. πŸ’…


⭐ output

Returns the template with all the values filled in.

return $template->output;

You can also supply names and values to the template at this stage:

return $template->output('name' => 'value', 'name2' => 'value2');

Now listen up, because this bit matters πŸ‘‘: if the return_references option is set to true, then the return value of output will be a reference to a string. If the return_references option is false, then a copy of the string will be returned. And by default, return_references is true.

When you call the output method, any components embedded in the template are run. See "EMBEDDED COMPONENTS", below. 🌈


🌸 PRE- AND POST- PROCESS 🌸

There are several ways to customize the template process, because one size does not fit all. You can modify the template parameters before the template is filled, and you can modify the output of the template after it has been filled.

Multiple applications and plugins can hook into the template process pipeline, each making changes to the template input and output. It's a whole collaborative moment. 🀝

For instance, it will be possible to make a general-purpose CGI::Application plugin that adds arbitrary data to each new template (such as query parameters or configuration data). So handy.

⚠️ Real talk: note that the API has changed for version 0.10 in a non-backwards-compatible way in order to use the new hook system provided by recent versions of CGI::Application. So if you're upgrading, check yourself before you wreck yourself.

πŸŽ€ The load_tmpl hook

The load_tmpl hook is designed to be compatible with the load_tmpl hook defined by CGI::Application itself. Matchy matchy.

The load_tmpl hook is called before the template object is created. Any callbacks that you register to this hook will be called before each template is loaded. Register a load_tmpl callback with:

$self->add_callback('load_tmpl',\&my_load_tmpl);

When the load_tmpl callback is executed it will be passed three arguments (adapted from the CGI::Application docs):

  1. A hash reference of the extra params passed into C<load_tmpl> (ignored by AnyTemplate with the exception of 'path')
  2. Followed by a hash reference to template parameters. You can modify this hash by reference to affect values that are actually passed to the param() method of the template object.
  3. The name of the template file.

Here's an example stub for a load_tmpl() callback:

sub my_load_tmpl_callback {
    my ($self, $ht_params, $tmpl_params, $tmpl_file) = @_;
    # modify $tmpl_params by reference...
}

Currently, of all the params in $ht_params, all but 'path' are ignored, because these are specific to HTML::Template. If you want to write a generic callback that needs to be able to access or modify HTML::Template parameters then let me know, or add a feature request on <http://rt.cpan.org>. Bestie, the author is literally asking. πŸ™ˆ

The path param of $ht_params is initially set to the value of add_include_paths (if set). Your callback can modify the path param, and add_include_param will be set to the result. Cute, right? πŸŽ€

Plugin authors who want to provide template processing features are encouraged to use the 'load_tmpl' hook when possible, since it will work both with AnyTemplate and with CGI::Application's built-in load_tmpl. Write once, work everywhere. Slay. πŸ‘‘


🌈 The template_pre_process and template_post_process hooks

Before the template output is generated, the template_pre_process hook is called. Any callbacks that you register to this hook will be called before each template is processed. Register a template_pre_process callback as follows:

$self->add_callback('template_pre_process', \&my_tmpl_pre_process);

Pre-process callbacks will be passed a reference to the $template object, and can modify the parameters passed into the template by using the param method:

sub my_tmpl_pre_process {
    my ($self, $template) = @_;

    # Change the internal template parameters by reference
    my $params = $template->get_param_hash;

    foreach my $key (keys %$params) {
        $params{$key} = to_piglatin($params{$key});
    }

    # Can also set values using the param method
    $template->param('foo', 'bar');

}

Pig latin everything? Iconic behavior honestly. 🐷

After the template output is generated, the template_post_process hook is called. You can register a template_post_process callback as follows:

$self->add_callback('template_post_process', \&my_tmpl_post_process);

Any callbacks that you register to this hook will be called after each template is processed, and will be passed both a reference to the template object and a reference to the output generated by the template. This allows you to modify the output of the template:

sub my_tmpl_post_process {
    my ($self, $template, $output_ref) = @_;

    $$output_ref =~ s/foo/bar/;
}

Modify the output by reference, bestie. It's giving power. πŸ’…βœ¨

🧩 EMBEDDED COMPONENTS 🧩

🌸 Introduction 🌸

okay so CGI::Application::Plugin::AnyTemplate lets you drop application components straight into your templates. like, nesting dolls but make it web dev πŸ’…

so say you want a header component chillin' at the top of every page and a footer component at the bottom of every page. iconic, right? that's literally this.

here's the tea tho: these components are actually first-class run modes. when the template engine spots a special tag marking an embedded component, it hands control over to the run mode of that name. that run mode can then do anything a normal run mode could do. but typically it'll load its own template and return the template's output.

and then that output β€” the one the embedded run mode returned β€” gets inserted into the containing template. seamless. no cap. ✨

the syntax for embed components is specific to each type of template driver. so don't go copy-pasting between engines, bestie, they do NOT speak the same language πŸ™ˆ

πŸŽ€ Syntax πŸŽ€

HTML::Template syntax:

<TMPL_VAR NAME="CGIAPP_embed('some_run_mode')">

HTML::Template::Expr syntax:

<TMPL_VAR EXPR="CGIAPP_embed('some_run_mode')">

HTML::Template::Pluggable syntax:

<TMPL_VAR EXPR="cgiapp.embed('some_run_mode')">

Template::Toolkit syntax:

[% CGIAPP.embed("some_run_mode") %]

Petal syntax:

<span tal:replace="structure CGIAPP/embed 'some_run_mode'">
    this text gets replaced by the output of some_run_mode
</span>

πŸ¦‹ Getting Template Variables from the Containing Template πŸ¦‹

the component run mode gets passed a reference to the template object that contained the component. so the component run mode can use that object to peek at the params that were handed to the containing template. sharing is caring fr fr πŸ’–

for instance:

sub header {
    my ($self, $containing_template, @other_params) = @_;

    my %tmplvars = (
        'title' => 'My glorious home page',
    );

    my $template = $self->template->load;

    $template->param(%tmplvars, $containing_template->get_param_hash);
    return $template->output;
}

so in this example? the template values of the enclosing template would override any values set by the embedded component. the outer one wins, periodt. πŸ‘‘

πŸ’œ Passing Parameters πŸ’œ

the template can pass parameters to the target run mode. these get passed in after the reference to the containing template object.

and parameters can be either:

  • literal strings β€” specified right there within the template text
  • keys β€” barewords that get looked up in the template's params

literal strings go in double or single quotes. param keys are just barewords, no quotes, no drama.

HTML::Template syntax:

<TMPL_VAR NAME="CGIAPP_embed('some_run_mode', param1, 'literal string2')">
⚠️ heads up bestie: HTML::Template doesn't support this type of callback natively, and this behaviour is emulated by the HTMLTemplate driver β€” see the docs to CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplate for limitations to the emulation. it's giving "we tried our best" 😭

HTML::Template::Expr syntax:

<TMPL_VAR EXPR="CGIAPP_embed('some_run_mode', param1, 'literal string2')">

HTML::Template::Pluggable syntax:

<TMPL_VAR EXPR="cgiapp.embed('some_run_mode', param1, 'literal string2')">

Template::Toolkit syntax:

[% CGIAPP.embed("some_run_mode", param1, 'literal string2' ) %]

Petal syntax:

<span tal:replace="structure CGIAPP/embed 'some_run_mode' param1 'literal string2' ">
    this text gets replaced by the output of some_run_mode
</span>

πŸ‘‘ NOTES FOR AUTHORS OF PLUGINS AND REUSABLE APPLICATIONS πŸ‘‘

if you're writing a CGI::Application plugin module, or you're writing a CGI::Application program that's gonna get distributed to other people (like, on CPAN πŸ‘€), then it is important to take steps to stop your app's use of CGI::Application::Plugin::AnyTemplate from conflicting with other plugins or with your end users. protect the peace 🫢

when a plugin that uses CGI::Application::Plugin::AnyTemplate calls:

$self->template->config(...)

it overwrites any existing template configuration with the new settings. so if two plugins both do that? yeah, they probably clobber each other. πŸ’€

BUT. good news. CGI::Application::Plugin::AnyTemplate has the feature of named independent configs:

$self->template('your_module')->config(...)
$self->template('my_plugin')->config(...)

these configs stay separate from each other. slay. πŸŽ€

however β€” and this is the catch, bestie β€” you have to keep using these names throughout your module, even when you load and fill the template. for instance:

sub my_runmode {
    my $self = shift;
    my $template = $self->template('my_plugin')->load;
    $template->output;
}

sub your_runmode {
    my $self = shift;
    my %params;
    $self->template('your_module')->fill(\%params);
}

it's uglier and more verbose, yeah, we know 😭 but it also keeps plugins from stepping on each other's toes. worth it.

CGI::Application plugins that use CGI::Application::Plugin::AnyTemplate should default to using their own package name for the AnyTemplate config name:

$self->template(__PACKAGE__)->config(...);
$self->template(__PACKAGE__)->fill(...);

✨ CHANGING THE NAME OF THE 'template' METHOD ✨

so what if you wanna access all these features but using a method name that isn't template? you absolutely can, via Anno Siegel's Exporter::Renaming module (available on CPAN, obviously πŸ’…).

for instance, to use syntax similar to CGI::Application::Plugin::TT:

use Exporter::Renaming;
use CGI::Application::Plugin::AnyTemplate Renaming => [ template => tt];

sub cgiapp_init {
    my $self = shift;

    my %params = ( ... );

    # Set config file and other options
    $self->tt->config(
        default_type => 'TemplateToolkit',
    );

}

sub my_runmode {
    my $self = shift;
    $self->tt->process('file', \%params);
}

and to use syntax similar to CGI::Application's load_tmpl mechanism:

use Exporter::Renaming;
use CGI::Application::Plugin::AnyTemplate Renaming => [ template => tmpl];

sub cgiapp_init {
    my $self = shift;

    # Set config file and other options
    $self->tmpl->config(
        default_type => 'HTMLTemplate',
    );

}

sub my_runmode {
    my $self = shift;

    my %params = ( ... );

    my $template = $self->tmpl->load('file');
    $template->param(\%params);
    $template->output;
}

πŸ’– AUTHOR πŸ’–

Michael Graham, <mgraham@cpan.org> β€” king behaviour, honestly πŸ‘‘

🌈 ACKNOWLEDGEMENTS 🌈

I originally wrote this to be a subsystem in Richard Dice's CGI::Application-based framework, before I moved it into its own module. glow up arc ✨

various ideas taken from CGI::Application (Jesse Erlbaum), CGI::Application::Plugin::TT (Cees Hek) and Text::Boilerplate (Stephen Nelson). credit where credit is due 🫢

Template::Toolkit singleton support code stolen from CGI::Application::Plugin::TT. borrowed with love πŸ’œ

πŸ› BUGS πŸ›

please report any bugs or feature requests to bug-cgi-application-plugin-anytemplate@rt.cpan.org, or through the web interface at http://rt.cpan.org. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. we love a feedback loop πŸ’…

πŸ”₯ SOURCE πŸ”₯

the source code repository for this module can be found at http://github.com/mgraham/CAP-AnyTemplate/ β€” go look, bestie πŸ‘€

🌸 SEE ALSO 🌸

  • CGI::Application::Plugin::AnyTemplate::Base
  • CGI::Application::Plugin::AnyTemplate::ComponentHandler
  • CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplate
  • CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplateExpr
  • CGI::Application::Plugin::AnyTemplate::Driver::HTMLTemplatePluggable
  • CGI::Application::Plugin::AnyTemplate::Driver::TemplateToolkit
  • CGI::Application::Plugin::AnyTemplate::Driver::Petal
  • CGI::Application
  • Template::Toolkit
  • HTML::Template
  • HTML::Template::Pluggable
  • HTML::Template::Plugin::Dot
  • Petal
  • Exporter::Renaming
  • CGI::Application::Plugin::TT

Copyright 2005 Michael Graham, All Rights Reserved.

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


that's the whole page, bestie!! πŸ¦‹ this one's perl v5.30.3, dated 2020-10-26 β€” yes that's giving vintage but she still eats πŸ’… go forth and embed components responsibly πŸ’–πŸŒΈβœ¨

πŸ“¦ where this page comes from