NAME

HTML::Obj2HTML - Create HTML from a arrays and hashes

SYNOPSYS

use HTML::Obj2HTML (components => 'path/to/components', default_currency => 'GBP', mode => 'XHTML', warn_on_unknown_tag => 1, html_fromarrayref_format => 0)

Usage

set_opt('opt-name','value');
set_dictionary(\%dictionary);
add_dictionary_items(\%dictionary_items);
set_snippet('snippet-name', \@Obj2HTMLItems);
register_extension('element-name', \%definition);

$result_html = gen(\@Obj2HTMLItems);

An Obj2HTML arrayref is a structure that when processed is turned into HTML. Without HTML::FromArrayref format eanbled, it looks like this:

[
   doctype => "HTML",
   html => [
     head => [
       script => { ... },
     ],
     body => { class => 'someclass', _ => [
       h1 => "Test page",
       p => "This is my test page!"
     ]}
   ]
];

Builtin Features

Plugins

Plugins provide a way to extend what is understood as an element in the Obj2HTML structure. The format for creating extensions is as follows:

HTML::Obj2HTML::register_extension($element_name, \%definition, $type);

Plugin Definition

Ordering is important!

Here's an example of using before and after to produce tabs:

my @tabs = ();
my @content = ();
HTML::Obj2HTML::register_extension("tabsection", {
  tag => "",
  before => sub {
    my $obj = shift;
    @curtabs = ();
    @content = ();
    return HTML::Obj2HTML::gen($obj);
  },
  after => sub {
    my $obj = shift;
    my $divinner = {
      class => "ui tabular menu",
      _ => \@tabs
    };
    if (ref $obj eq "HASH") {
      foreach my $k (%{$obj}) {
        if (defined $divinner->{$k}) { $divinner->{$k} .= " ".$obj->{$k}; } else { $divinner->{$k} = $obj->{$k}; }
      }
      return HTML::Obj2HTML::gen([ div => $divinner, \@content ]);
    } else {
      return HTML::Obj2HTML::gen([ div => { class => "ui top attached tabular menu", _ => \@tabs }, \@content ]);
    }
  }
});
HTML::Obj2HTML::register_extension("tab", {
  tag => "",
  before => sub {
    my $obj = shift;
    if ($obj->{class}) { $obj->{class} .= " "; }
    if ($obj->{active}) { $obj->{class} .= "active "; }
    push(@tabs, div => { class => $obj->{class}."item", "data-tab" => $obj->{tab}, _ => $obj->{label} });
    push(@content, div => { class => $obj->{class}."ui bottom attached tab segment", "data-tab" => $obj->{tab}, _ => $obj->{content} });
    return "";
  }
});

Now all I need to do to generate tabs and contents is:

[
  tabsection => [
    tab => { active => 1, tab => "intro", label => "Introduction", content => "Here's my intro" },
    tab => { tab => "detail", label => "Detail", content => "And some content!" }
  ]
]

This produces a <div> containing the tabs themselves, then each individual tab content in it's own <div>. See the Semantic UI tabs examples for details!

WHY

Have you ever built a really complex web page, with many parts replicated with other pages, sections of page that should only be shown in some circumstances?

Do you get frustrated making edits to HTML to add or remove an element level, and needing to try to figure out where the end tag should go/needs to be removed from?

Then this module is for you. This module allows you to build up an HTML like page, using manipulatable array and hash refs, with added features like embedding conditionals and coderefs that will only be executed right at the last moment, while rendering the HTML. This allows true separation of controller and view!

One of my favorite features is defining a single view that contains a form, but changing the form into a readonly display of the data simply by performing a set_opt("readonly",1). And I don't just mean adding readonly to the form elements - I mean removing the form elements entirely and leaving just the values!

Benefits

1. Providing a more extensible way of parsing a perl objects into HTML objects, including being able to create framework specific "plugins" that broaden what you can do

2. Providing the option to provide the content from within an attributes hash. This simplifies parsing and allows you to do something like:

div => { class => "segment", _ => "Some text" }

div => { segment => [ "Some text" ] }

But you can tell this module to use the HTML::FromArrayref syntax, in which case you would need to do:

div => { class => "segment" }, "Some text"

This module is also aware of tags that should not have an end tag; you don't need to provide anything more than the element name

p => [ "My first paragraph", br, "The next line" ]

But you can of course still provide attributes:

hr => { class => "ui seperator" }

3. Providing extensions via plugins

Using HTML::Obj2HTML::register_extension you can define your own element and how it should be treated. It can be a simple substitution:

HTML::Obj2HTML::register_extension("line", {
    tag => "hr",
    attr => { class => "ui seperator" }
});

Therefore:

line => { class => "red" }

Would yield:

<hr class='ui seperator red' />

Or you can define "before" and "after" subroutines to be executed, which can return larger pieces of rat HTML or an HTML::Obj2HTML object to be processed.

4. Providing components. Via a plugin you can also create full compents in files that are execute as perl scripts. These can return HTML::Obj2HTML objects to be further processed.

All in all, this looks a feels a bit like React, but for Perl (and with vastly different syntax).

SEE ALSO

Previous attempts to do this same sort of thing:

How this is used in Dancer: Dancer2::Template::Obj2HTML

And a different way of routing based on the presence of files, which are processed as HTML::Obj2HTML objects if they return an arrayref: Dancer2::Plugin::DoFile