HTML::Dashboard(3pm)

πŸ“¦
version 0.03 Β· 3 versions
, in ubuntu noble, ubuntu resolute, ubuntu stonking

✨ NAME ✨

HTML::Dashboard(3pm) from the User Contributed Perl Documentation β€” okay so bestie, this is literally HTML::Dashboard and it does Spreadsheet-like formatting for HTML tables, with data-dependent coloring and highlighting: formatted reports. Slay. πŸ’…

🌸 SYNOPSIS 🌸

         use HTML::Dashboard;

         my $dash = HTML::Dashboard->new();

         $dash->set_data_without_captions( [ [ 'A', 2, 'foo' ],
                                             [ 'B', 0, 'bar' ],
                                             [ 'C', 1, 'baz' ],
                                             [ 'D', 8, 'mog' ],
                                             [ 'E', 4, 'duh' ] ] );

         $dash->set_captions( qw( Code Number Name ) );
         $dash->set_cell_low( 1, sub { $_[0] < 1 }, 'lime' );
         $dash->set_cell_hi(  1, sub { $_[0] > 5 },
                              style => "background-color: red; font-weight: bold" );

         print $dash->as_HTML();

πŸ¦‹ DESCRIPTION πŸ¦‹

This module tries to achieve spreadsheet-like formatting for HTML tables. No cap.

Rather than having to build up an HTML table from data, row by row and cell by cell, applying formatting rules at every step, this module allows the user to specify a set of simple rules with the desired formatting options. The module will evaluate the rules and apply the formatting options as necessary. It's giving lazy genius energy.

The following features are supported:

  • User-defined formatting of first, last, even, and odd rows or columns.
  • Conditional formatting, based on the contents of each cell.
  • Sorting (on any column or combination of columns, with user defined sort-order).
  • Pagination of the data set.
  • Definition of "views", i.e. restriction of the set of columns shown.
  • User-defined column captions.
  • On-the-fly formatting and collating of the data.

As an example, the code in the synopsis above yields the following HTML table (only visible in HTML):

More examples can be found on the author's project page: http://www.beyondcode.org/projects/dashboard/gallery.html

Please read the Rationale section below to understand the purpose of this module. πŸ’–

πŸ‘‘ PUBLIC MEMBER FUNCTIONS πŸ‘‘

