DEV Community

🌌 Sébastien Feugère ☔
🌌 Sébastien Feugère ☔

Posted on • Updated on

The spooky CSS in Perl toolkit

Some people are really going to love that: a spooky inline CSS helper for composing Outlook messages in a Perl application!

Most websites those days are SPA applications that render on the front-side, or a mixture of backend and frontend rendering. There is also this trend of CSS in JavaScript also knowns as JSS that is debatable for the simplest projects (makes everything overcomplicated), but in for more complex applications, can be justified and very useful.

Anyway, I use a lot of server side rendering. I mean, the old-school way you know, I serve HTML, CSS, and JavaScript separately.

A couple of years ago, I encountered one of those specific cases where it was useful to generate the CSS from some Perl code. We developed a crawler that helped us to interact with our in house project management API, so that we could send alerts to our colleagues when requirements had not been delivered and deadlines approached. Day minus 7, minus 3 and minus 1. And those colleagues used old versions of Outlook, so it was obligatory tables based emails, you know, those who have all kind of style and non-standard attributes all over the tags. It's actually not even standard CSS, it is Outlook specific style attributes in HTML:

  <table
    align="center"
    bgcolor="#FFFFFF"
    cellpadding="0"
    cellspacing="0"
    class="w600"
    width="600"
  >
Enter fullscreen mode Exit fullscreen mode

In such a situation, the tags and style are totally mixed. There is no separation of concerns any more.

There are tools (e.g. Foundation Emails) for managing these kinds of beautiful things, but the integration with the crawler was more complicated than developing our own.

I thought of the CSS in JS thing and decided to implement a quick helper to inject the styled tags in appropriate places. Note that it starts to be interesting because when you compose such things, you have the same values repeated from dozens to hundreds of tags. And believe me, I had colleagues who really needed this automated, they would be asked to do it by hand otherwise. That's how things worked, in this company, e.g. there were no SCM tools and each “programmer” worked on their own repository, that were “merged” every few years. Also, production had hundreds of duplicates of the same app, but not the way you think of. Anyway, I'm digressing.

What I wanted was a nicely structured application, with separation of concerns, templates, no duplicated code and easy to maintain, although in the end it was for producing spooky inline CSS. This is how we would generate the previous HTML:

%= t table => ( class => 'w600', inline 'header-table' ) => begin
    ...
 % end
Enter fullscreen mode Exit fullscreen mode

The basis of our toolkit is the Mojolicious::Plugin::Email::Style::CSS module that is strictly the same as the principle of CSS in
JS
except it is CSS in Perl (I can hear some people crying, but this thing got designed with Outlook 2016 in mind, sorry).

It contains a list of constants that can be extended and nested.

package Mojolicious::Plugin::Email::Style::CSS;
use Mojo::Base -base, -signatures;

use constant PALETTE => {
  INFO    => '#4ECA47',
  WARNING => '#F0964B',
  DANGER  => '#F52930',
  WHITE   => '#FFFFFF',
};

use constant {

  ALIGNCENTER => {
    style => 'text-align: center'
  },
  CELLZERO     => {
    cellpadding => '0',
    cellspacing => '0',
  },

  HEADER_COLOR => [
    { bgcolor => PALETTE->{ INFO }},
    { bgcolor => PALETTE->{ WARNING }},
    { bgcolor => PALETTE->{ DANGER }},
   ],
  MAIN_WIDTH   => 600,
};
Enter fullscreen mode Exit fullscreen mode

This is only a couple of constant hashes that are structured as a Sass file would be. I means: CSS with variables.

We also have a css subroutine that will take care of the “preprocessing”. It now looks even more like CSS.

