On Wednesday 18 September 2002 01:10 pm, Josh Chamas spewed into the ether:
> Assuming that the data is available when the my:sub is starting
> to get processed, then you can use more XMLSubs to do your dirty work like:
>
> <% my $data_ref = \%data_hash; %>
> <my:sub data=$data_ref>
>     <my:value name='COST' fmt='%.2f' />
>     <my:value name='STATE' fmt='%uc2' />
> </my:sub>

I've been playing around with this some more and have this observation to make 
(among some others)-- what you have started here with XMLSubs is very nifty 
and offers the designer a large degree of abstraction ability (a la 
templating) in crufting up pages-- however, the one big weakness that I see 
is that all of of the ASP assisted bits-- nested subs, <% %> substitution, 
etc-- all happen in the PRE processing stage and not DURING the processing 
stage. A lot of what one wants to do doesn't come into scope until _during_ 
the processing.

For example, one use of this ability (and enabling a big win) would be in 
interpreting/rendering row data selected from a database. None of the nifty 
ASP-assist functions are at all useful in this context because it isn't until 
inside the iteration loop which is inside the my:sub function that this 
information first becomes available. 

Here is an (actual) example of the problem:

<table width="100%">
<my:pgloop sth=$sth>
  <fmt>PRICE:%.2f</fmt>
  <tr>
    <td><my:pimage pg="__PG__" sku="__SKU__" width="32"/></td>
    <td><a href="catalog/__PG__/__SKU__">__SKU__</a></td>
    <td>__USER1__</td>
    <td>__NAME__</td>
    <td align=right>__QTY_ONHAND__</td>
    <td align=right>__AMT_PRICE__</td>
  </tr>
</my:pgloop>
</table>

In this case, the 'my:pimage' sub is nested inside the 'my:pgloop' sub-- the 
statement handle '$sth' contains the selected DBI table rows, and the pgloop 
sub iterates the rows and subs the template for each row. The 'my:pimage' sub 
retrieves an image based on the product group and sku id. But in this case, 
the sku is not known until inside the pg:loop func. Intuitively (to me at 
least) the above is the logical way (and perhaps the only way) to construct 
the template. However, in real life, when I do this, ASP attempts to resolve 
the my:pimage sub before handing it off to the my:pgloop sub-- which is 
before any token-data is available to resolve the __TOKENS__. So processing 
(as written) works from the inside-out instead of outside-in, which imo would 
be the more intuitive and useful approach.

There might also be a compromise approach that's possible too- so that <% %> 
substitutions happen immediately before the sub, while __TOKEN__ values and 
nested xmlsubs would be processed after (during) being passed (ie. be delayed 
in resolution by one level of nesting so that any external references ie. 
__TOKENS__ or whatever could get resolved first).


> If you can end up with your document written entirely in XML,
> then you are on your way to having your data written entirely
> separately from your style, which is a good goal.  This is
> the XML/XSLT paradigm, but can be implemented with XHTML,
 XMLSubs, XSLT, you pick your favorite.

Yes, I'm doing my best to push my application in that direction. My current 
app involves an e-commerce platform which has to support a wide variety of 
styles and configurations. I am hoping to get to the point where the customer 
can supply the basic page with all the business logic accessible via special 
tags. So they get the control over design and layout while maintaining (but 
not being responsible for) the actual workings of the system.


> > B) The second issue is harder though and I still haven't thunk up a good
> > solution for it yet... and that is how to handle combined or calculated
> > post-processed fields-- ie
> >
> > <my:sub ...>
> > __VAR1__ times __VAR2__ = [product here somehow??]
> > </my:sub>
>
> Be careful... you are creating your own mini language here.

Heh- I hope not. I'm trying to stay away from that on purpose. But what I am 
discovering is that there needs to be formatting information, and also I had 
another idea before I went to sleep last night-- and that was the idea of 
tying a token to a well-known (somewhere-else predefined) rendering style...

<my:sub>
  <type>USER_TYPE:userlookup</type>
</my:sub>

And 'userlookup' would be a specific reference (either implicit or 
interpreted) to a renderer-- meaning it would tie the USER_TYPE token/data 
pair to a bit of code (function/whatever) that would render it in a way 
appropriate to a user lookup function-- however that happens to be defined.

A couple of years ago, I was working on a different (non-ASP) app where I 
needed to display a list of "who's on" along with related information about 
the user, where they were from, what they were doing, the time, and 
idle-time, and other information. In addition, there were several _very_ 
similar variations on this theme depending on who was asking, why they were 
asking, and what the general gist of the question was-- after writing a bunch 
of who's-on scripts, all slighly different but basically the same, I decided 
to create a more flexible approach and designed a single who's-on system that 
allowed predefined "fields", "renderers", and "reports" (stored in config 
files) that defined what a field was (where it came from, how it was used, 
etc) and this allowed me to create artificial fields through some transform 
of existing fields, and defined renderers that could be tied to a particular 
field (in the field definition), and finally reports which were collections 
of fields and rules regarding who could access the report and meta 
information such as report title and other "pretty" information.

