Maypole

 view release on metacpan or  search on metacpan

lib/Maypole/Manual/BuySpy.pod  view on Meta::CPAN

    }

    1;

Here we're overriding the C<parse_path> method which takes the C<path>
slot from the request and populates the C<table>, C<action> and
C<args> slots. If the user has asked for a page we don't know
about, we ask the usual Maypole path handling method to give it a try;
this will become important later on. We turn the default page,
C<DesktopDefault.aspx>, into the equivalent of C</tab/view/1> unless
another C<tabid> or C<ItemID> is given in the query parameters; this allows us
to use the ASP.NET-style C<DesktopDefault.aspx?tabid=3> to select a tab.

Now we have to create our C<tab/view> template; the majority of
this is copied from the F<DesktopDefault.aspx> source, but our panes
look like this:

    <td id="LeftPane" Width="170">
        [% pane("LeftPane") %]
    </td>
    <td width="1">
    </td>
    <td id="ContentPane" Width="*">
        [% pane("ContentPane") %]
    </td>
    <td id="RightPane" Width="230">
        [% pane("RightPane") %]
    </td>
    <td width="10">
        &nbsp;
   </td>

The C<pane> macro has to be the Template Toolkit analogue of the Perl code
we used for our mock-up:

    [% MACRO pane(panename) BLOCK;
        FOR module = tab.modules("pane", panename);
            "<P>"; module; " - "; module.definition; "</P>";
        END;
    END;

Now, the way that the iBuySpy portal works is that each module has a
definition, and each definition contains a path to a template:
C<$module-E<gt>definition-E<gt>DesktopSrc> returns a path name for an C<ascx>
component file. All we need to do is convert those files from ASP to the
Template Toolkit, and have Maypole process each component for each module,
right?

=head2 Components and templates

Dead right, but it was here that I got too clever. I guess it was the word
"component" that set me off. I thought that since the page was made up of a
large number of different modules, all requiring their own set of objects, I
should use a separate Maypole sub-request for each one, as shown in the
"Component-based pages" recipe in the
L<Request Cookbook|Maypole::Manual::Request>.

So this is what I did. I created a method in C<Portal::Module> that would
set the template to the appropriate C<ascx> file:

    sub view_desktop :Exported {
        my ($self, $r) = @_;
        $r->template($r->objects->[0]->definition->DesktopSrc);
    }

and changed the C<pane> macro to fire off a sub-request for each module:

    [% MACRO pane(panename) BLOCK;
        FOR module = tab.modules("pane", panename);
            SET path = "/module/view_desktop/" _ module.id;
            request.component(path);
        END;
    END; %]

This did the right thing, and a call to C</module/view_desktop/12> would
look up the C<Html Document> module definition, find the C<DesktopSrc> to
be F<DesktopModules/HtmlModule.ascx>, and process module 12 with that
template. Once I had converted F<HtmlModule.ascx> to be a Template Toolkit
file (and we'll look at the conversion of the templates in a second) it
would display nicely on my portal.

Except it was all very slow; we were firing off a large number of Maypole
requests in series, so that each template could get at the objects it
needed. Requests were taking 5 seconds.

That's when it dawned on me that these templates don't actually need different
objects at all. The only object of interest that C</module/view_desktop> is
passing in is a C<module> object, and each template figures everything out by
accessor calls on that. But we already have a C<module> object, in our C<FOR>
loop - we're using it to make the component call, after all! Why not just
C<PROCESS> each template inside the loop directly?

    [% MACRO pane(panename) BLOCK;
        FOR module = tab.modules("pane", panename);
            SET src = module.definition.DesktopSrc;
            TRY;
                PROCESS $src;
            CATCH DEFAULT;
                "Bah, template $src broke on me!";
            END;
        END;
    END; %]

This worked somewhat better, and took request times from 5 seconds down
to acceptable sub-second levels again. I could take the C<view_desktop>
method out again; fewer lines of code to maintain is always good. Now
all that remained to do for the view side of the portal was to convert
our ASP templates over to something sensible.

=head2 ASP to Template Toolkit

They're all much of a muchness, these templating languages. Some of
them, though, are just a wee bit more verbose than others. For instance,
the banner template which appears in the header consists of 104 lines
of ASP code; most of those are to create the navigation bar of tabs
that we can view. Now I admit that we're slightly cheating at the moment
since we don't have the concept of a logged-in user and so we don't
distinguish between the tabs that anyone can see and those than only an
admin can see, but we'll come back to it later. Still, 104 lines, eh?

The actual tab list is presented here: (reformated slightly for sanity)

    <tr>
        <td>
            <asp:datalist id="tabs" cssclass="OtherTabsBg" 
 repeatdirection="horizontal" ItemStyle-Height="25" 
 SelectedItemStyle-CssClass="TabBg" ItemStyle-BorderWidth="1" 
 EnableViewState="false" runat="server">
                <ItemTemplate>
                    &nbsp;<a href='<%= Request.ApplicationPath %>/
 DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid=
 <%# ((TabStripDetails) Container.DataItem).TabId %>' class="OtherTabs">
 <%# ((TabStripDetails) Container.DataItem).TabName %></a>&nbsp;
                </ItemTemplate>
                <SelectedItemTemplate>
                    &nbsp;<span class="SelectedTab">
 <%# ((TabStripDetails) Container.DataItem).TabName %></span>&nbsp;
                </SelectedItemTemplate>
            </asp:datalist>
        </td>
    </tr>

But it has to be built up in some 22 lines of C# code which creates and
populates an array and then binds it to a template parameter. See those
C<E<lt>%#> and C<E<lt>%=> tags? They're the equivalent of our Template
Toolkit C<[% %]> tags. C<Request.ApplicationPath>? That's our C<base>
template argument. 

In our version we ask the portal what tabs it has, and display the
list directly, displaying the currently selected tab differently:

    <table id="Banner_tabs" class="OtherTabsBg" cellspacing="0" border="0">
        <tr>
    [% FOR a_tab = portal.tabs %]
        [% IF a_tab.id == tab.id %]
            <td class="TabBg" height="25">
                &nbsp;<span class="SelectedTab">[%tab.name%]</span>&nbsp;
        [% ELSE %]
            <td height="25">
                &nbsp;<a href='[%base%]DesktopDefault.aspx?tabid=[%a_tab.id%]' 
                class="OtherTabs">[%a_tab.name%]</a>&nbsp;
        [% END %]
            </td>
    [% END %]
        </tr>



( run in 0.828 second using v1.01-cache-2.11-cpan-e1769b4cff6 )