Loading ...
Sorry, an error occurred while loading the content.

Re: Templating suggestions?

Expand Messages
  • Joshua Chamas
    ... If you want some standard templatting system other than ASP style , then why not use another templatting framework like Template Toolkit
    Message 1 of 6 , Mar 21 4:32 PM
    • 0 Attachment
      eamondaly wrote:
      >
      > This is going to be a long post, so please bear with me.
      >
      > I'm implementing a new site in Apache::ASP. I'd like to develop a
      > framework similar to our existing mod_perl setup. The way we
      > currently create a page is like so:
      >
      > - Templater creates template.html
      > - Contains several tokens like __TITLE__, __HEADER__, __BODY__,
      > __LEFT_COLUMN__
      > - Some default variables are set: $var{'LEFT_COLUMN'},
      > $var{'FOOTER'}, etc.
      > - Also contains some perl logic
      >

      If you want some standard templatting system other than
      ASP style <%= $vars{TITLE} %>, then why not use another
      templatting framework like Template Toolkit or Embperl ?
      I would not invent yet another templatting language
      when there are so many to choose from. Please see:

      http://www.perl.com/pub/a/2001/08/21/templating.html

      for templatting comparisons.

      If you want to create an XMLSubs templatting system, you
      can do that within Apache::ASP like

      <var name='TITLE' />

      If you REALLY want to do __TITLE__, you could do this in
      global.asa...

      use vars qw(%vars);
      sub Script_OnStart {
      %vars = (); # init per request
      }

      sub Script_OnFlush {
      my $data = $Response->{BinaryRef};
      $$data =~ s/__(\w+)__/$vars{$1}/isge;
      }

      > - Developer creates foo.html
      > - Sets $var{'TEMPLATE'} to template.html
      > - foo.html (optionally) contains variables like $var{'TITLE'},
      > $var{'HEADER'}, and $var{'LEFT_COLUMN'}. These append to or
      > override those set in the template
      > - Everything else in foo.html is treated as $var{'BODY'}
      > - Also contains perl logic
      >

      I am not sure where you are going with $var{BODY}, but you might
      be more interested in using mod_perl handlers with a templatting
      system instead like Template Toolkit or HTML::Template.

      > When a request comes in for foo.html, our perl module scans it for
      > the template name, reads in and executes everything in template.html,
      > executes everything in foo.html, substitutes all tokens with their
      > values (__TOKEN__ with $var{'TOKEN'}), and spits out the result. Can
      > I mimic this behavior in Apache::ASP?
      >

      Script_OnFlush handler can be used for doing any post processing
      of output your heart desires. But weight carefully the difference
      between can & should in this regards.

      > I've been trying things along the lines of (in foo.html):
      >
      > <my:template href="/templates/simple.inc">
      > WHEE! <% print scalar localtime %>
      > </my:template>
      >
      > and then, in global.asa:
      >
      > sub my::template {
      > my($args, $html) = @_;
      > my $template = $Server->MapPath($args->{'href'});
      > my $template_ref = $Response->TrapInclude($template);
      > $$template_ref =~ s/__BODY__/$html/;
      > $main::Response->Write($$template_ref);
      > }
      >
      > which works, but obviously only gets me halfway there. Can I declare
      > variables like %var in foo.html that are accessible to the routine
      > my::template? I can't set these as globals, because they're not.
      >

      This is a pretty good use of XMLSubs to create your own
      mini template system. If $template is just an HTML template
      and you don't want HTML developers adding ASP bits, then you
      might just read in the file directly with

      if(-e $file) {
      open(FILE, $file) || die("can't open file: $!");
      my $data = join('', <FILE>);
      close FILE;
      }

      This way your templates are safe from ASP code blocks,
      and gives you greater control of the template system.
      In order to allow XMLSubs tags use, but not ASP code
      blocks, I have considered configs or params to
      the Include() calls like CodeBlocks => 0 as in

      PerlSetVar CodeBlocks 0

      or

      $Response->Include({ File => 1, CodeBlocks => 0 }, @args);

      which could still allow for XMLSubs while disabling <% %>
      in a script/template. One of the complaints of ASP is
      that is allows too much to be put into templates, and while
      I see the above would be nice to have, no one has yet NEEDED
      it, thus is has never been done.

      > I really don't want our developers coding pages like:
      >
      > <!--#include file="everything_up_to_the_title.inc">
      > This is my title!
      > <!--#include file="everything_from_title_to_left_column.inc">
      > This is my left column
      > <!--#include file="everything_from_left_column_to_header.inc">
      > ...

      I agree, this is not so good these days, but Apache::ASP supports
      it for SSI backwards compatibility.

      > I'm open to any and all suggestions!

      I have long wanted to build support for easier variables substitution
      where one could do this:

      PerlSetVar QuickVars 1

      sub Script_OnStart {
      $Vars->{DATA} = 1;
      }

      Then in the ASP script, PHP quick variables would be supported like:

      <% for(1..10) { %>
      $DATA
      <% } %>

      $DATA would be pulled from $Vars->{DATA} automatically at runtime.
      I think this would be a worthwhile extension to Apache::ASP since
      <%= $Vars->{DATA} %> type templates can be unwieldy for HTML developers.

      I would adopt a standard of $\w+ for regexp matching for QuickVars
      since this is the kind of thing supported in PHP & is therefore
      a widely used practice. As a PerlSetVar QuickVars 1 config, this would
      have the added benefit of not affecting those Apache::ASP users until
      they explicitly want this feature.

      --Josh
      _________________________________________________________________
      Joshua Chamas Chamas Enterprises Inc.
      NodeWorks Founder Huntington Beach, CA USA
      http://www.nodeworks.com 1-714-625-4051

      ---------------------------------------------------------------------
      To unsubscribe, e-mail: asp-unsubscribe@...
      For additional commands, e-mail: asp-help@...
    • eamondaly
      Joshua Chamas, you re my hero. The key, of course, was the Script_OnStart event. Declaring %var there made $var{ TITLE }, $var{ HEADER }, $var{ BODY }, etc.
      Message 2 of 6 , Mar 22 12:48 PM
      • 0 Attachment
        Joshua Chamas, you're my hero.

        The key, of course, was the Script_OnStart event. Declaring %var
        there made $var{'TITLE'}, $var{'HEADER'}, $var{'BODY'}, etc.
        available to the page, its template, and its includes. You're a
        life-saver!

        For those curious, we ended up with the following:

        global.asa contains:

        sub Script_OnStart {
        use vars qw(%var);
        %var = ();
        }

        sub Script_OnEnd {
        $var{'TITLE'} ||= $var{'HEADER'};
        my $template_ref = $Response->TrapInclude($var{'TEMPLATE'});
        $main::Response->Write($$template_ref);
        }

        sub my::header {
        shift;
        $var{'HEADER'} .= shift;
        }

        sub my::template {
        my $args = shift;
        $var{'TEMPLATE'} = $Server->MapPath($args->{'href'});
        }

        sub my::body {
        my $args = shift;
        $var{'BODY'} = shift;
        }

        A typical page, foo.html, contains:

        <my:template href="/templates/simple.html" />

        <my:header>
        Welcome back, <%= $Session->{'username'} %>!
        </my:header>

        <my:body>
        <!--#include file="foo.inc" -->
        </my:body>

        And simple.html contains all the pretty formatting like so:

        <html>
        <head><title><%= $var{'TITLE'} %></title></head>
        <body>
        <h1><%= $var{'HEADER'} %></h1>
        <%= $var{'BODY'} %>
        </body>
        </html>

        The really great thing is that code can be added anywhere: to
        foo.html, simple.html, to their includes, in the subs-- it's
        ridiculously flexible. Plus, it's pretty similar to our existing
        module, which means I won't have a tough time retraining my staff.

        > I have long wanted to build support for easier variables
        substitution
        > where one could do this:
        >
        > PerlSetVar QuickVars 1
        >
        > sub Script_OnStart {
        > $Vars->{DATA} = 1;
        > }
        >
        > Then in the ASP script, PHP quick variables would be supported like:
        >
        > <% for(1..10) { %>
        > $DATA
        > <% } %>
        >
        > $DATA would be pulled from $Vars->{DATA} automatically at runtime.
        > I think this would be a worthwhile extension to Apache::ASP since
        > <%= $Vars->{DATA} %> type templates can be unwieldy for HTML
        developers.

        That's pretty much exactly how we do it:

        s/\$(\w+)/(exists $var{$1}) ? $var{$1} : "\$$1" /seg;

        We also have some special hashes, like %input and %db, which simplify
        development:

        s/\$input{'(\w+)'}/(exists $input{$1}) ? $input{$1} : "\$$1" /seg;

        Seems like we could just pop that into Script_OnEnd.

        Anyway, thanks again, Joshua. This stuff is great.


        ---------------------------------------------------------------------
        To unsubscribe, e-mail: asp-unsubscribe@...
        For additional commands, e-mail: asp-help@...
      • Joshua Chamas
        ... Glad its working out! Looking at your solution, I think I would probably do something more like:
        Message 3 of 6 , Mar 22 5:50 PM
        • 0 Attachment
          > A typical page, foo.html, contains:
          >
          > <my:template href="/templates/simple.html" />
          >
          > <my:header>
          > Welcome back, <%= $Session->{'username'} %>!
          > </my:header>
          >
          > <my:body>
          > <!--#include file="foo.inc" -->
          > </my:body>
          >

          Glad its working out!

          Looking at your solution, I think I would probably do
          something more like:

          <my:template href="/templates/simple.html" />
          <my:header>Welcome back, <%= $Session->{'username'} %>!</my:header>
          <my:body>
          <!--#include file="foo.inc" -->
          </my:body>
          </my:template>

          This would allow you to not have to do post processing in
          the Script_OnEnd. With the my:template enclosing my:header
          and my:body, you would guarantee the execution of the
          my:header & my:body before my:template, so when my:template
          executes the %vars data would already be define.

          This is a more natural use of the XMLSubs in my view, where
          inner tags get executed before outer tags.

          The only drawback to this approach is that $Response->Flush
          is disabled during XMLSubs execution, so you would not
          be able to flush a long running report data out periodically
          if the report were wrapped up in a <my:template/> tag,
          but your current method suffers from this same problem
          I believe.

          > Anyway, thanks again, Joshua. This stuff is great.
          >

          I'm glad you are making use of all the hooks. Its really
          nice to hear of creative solutions to intricate problems
          like this.

          -- Josh
          _________________________________________________________________
          Joshua Chamas Chamas Enterprises Inc.
          NodeWorks Founder Huntington Beach, CA USA
          http://www.nodeworks.com 1-714-625-4051

          ---------------------------------------------------------------------
          To unsubscribe, e-mail: asp-unsubscribe@...
          For additional commands, e-mail: asp-help@...
        • eamondaly
          ... ... I agree. I ve changed my code to work like this. Of course, now I ve run into a new roadblock. Can $Response- Redirect be called from
          Message 4 of 6 , Mar 27 6:05 AM
          • 0 Attachment
            --- In apache-asp@y..., Joshua Chamas <joshua@c...> wrote:
            > Looking at your solution, I think I would probably do
            > something more like:
            >
            > <my:template href="/templates/simple.html" />
            > <my:header>Welcome back, <%= $Session->{'username'} %>!
            </my:header>
            > <my:body>
            > <!--#include file="foo.inc" -->
            > </my:body>
            > </my:template>
            >
            > This is a more natural use of the XMLSubs in my view, where
            > inner tags get executed before outer tags.

            I agree. I've changed my code to work like this.

            Of course, now I've run into a new roadblock. Can $Response->Redirect
            be called from within XMLSubs? I've no trouble with normal <% blocks
            and accessing objects like $Session, $Response, and such, but
            redirects seem to be a no go. I have a very simple page like so:

            <my:template href="/templates/simple.html">
            <my:body>
            <% $Response->Redirect('/bar.html'); print "WHEE!" %>
            Didn't redirect.
            </my:body>
            </my:template>

            Accessing the page results in no data:

            $ telnet 63.121.xxx.xxx 80
            Trying 63.121.xxx.xxx...
            Connected to 63.121.xxx.xxx (63.121.xxx.xxx).
            Escape character is '^]'.
            GET /env.html HTTP/1.1
            Host: xxx.fastweb.com

            Connection closed by foreign host.

            I added debug hooks to my::template and my::body-- looking at the
            error log, I see they're not even being called:

            [Wed Mar 27 07:50:30 2002] [error] [asp] [6293] [debug] [env.html] -
            Session_OnEnd 0fa9687de85bb7a63efa76e3be4a4a5a
            [Wed Mar 27 07:50:30 2002] [error] [asp] [6293] [debug] [env.html] -
            Application_OnEnd
            [Wed Mar 27 07:50:30 2002] [error] [asp] [6293] [debug] [env.html] -
            Application_OnStart
            [Wed Mar 27 07:50:30 2002] [error] [asp] [6293] [debug] [env.html] -
            Session_OnStart 09bd6388e1013e29a4522c21480c1669
            [Wed Mar 27 07:50:30 2002] [error] [asp] [6293] [debug] [env.html] -
            Script_OnStart /home/httpd/hosts/fastweb/jobs2/docs/env.html
            [Wed Mar 27 07:50:30 2002] [error] [asp] [6293] [debug] [env.html] -
            Script_OnEnd /home/httpd/hosts/fastweb/jobs2/docs/env.html

            At debug level -3, I can see the redirect is being called:

            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8886;0.0017] executing
            __ASP__xxx_env_htmlx4b2b5bcadbb7f3b32a28e389dcd5b4d7
            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8892;0.0006] redirect called - location: /bar.html;
            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8895;0.0003] parsed session into /bar.html?session-
            id=03d219b11e03c858407d8e4bce265b97
            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8897;0.0002] new location after session query
            parsing /bar.html?session-id=03d219b11e03c858407d8e4bce265b97
            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8900;0.0003] Script_OnEnd
            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8902;0.0002] executing Script_OnEnd
            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8904;0.0002] [env.html] -
            Script_OnEnd /home/httpd/hosts/fastweb/jobs2/docs/env.html
            [Wed Mar 27 07:53:50 2002] [error] [asp] [6333] [debug]
            [1017237230.8919;0.0015] ASP Done Processing - asp: Apache::ASP=HASH
            (0x838d090);

            but there's no output. I created a plain old ASP file like so:

            <% $Response->Redirect('/bar.html'); print "WHEE!" %>

            which worked just fine. Any ideas? Any other debugging info I can
            provide? I'm including my global.asa below.

            use File::Basename qw(basename);

            sub Session_OnStart {
            $Application->{'Session'.$Session->SessionID} = '?';
            $Response->Debug("Session_OnStart ". $Session->SessionID);
            }

            sub Session_OnEnd {
            my $t_session_active = time() - $Session->{onstart};
            $Application->{'Session'.$Session->SessionID} = $t_session_active;
            $Response->Debug("Session_OnEnd ". $Session->SessionID);
            }

            sub Application_OnStart {
            $Response->Debug("Application_OnStart");
            }

            sub Application_OnEnd {
            $Response->Debug("Application_OnEnd");
            }

            sub Script_OnStart {
            $ENV{'PATH'} = '/bin:/usr/bin';
            $Response->Debug("Script_OnStart $0");
            $Session->{Started}++;

            use vars qw(%var %db %error $input);
            %var = %db = %error = ();

            $input = $Request->Params;
            }

            sub Script_OnEnd {
            $Response->Debug("Script_OnEnd $0");
            $Session->{Ended}++;
            }

            sub Script_OnFlush {
            my $data = $Response->{BinaryRef};
            $Response->Debug("Script_OnFlush: about to flush ".length
            ($$data)." bytes to client");
            }

            $SIG{__DIE__} = \&Carp::confess;

            sub my::login {
            $Response->Debug("Entered my::login");
            my $args = shift;

            if ($args->{'type'} eq 'db-only') {
            # Check the db for the username
            }
            else {
            unless ($Session->{'login'} == 1) {
            my %param;
            $param{'URL'} = $ENV{'REQUEST_URI'};
            $Response->Redirect( $Server->URL('/login/index.html', \%
            param) );
            }
            }
            $Response->Debug("Exited my::login");
            }

            sub my::title {
            $Response->Debug("Entered my::title");
            shift;
            $var{'TITLE'} .= shift;
            $Response->Debug("Exited my::title");
            }

            sub my::header {
            $Response->Debug("Entered my::header");
            shift;
            $var{'HEADER'} .= shift;
            $Response->Debug("Exited my::header");
            }

            sub my::function {
            $Response->Debug("Entered my::function");
            shift;

            my $style;

            if ($var{'FUNCTION_COUNT'} == 0) {
            $style = 'border:1px solid #58B; background: #8BE';
            }
            elsif ($var{'FUNCTION_COUNT'} == 1) {
            $style = 'border:1px solid #69C; background: #9CF';
            }
            else {
            $style = 'border:1px solid #CCC; background: #FFF';
            }

            $var{'FUNCTIONS'} .= <<EOF;
            <tr>
            <td align="center" valign="center" height="150" style="$style">
            EOF

            $var{'FUNCTIONS'} .= shift;

            $var{'FUNCTIONS'} .= <<EOF;
            </td>
            </tr>
            <tr>
            <td><img src="/spacer.gif" height="1" width="1"></td>
            </tr>
            EOF

            $var{'FUNCTION_COUNT'}++;
            $Response->Debug("Exited my::function");
            }

            sub my::footer {
            $Response->Debug("Entered my::footer");
            shift;
            $var{'FOOTER'} .= shift;
            $Response->Debug("Exited my::footer");
            }

            sub my::body {
            $Response->Debug("Entered my::body");
            my $args = shift;
            $var{'BODY'} = shift;
            $Response->Debug("Exited my::body");
            }

            sub my::var {
            $Response->Debug("Entered my::var");
            my ($args, $data) = @_;
            $data =~ s/^\n//;
            $data =~ s/\n$//;
            $var{$args->{'name'}} = $data;
            $Response->Debug("Exited my::var");
            }

            sub my::template {
            $Response->Debug("Entered my::template");
            my ($args, $data) = @_;
            $var{'TITLE'} ||= $var{'HEADER'};
            $var{'TEMPLATE'} = $Server->MapPath($args->{'href'});
            $Response->Include($var{'TEMPLATE'});
            $Response->Debug("Exited my::template");
            }





            ---------------------------------------------------------------------
            To unsubscribe, e-mail: asp-unsubscribe@...
            For additional commands, e-mail: asp-help@...
          • Joshua Chamas
            ... I believe you found a critical error in $Response- Redirect not working right under an XMLSubs. This seems to be due to Flush() being disabled under an
            Message 5 of 6 , Apr 2, 2002
            • 0 Attachment
              eamondaly wrote:
              >
              > I agree. I've changed my code to work like this.
              >
              > Of course, now I've run into a new roadblock. Can $Response->Redirect
              > be called from within XMLSubs? I've no trouble with normal <% blocks
              > and accessing objects like $Session, $Response, and such, but
              > redirects seem to be a no go. I have a very simple page like so:
              >
              > <my:template href="/templates/simple.html">
              > <my:body>
              > <% $Response->Redirect('/bar.html'); print "WHEE!" %>
              > Didn't redirect.
              > </my:body>
              > </my:template>
              >

              I believe you found a critical error in $Response->Redirect
              not working right under an XMLSubs. This seems to be due
              to Flush() being disabled under an XMLSubs, and $Response->Redirect()
              was basically using Flush() so it would end the script
              without doing anything!!

              I have reproduced/fix this and created a test case around this
              in my dev 2.33 version, hopefully proving that my fix for
              this works now & in the future. Let me know if you would like
              an early release of this module, and I will send it to you
              privately.

              Regards,

              Josh
              _________________________________________________________________
              Joshua Chamas Chamas Enterprises Inc.
              NodeWorks Founder Huntington Beach, CA USA
              http://www.nodeworks.com 1-714-625-4051

              ---------------------------------------------------------------------
              To unsubscribe, e-mail: asp-unsubscribe@...
              For additional commands, e-mail: asp-help@...
            Your message has been successfully submitted and would be delivered to recipients shortly.