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]