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{ <input type=image
src
="$$args{go_button}" border=0>}:qq{ <input type=submit value="Go">};
print << "EOF";
<table border=0 cellpadding=1 cellspacing=0>
<form method=GET action="/$storeid/$script">
<tr>
$caption
<td $class>
<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]