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

Templating suggestions?

Expand Messages
  • eamondaly
    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
    Message 1 of 6 , Mar 21, 2002
    • 0 Attachment
      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

      - 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

      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?

      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.

      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've tried stuff like structuring foo.htm as XML, but parsing that
      with XMLin breaks because the HEADER and LEFT_COLUMN variables often
      contain arbitrary HTML.

      I'm open to any and all suggestions!


      ---------------------------------------------------------------------
      To unsubscribe, e-mail: asp-unsubscribe@...
      For additional commands, e-mail: asp-help@...
    • Joshua Chamas
      ... If you want some standard templatting system other than ASP style , then why not use another templatting framework like Template Toolkit
      Message 2 of 6 , Mar 21, 2002
      • 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 3 of 6 , Mar 22, 2002
        • 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 4 of 6 , Mar 22, 2002
          • 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 5 of 6 , Mar 27, 2002
            • 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 6 of 6 , Apr 2 12:29 AM
              • 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.