sub css( $self ) {

  return {

    'align-center' => { ALIGNCENTER->%* },

    body => {
      leftmargin => '0',
      topmargin => '0',
      marginwidth => '0',
      marginheight => '0',
    },

    'top-table' => {
      bgcolor => PALETTE->{ WHITE },
      width => '100%',
      border => '0',
      CELLZERO->%*,
    },

    'content-table' => {
      align => 'center',
      bgcolor => PALETTE->{ WHITE },
      width => MAIN_WIDTH,
      CELLZERO->%*,
    },

    'header-table' => {
      border => '0',
      align => 'center',
      HEADER_COLOR->[ $self->alert_occurence - 1 ]->%*,
      width => MAIN_WIDTH,
      CELLZERO->%*,
      color => PALETTE->{ WHITE },
    },

    hello => {
      style => "color: #FFFFFF; " .
      "font-family: Arial, Gotham, 'Helvetica Neue', Helvetica,'sans-serif'; " .
      "font-size: 250%;"
    },

    'nbsp-top-margin' => {
      height => 17,
      style => "font-size: 17px; line-height: 17px"
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

The CSS.pm module also has an alert_occurence attribute so that we can send appropriate content and dynamically style the email depending on the emergency of the situation (day minus 7, 3, or 1):

use Mojo::Base -base, -signatures;

has 'alert_occurence';
Enter fullscreen mode Exit fullscreen mode

The CSS.pm module is registered as a inline helper in our Mojolicious::Plugin::Email::Style plugin, so that it can be consumed in Mojo templates:

use Mojo::Base 'Mojolicious::Plugin', -signatures;
use Mojolicious::Plugin::Email::Style::CSS;

sub register ( $self, $app, $conf ) {
  my $style = Mojolicious::Plugin::Email::Style::CSS->new;
    $app->helper(
      inline => sub ( $self, $key ) {
        return $style->css->{ $key }->%*;
    });
}
Enter fullscreen mode Exit fullscreen mode

The plugin is then loaded in a larger application:

package Email::Api;
use Mojo::Base 'Mojolicious', -signatures;

sub startup ( $app ) {
  # ...
  $app->plugin( 'Alerts::Api::Plugin::Style' );
  # ...
}

1;
Enter fullscreen mode Exit fullscreen mode

Templates

Here are the main templates used for the Outlook messages. This is the global layout:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns:v="urn:schemas-microsoft-com:vml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;" />
    <title>Alert</title>
    %= include 'layouts/style'
  </head>
  %= t body => ( inline 'body' ) => begin
    <%= content %>
  % end
</html>
Enter fullscreen mode Exit fullscreen mode

And here is how we use our inline helper:

% layout 'outlook';
% title 'Alert';
%= t table => ( inline 'top-table' ) => begin
  %= t tbody => begin
    %= t tr => begin
      %= t td => begin
        <!-- content ~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        %= t table => ( class => 'w600', inline 'content-table' ) => begin
          %= t tbody => begin
            <!-- header ~~~~~~~~~~~~~~~~~~~~~~~~~ -->
            %= t table => ( class => 'w600', inline 'header-table' ) => begin
              %= t tbody => begin
                %= t tr => begin
                  %= t td => ( align => 'center') => begin
                    <tbody>
                      <!-- top margin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
                      <tr>
                        %= t td => ( inline 'nbsp-top-margin' ) => begin
                          &nbsp;
                        % end
                      </tr>
                      <!-- logo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
                      %= include 'fragment/logo'
                      <tr>
                        %= t td => ( align => 'center', inline 'hello' ) => begin
                          %# include 'fragment/hi'
                        % end
                      </tr>
                    </tbody>
                  % end
                % end
              % end
            % end
          % end
        % end
      % end
    % end
  % end
% end
%#= include 'fragment/alert/first'

Enter fullscreen mode Exit fullscreen mode

Conclusion

The examples for this blog post are very simple. Such old-style Outlook emails can use very-very verbose attributes and be duplicated all over the place, especially if you generate content. You obviously don't want that to be edited by hand. My idea with this project was to avoid that someone had to edit the emails by hand, because this is how things were managed usually in this company.

I thought about releasing that on the CPAN in the Mojolicious::Pugin::* namespace, but I had difficulties evaluating if it was a useful thing, really.

Hope you liked that and that it was not too spooky.

References

Top comments (0)