Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Ordering Template cards

by Bod (Parson)
on Jan 24, 2021 at 16:03 UTC ( [id://11127369]=perlquestion: print w/replies, xml ) Need Help??

Bod has asked for the wisdom of the Perl Monks concerning the following question:

An internal system for business displays a series of 'cards' which show key data across various parts of the business all in one place. These are quite diverse and cover things like property occupancy levels, future pricing data and call centre call volume.

Currently this is implemented with a bespoke kind of template. There is an HTML file with placeholders for all the data items and a Perl script which gathers all the data from various systems across the business, reads the file, substitutes that data into the placeholders before displaying the output. It can be viewed at any time as a webpage but also runs twice per week from CRON and sends an email to key people. The script knows the difference by checking if $ENV{'GATEWAY_INTERFACE'} is defined.

This system already has 106 placeholders and needs extra information adding to it and I've decided to take the opportunity to refactor it to use Template thanks to the good influence of the Monastery! As part of the refactoring I want to add the facility for different users to be able to view the system with the cards in an order to suit them. Perhaps even to be able to hide the ones that do not interest them. We operate an open information policy so everyone in the business is permitted to see everything so there are no permission issues but not everything is actually useful to everyone so it would be good if they could put the cards they use most at the top and ones they seldom use further down.

In trying to work out how to implement this I have come up with a solution but it seems there must be a more elegant solution.

I've considered having a database table consisting of 4 fields:

 - User_idUser      - Foriegn Key to primary of User table
 - Card_idCard      - Foreign Key to primary of Card table
 - metric           - order to display cards for user
 - visible          - boolean - show card to user?
with User_idUser and Card_idCard being the composite primary key.

Then have a Perl script (of course!) that reads the cards from the database in the order given by metric. For each card it calls a subroutine that assembles the appropriate data for that card and uses Template to display the template file for that card. There will need to be 12 template files plus one for header and footers. Something like this (untested):

my $cards = $dbh->prepare("SELECT Card_idCard FROM CardList WHERE User +_idUser = $user_number AND visible = 1 ORDER BY metric"); $cards->execute(); while (my $card = $cards->fetchrow_array) { card_call_center() if $card == 1; card_price_data() if $card == 2; card_occupancy() if $card == 3; # etc etc } # ... sub card_call_center { # Collect data # from systems my $vars = { 'foo' => bar, 'some' => data, }; $template->process('card_call_center.tt', $vars); }
But I especially do not like the conditional subroutine calls with magic numbers in the statement modifiers.

Is there a more elegant solution?
Should I be calling multiple template files from the script or should that processing be done within Template?

Replies are listed 'Best First'.
Re: Ordering Template cards
by kcott (Archbishop) on Jan 25, 2021 at 01:00 UTC

    G'day Bod,

    "But I especially do not like the conditional subroutine calls with magic numbers in the statement modifiers."

    As far as the magic numbers are concerned, you can associate them with meaningful names; however, I really wanted to comment on the "conditional subroutine calls". Consider using a despatch table. Here's a rough example.

    my %handler_for = ( card1name => \&card_call_center, card2name => \&card_price_data, ... ); ... while (my $card = ...) { $handler_for{$card}->(); }

    — Ken

      Consider using a despatch table

      Fabulous - thanks Ken and ++

      That's exactly the kind of "more elegant solution" I was looking for. It is clearer, more extensible and much nicer than my solution.

Re: Ordering Template cards
by 1nickt (Canon) on Jan 24, 2021 at 16:46 UTC

    Hi,

    "Should I be calling multiple template files from the script or should that processing be done within Template?"

    Seems to me from what you've shown that for each rendering of the page, you will have an array of cards that you have pulled from the DB for the given user. If that's the case then I would use a [% FOREACH card IN cards %] ... [% END %] block in the template and just show 'em in the order given. See http://www.template-toolkit.org/docs/manual/Directives.html#section_Loop_Processing.

    Hope this helps!


    The way forward always starts with a minimal test.
      I would use a [% FOREACH card IN cards %] ... [% END %] block in the template and just show 'em in the order given

      I do exactly that when each card is a similar layout and it's really just a case of substituting in values. But here, every card is totally different. Some have tables, others just a heading a line of text whereas others have a graph. Hence why I was thinking of a separate template file for each card.

        If the cards are that different, I would keep them as separate files. This will make it easier to make changes to limited parts of the cards, and will maybe even encourage you to copy a template file for a slightly (but enough) different new card.

        As switching to a different card means a reload (I guess), keeping the different cards as different templates makes sense.

        Hm, so maybe the card hash in the array cards has a field card.data and a field card.template, and then inside the loop you process the template with the data using INCLUDE? You can reference data in variables using a sigil in nested directives for this.

        Hope this helps!


        The way forward always starts with a minimal test.
[OT] LaTeX for PDF? Re: Ordering Template cards
by bliako (Monsignor) on Jan 24, 2021 at 20:41 UTC

    Hi, this is irrelevant to your questions but I thought I mention one of the many ways to produce PDF output from your cards, e.g. for attaching to these email reports. This is of course LaTeX. It produces exceptional PDF/PS documents using a simple html-like (ok ok!) language which loves to be template-ed. You first need to install latex (TexLive will be called in your package manager) on your webserver. Then there are some Template plugins for it, e.g. Template::Latex (also Template::Plugin::Latex) and you have a Template-like interface to producing PDF files. The caveat is that you must now maintain both HTML and LaTeX templates. The latter is a pleasure though...

    If there was a Nobel for Computer Programs, more than one would have been awarded to the humble, open-source and free TeX/LaTeX - created by a true scientist with an eye for aesthetics and detail.

    Apropos your multiple templates dilemma. I would go with as many templates as possible if that would not hinder performance. For example, you can find out that card_call_center and card_sales have some common parts, let's say customer address. Well that part can be templated and re-used. Again, I don't know what effect this ultra-fine templating will have on performance.

    bw, bliako

      Thanks bliako, I shall look at LaTeX as I have had need to dynamically generate PDFs in the past. They are not needed for this application but no doubt I'll need them again one day.

      Apropos your multiple templates dilemma. I would go with as many templates as possible if that would not hinder performance.

      Performance really is not an issue here. The process of collecting together the information is quite slow as some of it calls web APIs. It is an ultra low traffic web page and, as they are done by CRON in the early hours of the morning, creating the emails is not something where time is important.

      It was more the block of conditional subroutines that seemed to be a rather inelegant way of doing things.

Re: Ordering Template cards
by LanX (Saint) on Jan 25, 2021 at 21:04 UTC

      In the original question I did put it was untested and showed the query as:

      y $cards = $dbh->prepare("SELECT Card_idCard FROM CardList WHERE User_ +idUser = $user_number AND visible = 1 ORDER BY metric");
      But you are right to point out this undefined behaviour. However, when written as you suggest, it always returns true (I haven't completely got my head around why) and the loop doesn't end. It needs to be written like this:
      my $card; while (($card) = $cards->fetchrow_array)

        I haven't completely got my head around why

        In while (my ($card) = $cards->fetchrow_array), the boolean condition being evaluated is the return value of the list assignment ()=..., and as per Assignment Operators, "a list assignment in scalar context returns the number of elements produced by the expression on the right hand side of the assignment."

        However, like LanX, I can't reproduce this, and you'd have to show an SSCCE that does. Are you sure you didn't accidentally write fetchrow_arrayref instead? That method returns undef if no more rows remain - and as per the above, it's one value being assigned, so the return value of the list assignment is 1 (true). For fetchrow_arrayref, the correct loop condition is while (my $card = $cards->fetchrow_arrayref), since that's a scalar assignment and it'll return undef (false).

        use warnings; use strict; use Data::Dumper; use DBI; my $dbh = DBI->connect("dbi:SQLite:dbname=:memory:", undef, undef, { RaiseError=>1, AutoCommit=>1 } ); $dbh->do(q{ CREATE TABLE foobar ( foo TEXT, bar TEXT ) }); my $in = $dbh->prepare(q{INSERT INTO foobar (foo,bar) VALUES (?,?)}); $in->execute('a','b'); $in->execute('c','d'); my $cards = $dbh->prepare('SELECT * FROM foobar'); $cards->execute; while (my ($card) = $cards->fetchrow_array) { # OR #while (my $card = $cards->fetchrow_arrayref) { # BUT NOT #while (my ($card) = $cards->fetchrow_arrayref) { print Dumper($card); }
        I have encountered similar problems before and I'm not sure either.
        Perl has some complicated rules about "definedness" and creating "my" variables within looping statements.
        I hope the Monks can further explain this seeming "weirdness".

        As a suggestion, I most often use fetchall_arrayref.
        Something like:

        my $card_ref = $cards->fetchall_arrayref; foreach my $card (@$card_ref) {..}
        This does expand the memory used by Perl to include the entire result set. But that is fine if the result set is "small" and the memory used for that result is recycled for further use by Perl. "Small" is a relative term. Nowadays, I would consider a 10,000 line result set as "small".
        > However, when written as you suggest, it always returns true

        That's hard to believe, it's a very common construct.

        And a little test in the debugger doesn't show any endless loop.

        DB<2> while ( my ($x) = () ) {die $x} DB<3>

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11127369]
Approved by Discipulus
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (4)
As of 2024-04-24 05:16 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found