When I first started, it seemed pretty daunting. But in the end, it turned out 
that it was an extremely workable system-- there were a few wrinkles that 
needed to be ironed-out and areas extended in order to ultimately make it all 
work the way it should. The code was easy to follow and most importantly made 
designing a new report a 10-minute process. Once the first few reports had 
been created, 99% of the field & renderers had already been defined so 
building a new report consisted mostly of choosing predefined fields and the 
context in which the report could be viewed.

While that bit of code was specific to the application I was working on at the 
time, the _idea_ of that code seems germain and relevant to what we're 
discussing here-- and is very similar to this whole templating thing we're 
talking about.



Another useful thing might be a <def> field (define) field that could 
construct artificial tokens based on expression-manipulation of existing data 
available in the dataset at time of rendering. That way you could have a cost 
field and apply a markup value to it and come up with an artificial, but 
usable price token.

<my:sub>
  <define>PRICE:COST*1.2</define>
 The cost of this item is __COST__ and it is currently selling for __PRICE__.
</my:sub>

Finally there is a need for some sort of simple if/then logic. This is where 
everything gets tricky. If you go to far with this concept then you cross the 
threshold of simple formatting and get into procedural aspects which should 
probably be avoided. There probably should never be any sort of iterative 
aspect. That seems definately over the line, and can be handled anyway by 
simply creating a my:sub that performs the iteration. But consider this 
situation:

Suppose you have a product table and some of the products in the table are on 
sale. When you retrieve the list of the items, you would like to apply one 
style if onsale, and another if not.

I think there oughta be some sort of facility for handling this type of 
formatting situation at least for the simplistic case-- though I quickly 
admit that if you add too much flexibility that it gets out of hand very 
quickly, and further I am at a loss to suggest a suitable style for such a 
framework.

Besides, at some point, if you alter enough of the basic template, you're not 
really saving yourself any work, which was the original objective, and 
arguably could be adding to your workload by adding additional crap to  
remember.



> You could do this instead:
>
> <% my $data_ref = \%$data_hash; %>
> <my:sub>
>    <%= $data_ref->{VAR1} * $data_ref->{VAR2} %>
> </my:sub>
>
> I caution against going down the create your own mini language
> path because it will end up being a language that only you know.

Definatelyt agreed. Trying to stick to formatting issues only.



> I would recommend more that if you want to inline a mini language,
> that you look into rendering inline a bit of a template toolkit
> or HTML::Template into your Apache::ASP

Actually I have looked at them and they seem overblown and needlessly 
cumbersome for what I want to do. I think you could get away with some very 
simple formatting rules, and perhaps some method to generate artificial 
tokens. Being able to define a renderer is a nice thing too but an extra. All 
of these things are issues the designer/webmaster needs to have access to in 
order to have the control over the creative aspect of the site. They do not 
need to concern themselves with the inner workings of the XMLSubs they use. 
They need only know that if you supply "these parameters" and in "this way" 
then "this will happen"-- very black box-ish, and html-like.


>> I guess perhaps there is a limit to how much one can cram
>> into this XMLSubs thing... ;)
>
> There is a limit, but I would like to break it.  I believe I can add
> a lot of power to XMLSubs, if I allow them to also be called at
> compile time as an extension, so that the ASP template being compiled
> could be modified instead of the output.  The runtime model has more
> limits, and the compile time model would only be added as an extension.
> I have been mulling these kinds of extensions to XMLSubs for a while,
> so do not hold your breath.

I don't hold my breath much anymore. I tend to faint a lot that way ;)


> If you really want your site to be fast, then precompile your scripts
> in the parent httpd.  The real performance hit your are saving yourself
> from it having to compile the scripts in every child httpd separately.
> Read here for more:
>
>    http://www.apache-asp.org/tuning.html#Precompile%20S36827838

Yup, I already do that and it works well. Also moving the .htaccess stuff into 
the apache config helps a lot too.


BTW- here is a better and improved version of the rendering function I 
submitted last time. It is more efficient because it cranks through the 
tokens it finds instead of all the ones available in the hash. The render 
function itself though is a drop-in replacement of the one I presented 
before. This snippet is capable of processing the my:sub examples I presented 
above (though you would have to finish wrapping the my:sub setup around it).


my %fmt = (); # for format view

## extract format (if any) - what's left becomes template string
$body =~ s/<fmt>(.*?)<\/fmt>//i;
$fmt = $1;

## scoop-up formats into a convenient hash
foreach (split(/,/, $fmt)) {
        my ($key, $val) = split(/:/, $_);
        $fmt{uc(eatwhite($key))} = eatwhite($val);
        }