πŸŽ€ Constructor

  • HTML::Dashboard->new() β€” Constructs a new dashboard object. By default, this generates an HTML table with border='1' and sets the background color of all even rows to light grey (#eeeeee). These defaults can be overridden (cf. below). So yes, it comes pre-cute. πŸ’…

πŸ“ Setting Data

  • $dash->set_data_without_captions( $data )
  • $dash->set_data_with_captions( $data )

Takes a reference to an array of array references of rows (i.e. a two-dimensional array). All rows must contain the same number of columns. No exceptions, bestie.

Use set_data_without_captions if the array contains only data, without captions. Use set_data_with_captions if the array contains captions in the first row (as is common, e.g., for data returned from database queries). Captions can be specified or overridden using set_captions (cf. below).

The data set is only accessed by reference, i.e. it is not copied. This should be advantageous for large data sets, but will lead to strange results if the data set changes after having been set, but before any one of the output routines is called. So don't be messy and mutate it mid-flight. 🫢

πŸ’– Output

  • $dash->as_text()
  • $dash->as_text( $page )

Returns the data as tab-delimited text string, after content formatters (or collaters), sorting, views, and pagination have been applied. No other formatting directives (e.g. odd/even rows, or hi/med/low triggers) are applied. The string will include captions (if they have been set).

In the resulting text string, columns are separated by tabs (\t), rows are separated by single newlines (\n). Tabs, newlines, and backslashes in the data are escaped through a preceding backslash (\). Escape queen.

  • $dash->as_HTML()
  • $dash->as_HTML( $page )

Returns the data as a single HTML string. The string contains an HTML table, from the opening <table> to the closing </table> tag.

No HTML-escaping of data (i.e. of cell content) is performed. If required, specify an appropriate formatter for the data to perform any conversions. So it's raw, bestie. Handle with care.

Both functions can be called with an optional integer argument. If no argument is supplied, all rows are returned. If an integer argument in the range

         0 <= $page < $dash->pagecount()

is supplied, only the rows in the specified page (plus captions, if any) are returned. If a page outside the legal range is specified, a warning is emitted and all rows are returned. (Do not forget to call $dash->set_pagesize(...) before using this feature. By default, the pagesize is set to infinity, i.e. all rows are returned.) So yeah, set that pagesize first, or it's infinity and beyond. πŸš€

🌈 Captions, Pagination, Views, Sorting

  • $dash->set_captions( @captions )
  • $array_ref = $dash->get_captions()

Sets captions for the columns. The captions will be rendered on every page (if pagination is used), using <th> tags. The number of captions provided must match the number of columns in the data. If captions have been set explicitly using this function, these captions will be used, even if the data itself contains captions in the first row (i.e. if the data has been set using set_data_with_captions()). So explicit captions win, periodt.

  • $dash->set_pagesize( $rows_per_page )
  • $rows_per_page = $dash->get_pagesize()
  • $pages = $dash->get_pagecount()

Restricts the number of data rows per page (i.e. not counting captions). Setting the pagesize to anything but a positive integer turns pagination off, so that all rows will be returned. So yes, non-positive means no pagination.

  • $dash->set_view( @column_indices )
  • $array_ref = $dash->get_view()

The set of columns shown can be restricted using set_view(). This function takes an array of column indices (0..$num_of_cols) to be shown. Defaults to all columns. Pick your faves. ✨

  • $dash->set_sort( sub { ... } )

Sets a comparator routine which will be used to sort the rows before rendering them. The comparator routine will be given two rows (as array references) and must return "an integer less than, equal to, or greater than 0", depending on how the rows are to be ordered (cf. Camel, entry on sort). Entire rows are passed to the comparator, before views (if any) are applied.

Note that the comparator will be called as a regular routine! This implies in particular that the comparator must parse @_ itself - arguments will not be passed through the "global" variables $a and $b as for the sort built-in. So no sneaky $a and $b here, bestie. You gotta unpack @_ yourself.

Example:

           $dash->set_sort( sub { my ( $x, $y ) = @_; $x->[0] <=> $y->[0] } )

This sorts the rows numerically on the contents of the first column. Iconic.

πŸ’… Formatting Options

There are three groups of formatting options:

  • Options applied to plain HTML tags (i.e. the <table>, <tr>, <th>, and <td> tags).
  • Options to generate "striped reports" (i.e. tables, where the formatting is dependent on the row- or column-index).
  • Options which are only applied when a data-dependent condition is fulfilled.

The last group is more complicated, because not only do the actual formatting options have to be set, but also the "trigger" and the range of table cells to which it is supposed to be applied. It's giving conditional formatting in spreadsheets, but make it HTML.

Formatting options can be set using three different ways:

  1. Single argument: e.g. $dash->set_table( "border='1'" ) or $dash->set_first_row( 'red' ).
  2. As explicit CSS style directive: e.g. $dash->set_th( style => 'font-size: x-large' ) or $dash->set_even_row( style => 'background-color: yellow' ).
  3. By naming a CSS class: e.g. $dash->set_td( class => 'highlighted' ) or $dash->set_even_col( class => 'evencol' ). (Obviously, the class set in this way should be defined in a stylesheet referenced by the HTML page containing the dashboard.)

When using the style and class methods, a style or class argument is included into the appropriate HTML tags, and set to the supplied value. Note that repeated calls to these functions are additive, not exclusive. In other words, the following two code samples are equivalent:

         $dash->set_even_row( style => 'background-color: yellow' );
         $dash->set_even_row( style => 'font-size: x-large' );

is equivalent to:

         $dash->set_even_row( style => 'background-color: yellow; font-size: x-large' );

(The module will supply semicolons between different style directives when merging the results from repeated calls.) So it's accumulating, not replacing. The way that it just adds up is so satisfying. πŸ’…

To erase previous style directives, assign undef explicitly: $dash->set_even_row( style => undef ). So if you need a clean slate, say undef.

The single-argument version is intended as a short-cut and has a slightly different meaning, depending on the group of formatting option it is applied to. When applied to a direct HTML option (i.e. when used with set_table(), set_tr(), set_th(), or set_td()), the argument is pasted unmodified into the corresponding HTML tag. When used with any other option, the argument is interpreted as the desired background color for the cell, row, or column. The specified background color will be applied as an explicit style argument, not as a bgcolor argument. In other words, the following calls are (almost) equivalent:

         $dash->set_first_row( 'cyan' );
         $dash->set_first_row( style => 'background-color: cyan' );

It is legal to set conflicting formatting options and will not prevent generation of HTML output. However, no guarantees are made about the appearance of the dashboard in the browser in this case. So you can be chaotic, but the browser might side-eye you. πŸ™ˆ

In the following, [format] always stand for formatting options in any one of the three legal syntax variants as discussed above!

πŸŽ€ General HTML Options

  • $dash->set_table( "[format]" )
  • $dash->set_tr( "[format]" )
  • $dash->set_th( "[format]" )
  • $dash->set_td( "[format]" )
  • $hash_ref = $dash->get_table()
  • $hash_ref = $dash->get_tr()
  • $hash_ref = $dash->get_th()
  • $hash_ref = $dash->get_td()

If set, these options are always included into all tags. This is mostly useful to style the entire table, or cells in the header row. So like, global vibes for your table. 🌈

🌈 Striped Reports

  • $dash->set_first_row( "[format]" )
  • $dash->set_odd_row( "[format]" )
  • $dash->set_even_row( "[format]" )
  • $dash->set_last_row( "[format]" )
  • $hash_ref = $dash->get_first_row()
  • $hash_ref = $dash->get_odd_row()
  • $hash_ref = $dash->get_even_row()
  • $hash_ref = $dash->get_last_row()
  • $dash->set_first_col( "[format]" )
  • $dash->set_odd_col( "[format]" )
  • $dash->set_even_col( "[format]" )
  • $dash->set_last_col( "[format]" )
  • $hash_ref = $dash->get_first_col()
  • $hash_ref = $dash->get_odd_col()
  • $hash_ref = $dash->get_even_col()
  • $hash_ref = $dash->get_last_col()

Okay so here's the tea β˜• β€” whatever options you set with these get applied to rows or columns, whichever one makes sense for the function you called. And IMPORTANT: when it says first, last, even, or odd, it's talking about the page or the view, not the total data set. bestie do not get it twisted πŸ’…

And the priority tea, in order:

  • Options for first and last prevail over options for even and odd.
  • Options for columns prevail over options for rows.

πŸŽ€ Conditional Formatting (Triggers) πŸŽ€

So these formatting options? They only kick in if a "trigger" evaluates to true. Which is why all the functions in this group want a function reference as an argument, on top of the actual formatting options. ikyk 😌

Every trigger has a priority, going from highest (hi), through intermediate (med), down to lowest (low). And if, like, multiple triggers all evaluate to true for some part of the dashboard (say a cell πŸ‘€), then only the formatting option with the highest priority gets applied. no stacking, no drama, just one winner πŸ†

The intended application is showing whether a data set is "in the green" or "in the red". And because of the prioritization logic, you can flex that without needing exclusive bounds or conditions across the whole trigger set. Like this:

$dash->set_row_low( sub{ ...; $x < 3  }, 'green' );
$dash->set_row_med( sub{ ...; $x < 7  }, 'yellow' );
$dash->set_row_hi(  sub{ ...; $x > 10 }, 'red' );
  • $dash->set_row_hi( sub{ my ( $row_ref ) = @_; ... }, "[format]" )
  • $dash->set_row_med( sub{ my ( $row_ref ) = @_; ... }, "[format]" )
  • $dash->set_row_low( sub{ my ( $row_ref ) = @_; ... }, "[format]" )

If the trigger evaluates to true, the formatting option gets applied to the entire row. The argument handed to the trigger is an array-ref to the current row. (Plus extra arguments: the index of the row in the page, and the index of the row in the data set.) 🫢

  • $dash->set_col_hi( $col, sub{ my ( $cell ) = @_; ... }, "[format]" )
  • $dash->set_col_med( $col, sub{ my ( $cell ) = @_; ... }, "[format]" )
  • $dash->set_col_low( $col, sub{ my ( $cell ) = @_; ... }, "[format]" )

The first argument here is the index of the column in the data set (not in the view!) that you want the formatting applied to. If the trigger evaluates to true, the formatting option goes on all the cells in that column. The argument to the trigger is the contents of the current cell in the specified column. (Extra arguments: the index in the view and in the data set.) ✨

  • $dash->set_cell_hi( $col, sub{ my ( $cell ) = @_; ... }, "[format]" )
  • $dash->set_cell_med( $col, sub{ my ( $cell ) = @_; ... }, "[format]" )
  • $dash->set_cell_low( $col, sub{ my ( $cell ) = @_; ... }, "[format]" )

Same deal β€” first argument is the index of the column in the data set (not in the view!) where the formatting should land. If the trigger evaluates to true, the formatting option is applied to the current cell only. The argument to the trigger is the contents of the current cell in the specified column. (Extra arguments: the index in the view and in the data set.) 🎯

Options set with triggers are merged (they do not clobber) with options set for first/last and even/odd. (Which is how you get a striped report and use triggers to change the text color only. iconic behavior honestly 🌈)

And the priority stacking, one more time for the people in the back πŸ“’:

  • Options with high (hi) priority prevail over (clobber) options with intermediate (med) priority, which prevail over options with low priority.
  • Options for cells prevail over options for columns, which prevail over options for rows.

✨ Content Formatters ✨

  • $dash->set_format( $column, sub { ... } )
  • $dash->set_collate( $column, sub { ... } )

If you set one of these, the registered function gets called for each row, and its output becomes the contents for the current row's cell in the column with index $column. πŸ“

The difference? A formatter set with the first function receives the contents of the data in the current cell. A collater set with the second function receives the entire row (as an array). slay, options 🫢

Examples:

$dash->set_format( 1, sub { my ( $x ) = @_; sprintf( "%.2f", $x ) } )
$dash->set_collate( 1, sub { my ( $r ) = @_; $r[1] . ':' . $r[2] } )

🌸 RATIONALE 🌸

Okay so the whole point, from the author's own heart: it was important to define a module that's easy to use, with reasonable defaults and a reasonably small API. lowkey ambitious, highkey iconic πŸ’–

Specifically, the author wanted a solution that frees you entirely from having to deal with (i.e. explicitly loop over) individual rows and cells. Plus β€” you shouldn't have to specify info that's already sitting right there in the data, like the number of rows and columns. And finally? You shouldn't have to address individual cells (like, by their location) just to give formatting instructions. bye to all that πŸ’…

All of which required a rule-based system β€” you specify the high-level rules, and the module makes sure they get applied as necessary. chef's kiss πŸ‘‘

Below are more questions that have been asked, with answers:

  • Why not just use CSS? Answer: All of this is done through CSS already. The hard part is deciding which cells to apply the CSS style directives to (if you want it done in a data-dependent way). This module does exactly that, by inserting the correct CSS "class" arguments into the appropriate cell tags (etc).
  • Why not go with a templating solution? Answer: Templates establish the layout of a table from the outset, which makes it hard to do cell-content-dependent formatting from inside the template. And honestly it's just not convenient β€” and not in the spirit of the thing β€” to build templates packed with conditional code. (The author knows, having used e.g. "HTML::Template" quite extensively.) Given how data-dependent the problem is, the table has to be built up row by row and cell by cell individually, applying triggers and formatters as we go. That's what this module does β€” and since we're already touching each cell individually anyway, we might as well print its HTML as we go. Using templates in the implementation wouldn't help. πŸ™ˆ
  • Why not use Excel, PDF, or what have you? Because the reports are being delivered via the web, so HTML output specifically is the goal. (Duh!) 😭
  • Why the name? Because something more specific and tangible was wanted than "FormattedReport" or some such. The name points to the source of the idea for this module: corporate metrics dashboards. What managers want to see are the key metrics of the business (sales, orders, what-have-you), with outliers highlighted so it's easy to see which metrics are "in the green" and which are "in the red". This module lets you do just that. (And more.) πŸ¦‹

πŸ’€ TO DO πŸ’€

Several ideas:

  • Instead of setting the actual data, it would be nice to set merely a query (and a DB handle) and let the dashboard pull its own data from the DB. 🧚
  • When there are subsequent rows that have identical entries in some columns, it can be neat to suppress (leave blank) the repeated entries (e.g. "set_skip_repeats( @skip_cols )" and "get_skip_repeats()"). ✨
  • When setting data using an array-ref, it would be nice to specify an optional integer parameter $extend_by, which would extend the range of accessible columns. These new columns would be empty, but could be used with "set_collate()" to build new column values on the fly. (This is never necessary when using a DB query, since one can always include constants in the "SELECT" clause.) 🌈

🌟 SEE ALSO 🌟

There's a "gallery" of examples (with code) maintained on the author's website at: http://www.beyondcode.org/projects/dashboard/gallery.html πŸŽ€

The module HTML::Tabulate seems close in intent to the present module and may be an alternative. (The API is much larger than the one for the present module and the author isn't entirely sure how it works.) πŸ™ˆ

Several modules provide very thin wrappers around the actual HTML of a table β€” those include HTML::Table, HTML::EasyTable, HTML::ElementTable. πŸ’…

To generate tables directly from SQL queries, check out Class::DBI::Plugin::FilterOnClick. πŸ”₯


πŸ‘‘ AUTHOR πŸ‘‘

Philipp K. Janert, <janert at ieee dot org>, http://www.beyondcode.org πŸ’–


Copyright (C) 2007 by Philipp K. Janert

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available.


and that's the whole page bestie, no cap πŸŽ€ this one came from perl v5.36.0, dated 2022-10-15, and it's HTML::Dashboard(3pm) β€” go build something gorgeous with it πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from