htmlcssperltemplatescatalyst

How can I apply CSS file to specific template in my Catalyst webapp using a wrapper


I don't really understand how the wrapper works. I understood the example of the catalyst tutorial but I don't know how to apply specific CSS file for a specific template. Should I use [% IF %] statement in my wrapper.tt in order to select a specific template ? Do I call the CSS file with stash, like I do for a template in the controller ?

Some examples or hints would be great, thanks


Solution

  • You can certainly assign the CSS file to a stash variable in your controller.

    sub frobnicate :Local {
        my ($self, $c) = @_;
    
        # ...
    
        # this would probably be implied, i.e. in a properly configured Catalyst
        # you don't have to actually set this, it will just load the right thing
        $c->stash->{template} = 'frobnicate'; 
    
        # this one is for loading the right CSS
        $c->stash->{css_file} = 'frobnication.css';
    }
    

    And then in your TT wrapper, possibly wrapped in [% IF css_file %]...[% END %]:

    <head>
        <link rel="stylesheet" href="[% css_file %]">
    </head>
    

    That would work, but it is not good practice, because you are breaking the separation of concerns. The way the page looks should have nothing to do with your application controller.

    You could also just load each CSS file whenever it's needed, but that is bad practice too, because it will impact page load times and the order in which things are loaded. Typically one puts CSS at the top in the <head>, and most javascript files at the end of the page, just before the </body> so that there is already content rendered before the browser goes off and fetches and runs javascript.

    A more flexible, but also more complex solution is to write a method in your View that can be exposed as a TT vmethod to the Template Toolkit, and use it to add CSS files to the stash when needed. You can do that with expose_methods.

    package MyApp::View::TT; # or whatever you have called this
    
    # ...
    
    expose_methods => [qw/add_css_file/],
    
    # ...
    
    sub add_css_file {
        my ( $c, $self, $css_file ) = @_;
    
        push @{ $c->stash->{_css_files} }, $css_file;
    
        return;
    }
    

    Now you can use that in your template files. You can have a block at the very top or very bottom of each file to add CSS files to the list of files that should be loaded right where they belong to logically.

    <h1>Order Confirmation</h1>
    [% add_css_file('confirmation.css') %]
    

    In your wrapper, you can iterate that list of files and load each of them. As you can see this approach comes with the benefit of allowing you to have more than one file.

    <head>
      [% FOREACH file IN _css_files %]
        <link rel="stylesheet" href="[% file %]">
      [% END %]
    </head>
    

    They'll be available in the stash because the wrapper gets rendered after the inner part of the page, but you can't do it from the template directly, because you cannot change the stash within Template Toolkit. If there are no files, this will not do anything because the loop has nothing to iterate over.

    Note how I called the stash key _css_file. The underscore _ indicates that it's meant to be a private variable. Perl has no concept of private or public, but that's a convention to tell other developers not to mess with this.

    It would be advisable to now add a second method to the View to read the list and return it. That would decouple the implementation detail of how the list of files is stored completely from the template. You could change it entirely without having to touch the template files at all.

    If you have that exposed method, it would be smart to for example make sure that each file is only included once, e.g. with List::Util::uniq, or by using a hash and accessing the keys instead of going for an array.

    I originally used this approach for javascript files rather than CSS. For CSS in your application I actually believe it would be smarter to condense all the styles into one file, and minify it. Your users will download most of them anyway, so why have the overhead of loading multiple files and making each initial page load a little bit slower, and blowing up their cache, if you can just have the very first page load be a tiny bit longer and then read everything from cache?