foreach (@hash_list) { render($body, $_, \%fmt) }

## more efficient render func
sub render {
        my ($tmpl, $hash, $fmt) = @_;
        while ($tmpl =~ /__(.*?)__/) {
                my $TOKEN = $1; my $token = lc($TOKEN);
                if ((my $fmt = $$fmt{$TOKEN}) ne undef) {
                        # to-uppercase
                        if ($fmt =~ /^%uc/i) { $fmt =~ s/[^0-9]//gm; 
$$hash{$token} = uc(substr($$hash{$token}, 0, $fmt || 
length($$hash{$token}))); }
                        # to-lowercase
                        elsif ($fmt =~ /^%lc/i) { $fmt =~ s/[^0-9]//gm; 
$$hash{$token} = lc(substr($$hash{$token}, 0, $fmt || 
length($$hash{$token}))); }
                        # picture
                        else { $$hash{$token} = sprintf($fmt, $$hash{$token}); 
}
                        }
                $tmpl =~ s/__${TOKEN}__/$$hash{$token}/gme;
                }
        print $tmpl;
        }


__END__

BTW-- additional predefined formats can be added as desired. Some useful ones 
to add might include a telephone-number style renderer, a ssn style, a 
zipcode style (zip+4), an email style, a url style, etc. They could all be 
added in the if/then ladder where it is deciding what format to apply to the 
data. Also, if enough renderers are defined, or additional 
functionality/flexibility/or config options is desired, the whole formatting 
bit can be removed to its own subsystem in a style similar to the "who's-on" 
model I described above.


Here is a snippet of code from an actual my:sub (my:pgloop, used above) that 
makes use of this particular renderer:

sub my::pgloop { # iterate thru results set and process body block
        my ($args, $body) = @_;

        ## extract format (if any) - what's left becomes template string
        my %fmt = (); # hash for format view
        $body =~ s/<fmt>(.*?)<\/fmt>//i;
        my $fmt = $1;

        ## scoop-up formats into a convenient hash
        foreach (split(/,/, $fmt)) {
                my ($key, $val) = split(/:/, $_);
                $fmt{uc(eatwhite($key))} = eatwhite($val);
                }

        ## iterate data rows and render accordingly
        while (my $row = $$args{sth}->fetchrow_hashref('NAME_lc')) {
                ## render hash using template string and format hash
                render($body, $row, \%fmt);
                }
        }

__END__

This illustrates how a complete XMLSub function can be constructed using the 
snippets of code I've presented thus far. Simply substitute the guts of this 
function with the code snippets and make the minor adjustments so that the 
variables match up.


Finally I would like to present another snippet of code that I use in my 
applications. This time it is an embeddable search form that makes it very 
easy to stick a search widget just about anywhere.

sub my::search { # display a small, embeddable search form
        my ($args, $body) = @_;
        my $script = $$args{script} || 'catalog';
        my $class = qq{class="$$args{class}"} if ($$args{class} ne '');
        my $caption = qq{<td align=right $class>$body</td>} if ($body ne '');
        my $size = $$args{size} || 10;
        my $go_button = ($$args{go_button} ne '')?qq{&nbsp;<input type=image 
src
="$$args{go_button}" border=0>}:qq{ &nbsp;<input type=submit value="Go">};
        print << "EOF";
<table border=0 cellpadding=1 cellspacing=0>
<form method=GET action="/$storeid/$script">
<tr>
        $caption
        <td $class>
        &nbsp;<input type="text" name="search" value="-Search-" size=$size 
maxle
ngth=80 onfocus="if(this.value=='-Search-') {this.value='';}" 
onblur="if(this.va
lue=='') {this.value='-Search-';}">
        $go_button
        </font>
        </td>
</tr>
</form>
</table>
EOF
        }

__END__

To use this in an html/asp script, just do something like:

<my:search go_button="$themeurl/go_button.gif"/>

-or-

<my:search go_button="$themeurl/go_button.gif"><b>Search</b></my:search>

(Anything placed inside the tags becomes the caption for the search). Notice 
also that this includes some simple javascript to make the word 'search' 
appear inside the text box whenever the user has not typed anything in it. 
This makes it possible to use it in a very "small" footprint.


As always, I hope I'm helping someone. There's a lot of neat stuff you can do 
with Apache::ASP. Part of getting it widely-used is helping people to see how 
it can be used to solve real-world problems and offering real world examples 
to "steal" or get inspired by.



John


--------------------------------------------------------------------------------
Check out http://www.Wizard.Org for great deals on Electronic Parts
*NEW* Computer Parts & Accessories - Drives - LCD - Systems - Linux
--------------------------------------------------------------------------------
** Affordable Online Store w/Merchant Card Processing & Paypal **
Write to us: [EMAIL PROTECTED]  --  Get your Store Online Today!
--------------------------------------------------------------------------------



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to