Dec 15

(These posts are starting to follow a familiar pattern: “In Mason 1 we did X, but in Mason 2, Moose makes it so much easier…)”

Anyway.

In Mason 1, content wrapping (the practice of wrapping HTML in one or more surrounding templates) was achieved via autohandlers. Each autohandler would print its content and then call $m->call_next, which would pass control to the next component down the subclass chain. This was all special-purpose code written against Mason’s already hacky inheritance scheme.

But in Mason 2, Moose gives us a natural solution for this pattern: inner and augment.

At the start of a Mason 2 request, after determining the top-level component (aka “page component”), Mason will call that component’s render() method. By default, render() will just call main(), which outputs the main body of the component. However, each ancestor of the page component has an opportunity to wrap render() with its own template. For example:

# /Base.m - top-most template
#
<%augment render>
  <html><body>
      <% inner() %>
      <div class="footer">Copyright 2010 McHuffy Inc.</div>
    </body></html>
</%augment>

# /product/Base.m - product-specific template
#
<%augment render>
  <h2>Products</h2>
  <& product_nav.mi &>
  <div class="products_body">
    <% inner() %>
  </div>
</%augment>

# /product/sales/Base.pm - utility methods, no additional template
#
<%method some_utility_method>
   ...
</%method>

# /product/sales/display.m:
#
<h3><% $product->title %></h3>
...

The page /product/sales/display will be wrapped by two templates: the outer one
in /Base.pm, and the inner one in /product/Base.pm.

A couple of new things here for Mason 1 users:

  • ‘autohandler’ has changed to ‘Base.m’. A component of this name becomes the automatic superclass of every component in its directory and subdirectories.
  • There are sections for declaring Moose methods and method modifiers with embedded-Perl syntax: <%after>, <%around>, <%augment>, <%before>, and <%method>. (Why we “need” all these explicit sections is a subject for another post.)

Now here’s an implementation snag. Notice that neither /product/sales/display nor /product/sales/Base.pm have a render method. For the former, we want to automatically create a render that just calls main, which will display the main body of the component. For the latter, we want render to just fall through to the next level. But there’s no way to know in advance whether a component will be used as the page component or as one of its superclasses, so we have to create a default render for every component that does the right thing at runtime. In pseudo-code:

if (we are a superclass of the page component) {
    # Go to the next level.
    inner();
} else {
    # We are the page component. Draw the main content.
    $self->main();
}

Unfortunately there’s no official way to ask Moose whether we’re at the bottom of the inner() chain. So I’ve got a couple of choices here:

1. Check ref($self):

if ( ref($self) ne __PACKAGE__ ) {
    inner();
} else {
    $self->main();
}

As long as we provide a default render for every component, I think this should correctly distinguish the bottom of the chain. But I’m not entirely sure – Moose does a lot of with auto-generated subclasses.

2. Steal the code out of Moose::inner:

if ( $Moose::INNER_BODY ) {
    inner();
} else {
    $self->main();
}

Of course this is awful, as it relies on implementation details that are subject to change.

For now I’ll be going with #1.

4 Responses to “Mason 2: Content wrapping”

  1. Sebastian Willert Says:

    Let me get this out of the way: I’m totally impressed by your work on Mason2 … but relying on package name equality seems like a bad idea (at least on face-value, you will have thought about it much more than I have, thats a given).

    If I understand Moose correctly (and thats a big assumption, just ask rafl *g*) you can’t rely on package names for anything within Moose. Nearly every dynamic changes (run-time trait composition, metaclass manipulation a.s.o.) to a class will create an anonymous class and string equality will fall flat. At the very least, you should check if $self is a subclass of __PACKAGE__ but even this seems flaky.

    That said: I don’t know how much time I can put into this but as a long-time fan and user of Mason with some quite unusual (some would — and did — say insane) use-cases for Mason I’d really like to help out with this effort. Is there any group / IRC channel / mailing list / whatever I might join to get a feel for the project?

    Cheers,
    Sebastian

  2. Jonathan Swartz Says:

    > Relying on package name equality seems like a bad idea.

    Sebastian: I know, it makes me uneasy too. Checking if $self is a subclass won’t work, that will always be true anywhere in the superclass chain.

    Better perhaps to look at $self->comp_path, which is unique for each component and will not change in an autogenerated csublass.

    > I’d really like to help out with this effort. Is there any group / IRC channel / mailing list / whatever I might join to get a feel for the project?

    Great, can always use the help! There’s #mason on irc.perl.org, I’ll start hanging out there. Also will probably talk a fair bit about it on #moose.

  3. Robert Says:

    (These posts are starting to follow a familiar pattern: “In Mason 1 we did X, but in Mason 2, Moose makes it so much easier…)”
    ====

    That is because the Perl ecosystem has matured/evolved and your understanding has increased as well. :-)

  4. Joakim Says:

    Hi,

    I am trying to use mason2 together with catalyst. And i want to use the content wrapper funtionallity for a standard layout for all pages. But, is it some way to specify that a specific file should be the base. This would be really handy, because i in some cases would like to skip the bases for instance if i only want to send the content without the layout wrapper. Is this possible to achive in some way?

Leave a Reply

preload preload preload