The Zope Book by Michel Pelletier and Amos Latteier Status | Comments | CVS
The book is still in development and you may see comments from the technical and development editors in the text. These can be ignored.
In this chapter you'll find out some of the theory behind DTML as well as learning about many new DTML tags. Understanding how DTML works can help you figure out how to make the best use of it.
One of DTML's most fundamental jobs is to look up variables and then perform actions on them, such as insert them into the DTML object's output. When you insert a variable with the var tag you are telling Zope to look up a variable and then insert its value:
<dtml-var favoriteColor>
The DTML engine will look for a variable with the name favoriteColor. We say that DTML looks for a variable in its namespace. A namespace is used to resolve names.
Whenever DTML code is executed Zope creates a DTML namespace to resolve variable names. It's important to understand the workings of the DTML namespace so that you can accurately predict how DTML will locate variables. Some of the trickiest problems you will run into with DTML can be resolved by understanding the DTML namespace.
The DTML namespace is a collection of objects arranged in a stack. A stack is a list of objects that can manipulated by pushing and popping objects on to and off of the stack, as illustrated in [7-1].
When Zope looks for names in the DTML namespace stack it first looks at the very top most object in the stack. If the name can't be found there, then the next item down is looked in. DTML will work its way down the stack, checking each object in turn until it finds the name that it is looking for. If the DTML engine gets all the way down to the bottom of the stack and can't find what it is looking for, then an error is generated. For example, try looking for the non-existent name, unicorn:
<dtml-var unicorn>
As long as there is no variable named unicorn viewing this DTML will return an error, as shown in [7-2].
DTML Namespaces are built dynamically for every request in Zope. When you call a DTML Method or object through the web, the DTML namespace starts with the same first two stack elements:
The client object in DTML depends on whether or not you are executing a DTML Method or a DTML Document. In the case of a Document, the client object is always the document itself, or in other words, a DTML Document is its own client object.
A DTML Method however can have different kinds of client objects depending on how it is called. For example, if you had a DTML Method that displayed all of the contents of a folder then the client object would be the folder that is being displayed. This client object can change depending on which folder the method in question is displaying. For example, consider the following DTML Method named list in the root folder:
<dtml-var standard_html_header> <ul> <dtml-in objectValues> <li><dtml-var title_or_id></li> </dtml-in> </ul> <dtml-var standard_html_footer>
Now, what this method displays depends upon how it is used. If
you apply this method to the Reptiles folder with the URL
http://localhost:8080/Reptiles/list
, then you will get
something that looks like [7-3].
But if you were to apply the method to the Birds folder with
the URL http://localhost:8080/Birds/list
then you would get
something different, as shown in [7-4].
Same DTML Method, different results. In the first example, the client object of the list method was the Reptiles folder. In the second example, the client object was the Birds folder. When Zope looked up the objectValues variable, in the first case it called the objectValues method of the Reptiles folder, in the second case it called the objectValues method of the Birds folder.
In other words, the client object is where variables such as methods, and properties are looked up first.
As you saw in Chapter 4, if Zope cannot find a variable in the client object, it searches through the object's containers. Zope uses acquisition to automatically inherit variables from the client object's containers. So when Zope walks up the object hierarchy looking for variables it always starts at the client object, and works its way up from there.
The REQUEST namespace is the very bottom most namespace on the DTML namespace stack. This namespace contains all of the information specific to the current web request.
Just as the client object uses acquisition to look in a number of places for variables, so too the request looks up variables in a number of places. When the request looks for a variable it consults these sources in order:
The REQUEST namespace is very useful in Zope since it is the primary way that clients (in this case, web browsers) communicate with Zope by providing form data, cookies and other information about themselves.
A very simple and enlightening example is to simply print the REQUEST out in an HTML page:
<dtml-var standard_html_header> <dtml-var REQUEST> <dtml-var standard_html_footer>
Try this yourself, you should get something that looks like [7-5].
Since the request comes after the client object, if there are names that exist in both the request and the client object, DTML will always find them first in the client object. This can be a problem. In the next section you'll learn some ways to get around this problem by controlling more directly how DTML looks up variables.
When you insert a variable using the var tag, DTML first looks up the variable using the DTML namespace, it then renders it and inserts the results. Rendering means turning an object or value into a string suitable for inserting into the output. Zope renders simple variables by using Python's standard method for coercing objects to strings. For complex objects such as other DTML Methods and SQL Methods, Zope will call the object instead of just trying to turn it into a string. This allows you to insert DTML Method into other DTML Methods.
In general DTML renders variables in the way you would expect. It's only when you start doing more advanced tricks that you become aware of the rendering process. Later in this chapter we'll look at some examples of how to control rendering.
Now that you have seen that the DTML namespace is a stack, you may be wondering how, or even why, new objects get pushed onto it.
Some DTML tags modify the DTML namespace while they are executing. A tag may push some object onto the namespace stack during the course of execution. These tags include the in tag, the with tag, and the let tag.
When the in tag iterates over a sequence it pushes the current item in the sequence onto the top of the namespace stack:
<dtml-var getId> <!-- This is the id of the client object --> <dtml-in objectValues> <dtml-var getId> <!-- this is the id of the current item in the objectValues sequence --> </dtml-in>
You've seen this many times throughout the examples in this book. While the in tag is iterating over a sequence, each item is pushed onto the namespace stack for the duration of the contents of the in tag block. When the block is finished executing, the current item in the sequence is popped off the DTML namespace stack and the next item in the sequence is pushed on.
The with tag pushes an object that you specify onto the top of the namespace stack for the duration of the with block. This allows you to specify where variables should be looked up first. When the with block closes, the object is popped off the namespace stack.
Consider a folder that contains a bunch of methods and properties that you are interested in. You could access those names with Python expressions like this:
<dtml-var standard_html_header> <dtml-var expr="Reptiles.getReptileInfo()"> <dtml-var expr="Reptiles.reptileHouseMaintainer"> <dtml-in expr="Reptiles.getReptiles()"> <dtml-var species> </dtml-in> <dtml-var standard_html_footer>
Notice that a lot of complexity is added to the code just to get things out of the Reptiles folder. Using the with tag you can make this example much easier to read:
<dtml-var standard_html_header> <dtml-with Reptiles> <dtml-var getReptileInfo> <dtml-var reptileHouseMaintainer> <dtml-in getReptiles> <dtml-var species> </dtml-in> </dtml-with> <dtml-var standard_html_footer>
Another reason you might want to use the with tag is to put the request, or some part of the request on top of the namespace stack. For example suppose you have a form that includes a input named id. If you try to process this form by looking up the id variable like so:
<dtml-var getId>
You will not get your form's id variable, but the client object's id. One solution is to push the web request's form on to the top of the DTML namespace stack using the with tag:
<dtml-with expr="REQUEST.form"> <dtml-var getId> </dtml-with>
This will ensure that you get the form's id first.
If you submit your form without supplying a value for the id input, the form on top of the namespace stack will do you no good, since the form doesn't contain an id variable. You'll still get the client object's id since DTML will search the client object after failing to find the id variable in the form. The with tag has an attribute that lets you trim the DTML namespace to only include the object you specify:
<dtml-with expr="REQUEST.form" only> <dtml-if id> <dtml-var getId> <dtml-else> <p>The form didn't contain an "id" variable.</p> </dtml-if> </dtml-with>
Using the only attribute allows you to be sure about where your variables are being looked up.
The let tag lets you push a new namespace onto the namespace stack. This namespace is defined by the tag attributes to the let tag:
<dtml-let person="'Bob'" relation="'uncle'"> <p><dtml-var person>'s your <dtml-var relation>.</p> </dtml-let>
This would display:
<p>Bob's your uncle.</p>
The let tag accomplishes much of the same goals as the with tag. The main advantage of the let tag is that you can use it to define multiple variables to be used in a block. In general the with tag is more useful to push existing objects on to the namespace stack, while the let tag is better suited for defining new variables for a block.
When you find yourself writing complex DTML that requires things like new variables, there's a good chance that you could do the same thing better with Python or Perl. Advanced scripting is covered in Chapter 8.
The DTML namespace can be accessed directly in DTML by working with the _ (underscore) object. The _ namespace is often referred to as as "the under namespace".
The under namespace provides you with many useful methods for certain programming tasks. Let's look at a few of them.
Say you wanted to print your name three times. This can be done with the in tag, but how do you explicitly tell the in tag to loop three times? Just pass it a sequence with three items:
<dtml-var standard_html_header> <ul> <dtml-in expr="_.range(3)"> <li><dtml-var sequence-item>: My name is Bob.</li> </dtml-in> </ul> <dtml-var standard_html_footer>
The _.range(3)
Python expression will return a sequence of the
first three integers, 0, 1, and 2. The range function is a
standard Python built-in and many of Python's built-in functions
can be accessed through the _ namespace, including:
range([start,], stop, [step])
start
to stop
counting step
integers at a
time. start
defaults to 0 and step
defaults to 1. For example _.range(3,9,2)
gives [3,5,7,9]
.
len(sequence)
len
returns the size of sequence as an integer.These and many others are explained in more detail in Appendix A.
The under namespace can also be used to explicitly control variable look up. There is a very common usage of this syntax. You've seen that the in tag defines a number of special variables, like sequence-item and sequence-key that you can use inside a loop to help you display and control it. What if you wanted to use one of these variables inside a Python expression?:
<dtml-var standard_html_header> <h1>The squares of the first three integers:</h1> <ul> <dtml-in expr="_.range(3)"> <li>The square of <dtml-var sequence-item> is: <dtml-var expr="sequence-item * sequence-item"> </li> </dtml-in> </ul> <dtml-var standard_html_footer>
Try this, does it work? No! Why not? The problem lies in this var tag:
<dtml-var expr="sequence-item * sequence-item">
Remember, everything inside a Python expression attribute must be a valid Python expression. In DTML, sequence-key is the name of a variable, but in Python this means "The object sequence minus the object key". This is not what you want.
What you really want is to look up the variable sequence-item. The way to do this in a DTML expression is to use the getitem utility function to explicitly look up a variable:
<dtml-var expr="_.getitem('sequence-item') * _.getitem('sequence-item')">
The getitem function takes the name to look up as its first argument. Now, the DTML Method will correctly display the sum of the first three integers. The getitem method takes an optional second argument which specifies whether or not to render the variable. Recall that rendering a DTML variable means turning it into a string. By default the getitem function does not render a variable.
Here's how to insert a rendered variable named myDoc:
<dtml-var expr="_.getitem('myDoc', 1)">
This example is in some ways rather pointless, since it's the functional equivalent to:
<dtml-var myDoc>
However, suppose you had a form in which a user got to select which document they wanted to see from a list of choices. Suppose the form had an input named selectedDoc which contained the name of the document. You could then display the rendered document like so:
<dtml-var expr="_.getitem(selectedDoc, 1)">
Notice in the above example that selectedDoc is not in quotes. We don't want to insert the variable named selectedDoc we want to insert the variable named by selectedDoc. For example, the value of selectedDoc might be chapterOne. Using indirect variable insertion you can insert the chapterOne variable. This way you can insert a variable whose name you don't know when you are authoring the DTML. Indirect variable insertion is hard to wrap your head around, but it can be quite useful. Don't worry if you don't completely understand indirect variable insertion. It's an advanced concept that you can safely file away until some day when you need it.
Zope can be used by many different kinds of users. For example, the Zope site, Zope.org, has over 11,000 community members at the time of this writing. Each member can log into Zope, add objects and news items, and manage their own personal area.
Because DTML is a programming language, it is very flexible about working with objects and their properties. If there were no security system that constrained DTML then a user could potentially create malicious or privacy-invading DTML code.
DTML is restricted by standard Zope security settings. So if you don't have permission to access an object by going to its URL you also don't have permission to access it via DTML. You can't use DTML to trick the Zope security system.
For example, suppose you have a DTML Document named Diary which is private. Anonymous users can't access your diary via the web. If an anonymous user views DTML that tries to access your diary they will be denied:
<dtml-var Diary>
DTML verifies that the current user is authorized to access all DTML variables. If the user does not have authorization, than the security system will raise an Unauthorized error and the user will be asked to present more privileged authentication credentials.
In Chapter 6 you can read about security rules for executable content. There are ways to tailor the roles of a DTML Document or Method to make it access restricted variables regardless of the viewer's roles.
The DTML engine will take some steps to try to make sure you don't do anything silly or destructive such as crash Zope.
DTML will not let you gobble up memory or execute infinite loops and recursions. For example, you cannot create huge lists with the _.range utility function. You also have no way to access the filesystem directly in DTML.
Keep in mind however that these safety limits are simple and can be outsmarted by determined users. It's generally not a good idea to let anyone you don't trust write DTML code on your site.
In the rest of this chapter we'll look at the many advanced DTML tags. These tags are summarized in Appendix A. DTML has tags to do all kind of things. In fact you can download many interesting contributed DTML tags from the Zope.org web site. For example, Ty Sarna's Calendar tag gives you the ability to easily format date-oriented objects as a calendar.
The var tag can call methods, but it also inserts the return value. Using the call tag you can call methods without inserting their return value into the output. This is useful if you are more interested in the effect of calling a method rather than its return value.
For example, when you want to change the value of a property, animalName, you are more interested in the effect of calling the manage_changeProperty method than the return value the method gives you. Here's an example:
<dtml-if expr="REQUEST.has_key('animalName')"> <dtml-call expr="manage_changeProperty(animalName=REQUEST['animalName'])"> <h1>The property 'animalName' has changed</h1> <dtml-else> <h1>No properties were changed</h1> </dtml-if>
In this example, the page will change a property depending on whether a certain name exists. The result of the manage_changeProperty method is not important and does not need to be shown to the user.
Another common usage of the call tag is calling methods that effect
client behavior, like the RESPONSE.redirect
method:
<dtml-var standard_html_header> <dtml-let target="'http://example.com/new_location.html'"> <h1>This page has moved, you will now be redirected to the correct location. If your browser does not redirect, click <a href="<dtml-var target>"><dtml-var target></a>.</h1> <dtml-call expr="RESPONSE.redirect(target)"> </dtml-let> <dtml-var standard_html_footer>
In short, the call tag works exactly like the var tag with the exception that it doesn't insert the results of calling the variable.
Zope has extensive exception handling facilities. You can get access to these facilities with the raise and try tags. For more information on exceptions and how they are raised and handled see a book on Python or you can read the online Python Tutorial.
You can raise exceptions with the raise tag. One reason to raise exceptions is to signal an error. For example you could check for a problem with the if tag, and in case there was something wrong you could report the error with the raise tag.
The raise tag has a type attribute for specifying an error type. The error type is a short descriptive name for the error. In addition, there are some standard error types, like Unauthorized and Redirect that are returned as HTTP errors. Unauthorized errors cause a log-in prompt to be displayed on the user's browser. You can raise HTTP errors to make Zope send an HTTP error. For example:
<dtml-raise type="404">Not Found</dtml-raise>
This raises an HTTP 404 (Not Found) error. Zope responds by sending the HTTP 404 error back to the client's browser.
The raise tag is a block tag. The block enclosed by the raise tag is rendered to create an error message. If the rendered text contains any HTML markup, then Zope will display the text as an error message on the browser, otherwise a generic error message is displayed.
Here is a raise tag example:
<dtml-if expr="balance >= debit_amount"> <dtml-call expr="debitAccount(account, debit_amount)"> <p><dtml-var debit_amount> has been deducted from your account <dtml-var account>.</p> <dtml-else> <dtml-raise type="Insufficient funds"> <p>There is not enough money in account <dtml-account> to cover the requested debit amount.</p> </dtml-raise> </dtml-if>
There is an important side effect to raising an exception, exceptions cause the current transaction to be rolled back. This means any changes made by a web request to be ignored. So in addition to reporting errors, exceptions allow you to back out changes if a problem crops up.
If an exception is raised either manually with the raise tag, or as the result of some error that Zope encounters, you can catch it with the try tag.
Exceptions are unexpected errors that Zope encounters during the execution of a DTML. Once an exception is detected, the normal execution of the DTML stops. Consider the following example:
Cost per unit: <dtml-var expr="_.float(total_cost/total_units)" fmt=dollars-and-cents>
This DTML works fine if total_units is not zero. However, if total_units is zero, a ZeroDivisionError exception is raised indicating an illegal operation. So rather than rendering the DTML, an error message will be returned.
You can use the try tag to handle these kind of problems. With the try tag you can anticipate and handle errors yourself, rather than getting a Zope error message whenever an exception occurs.
The try tag has two functions. First, if an exception is raised, the try tag gains control of execution and handles the exception appropriately, and thus avoids returning a Zope error message. Second, the try tag allows the rendering of any subsequent DTML to continue.
Within the try tag are one or more except tags that identify and handle different exceptions. When an exception is raised, each except tag is checked in turn to see if it matches the exception's type. The first except tag to match handles the exception. If no exceptions are given in an except tag, then the except tag will match all exceptions.
Here's how to use the try tag to avoid errors that could occur in the last example:
<dtml-try> Cost per unit: <dtml-var expr="_.float(total_cost/total_units)" fmt="dollars-and-cents"> <dtml-except ZeroDivisionError> Cost per unit: N/A </dtml-try>
If a ZeroDivisionError is raised, control goes to the except tag, and "Cost per unit: N/A" is rendered. Once the except tag block finishes, execution of DTML continues after the try block.
DTML's except tags work with Python's class-based exceptions. In addition to matching exceptions by name, the except tag will match any subclass of the named exception. For example, if ArithmaticError is named in a except tag, the tag can handle all ArithmaticError subclasses including, ZeroDivisionError. See a Python reference such as the online Python Library Reference for a list of Python exceptions and their subclasses.
Inside the body of an except tag you can access information about the handled exception through several special variables.
You can use these variables to provide error messages to users or to take different actions such as sending email to the webmaster or logging errors depending on the type of error.
The try tag has an optional else block that is rendered if an exception didn't occur. Here's an example of how to use the else tag within the try tag:
<dtml-try> <dtml-call feedAlligators> <dtml-except NotEnoughFood WrongKindOfFood> <p>Make sure you have enough alligator food first.</p> <dtml-except NotHungry> <p>The alligators aren't hungry yet.</p> <dtml-except> <p>There was some problem trying to feed the alligators.<p> <p>Error type: <dtml-var error_type></p> <p>Error value: <dtml-var error_value></p> <dtml-else> <p>The alligator were successfully fed.</p> </dtml-try>
The first except block to match the type of error raised is rendered. If an except block has no name, then it matches all raised errors. The optional else block is rendered when no exception occurs in the try block. Exception in the else block are not handled by the preceding except blocks.
You can also use the try tag in a slightly different way. Instead of handling exceptions, the try tag can be used not to trap exceptions, but to clean up after them.
The finally tag inside the try tag specifies a cleanup block to be rendered even when an exception occurs.
The finally block is only useful if you need to clean up something that will not be cleaned up by the transaction abort code. The finally block will always be called, whether there is an exception or nor and whether a return tag is used or not. If you use a return tag in the try block, any output of the finally block is discarded. Here's an example of how you might use the finally tag:
<dtml-call acquireLock> <dtml-try> <dtml-call useLockedResource> <dtml-finally> <!-- this always gets done even if an exception is raised --> <dtml-call releaseLock> </dtml-try>
In this example you first acquire a lock on a resource, then try to perform some action on the locked resource. If an exception is raised, you don't handle it, but you make sure to release the lock before passing control off to an exception handler. If all goes well and no exception is raised, you still release the lock at the end of the try block by executing the finally block.
The try/finally form of the try tag is seldom used in Zope. This kind of complex programming control is often better done in Python or Perl.
DTML can be documented with comments using the comment tag:
<dtml-var standard_html_header> <dtml-comment> This is a DTML comment and will be removed from the DTML code before it is returned to the client. This is useful for documenting DTML code. Unlike HTML comments, DTML comments are NEVER sent to the client. </dtml-comment> <!-- This is an HTML comment, this is NOT DTML and will be treated as HTML and like any other HTML code will get sent to the client. Although it is customary for an HTML browser to hide these comments from the end user, they still get sent to the client and can be easily seen by 'Viewing the Source' of a document. --> <dtml-var standard_html_footer>
The comment block is removed from DTML output.
In addition to documenting DTML you can use the comment tag to temporarily comment out other DTML tags. Later you can remove the comment tags to re-enable the DTML.
The tree tag lets you easily build dynamic trees in HTML to display hierarchical data. For example here's a tree that represents a collection of folders and sub-folders.
Here's the DTML that generated this tree display:
<dtml-var standard_html_header> <dtml-tree> <dtml-var getId> </dtml-tree> <dtml-var standard_html_footer>
The tree tag queries objects to find their sub-objects and takes care of displaying the results as a tree. The tree tag block works as a template to display nodes of the tree.
Because tree state is stored in a cookie, only one tree can appear on a web page at a time.
You can tailor the behavior of the tree tag quite a bit with tree tag attribute and special variables. Here is a sampling of tree tag attributes.
Suppose you want to use the tree tag to create a dynamic site map. You don't want every page to show up in the site map. Let's say that you put a property on folders and documents that you want to show up in the site map.
Let's first define a Script with the id of publicObjects that returns public objects:
""" Returns sub-folders and documents that have a true 'siteMap' property. """ results=[] for object in context.objectValues(['Folder', 'Document']): if object.hasProperty('siteMap') and object.siteMap: results.append(object) return results
Now we can create a DTML Method that uses the tree tag and our Scripts to draw a site map:
<dtml-var standard_html_header> <h1>Site Map</h1> <p><a href="&dtml-URL0;?expand_all=1">Expand All</a> | <a href="&dtml-URL0;?collapse_all=1">Collapse All</a> </p> <dtml-tree branches="publicObjects" skip_unauthorized="1"> <a href="&dtml-absolute_url;"><dtml-var title_or_id></a> </dtml-tree> <dtml-var standard_html_footer>
This DTML Method draws a link to all public resources and displays them in a tree. Here's what the resulting site map looks like.
For a summary of the tree tag arguments and special variables see Appendix A.
In general DTML creates textual output. You can however, make DTML return other values besides text. Using the return tag you can make a DTML Method return an arbitrary value just like a Python or Perl-based Script.
Here's an example:
<p>This text is ignored.</p> <dtml-return expr="42">
This DTML Method returns the number 42.
Another upshot of using the return tag is that DTML execution will stop after the return tag.
If you find yourself using the return tag, you almost certainly should be using a Script instead. The return tag was developed before Scripts were, and is largely useless now that you can easily write scripts in Python and Perl.
The sendmail tag formats and sends a mail messages. You can use the sendmail tag to connect to an existing Mail Host, or you can manually specify your SMTP host.
Here's an example of how to send an email message with the sendmail tag:
<dtml-sendmail> To: <dtml-var recipient> Subject: Make Money Fast!!!! Take advantage of our exciting offer now! Using our exclusive method you can build unimaginable wealth very quickly. Act now! </dtml-sendmail>
Notice that there is an extra blank line separating the mail headers from the body of the message.
A common use of the sendmail tag is to send an email message generated by a feedback form. The sendmail tag can contain any DTML tags you wish, so it's easy to tailor your message with form data.
The mime tag allows you to format data using MIME (Multipurpose Internet Mail Extensions). MIME is an Internet standard for encoding data in email message. Using the mime tag you can use Zope to send emails with attachments.
Suppose you'd like to upload your resume to Zope and then have Zope email this file to a list of potential employers.
Here's the upload form:
<dtml-var standard_html_header> <p>Send you resume to potential employers</p> <form method=post action="sendresume" ENCTYPE="multipart/form-data"> <p>Resume file: <input type="file" name="resume_file"></p> <p>Send to:</p> <p> <input type="checkbox" name="send_to:list" value="jobs@yahoo.com"> Yahoo<br> <input type="checkbox" name="send_to:list" value="jobs@microsoft.com"> Microsoft<br> <input type="checkbox" name="send_to:list" value="jobs@mcdonalds.com"> Mc Donalds</p> <input type=submit value="Send Resume"> </form> <dtml-var standard_html_footer>
Create another DTML Method called sendresume to process the form and send the resume file:
<dtml-var standard_html_header> <dtml-in sendto> <dtml-sendmail> To: <dtml-var sequence-item> Subject: Resume <dtml-mime type=text/plain encode=7bit> Hi, please take a look at my resume. <dtml-boundary type=application/octet-stream disposition=attachment encode=base64><dtml-var expr="resume_file.read()"></dtml-mime> </dtml-sendmail> </dtml-in> <p>Your resume was sent.</p> <dtml-var standard_html_footer>
This method iterates over the sendto variable and sends one email for each item.
Notice that there is no blank line between the To:
header and
the starting mime tag. If a blank line is inserted between them
then the message will not be interpreted as a multipart message
by the receiving mail reader.
Also notice that there is no newline between the boundary tag and the var tag, or the end of the var tag and the closing mime tag. This is important, if you break the tags up with newlines then they will be encoded and included in the MIME part, which is probably not what you're after.
As per the MIME spec, mime tags may be nested within mime tags arbitrarily.
The unless tag is the opposite of the if tag. The DTML code:
<dtml-if expr="not butter"> I can't believe it's not butter. </dtml-if>
is equivalent to:
<dtml-unless expr="butter"> I can't believe it's not butter. </dtml-unless>
What is the purpose of the unless tag? It is simply a convenience tag. The unless tag is more limited than the if tag, since it cannot contain an else or elif tag.
Like the if tag, calling the unless tag by name does existence checking, so:
<dtml-unless the_easter_bunny> The Easter Bunny does not exist or is not true. </dtml-unless>
Checks for the existence of the_easter_bunny as well as its truth. While this example only checks for the truth of the_easter_bunny:
<dtml-unless expr="the_easter_bunny"> The Easter Bunny is not true. </dtml-unless>
This example will raise an exception if the_easter_bunny does not exist.
The unless tag is seldom needed.
Often you want to present a large list of information but only show it to the user one screen at a time. For example, if a user queried your database and got 120 results, you will probably only want to show them to the user a small batch, say 10 or 20 results per page. Breaking up large lists into parts is called batching. Batching has a number of benefits.
The in tag provides several variables to facilitate batch processing. Let's look at a complete example that shows how to display 100 items in batches of 10 at a time:
<dtml-var standard_html_header> <dtml-in expr="_.range(100)" size=10 start=query_start> <dtml-if sequence-start> <dtml-if previous-sequence> <a href="<dtml-var URL><dtml-var sequence-query >query_start=<dtml-var previous-sequence-start-number>"> (Previous <dtml-var previous-sequence-size> results) </a> </dtml-if> <h1>These words are displayed at the top of a batch:</h1> <ul> </dtml-if> <li>Iteration number: <dtml-var sequence-item></l1> <dtml-if sequence-end> </ul> <h4>These words are displayed at the bottom of a batch.</h4> <dtml-if next-sequence> <a href="<dtml-var URL><dtml-var sequence-query >query_start=<dtml-var next-sequence-start-number>"> (Next <dtml-var next-sequence-size> results) </a> </dtml-if> </dtml-if> </dtml-in> <dtml-var standard_html_footer>
This is a rather complex example. First let's take a look at what the viewer of this document sees, as shown in [7-8].
Now let's take a look at the DTML to get an idea of what's going on. First we have an in tag that iterates over 100 numbers that are generated by the range utility function. The size attribute tells the in tag to display only 10 items at a time. The start attribute tells the in tag which item number to display first.
Inside the in tag there are two main if tags. The first one tests
special variable sequence-start
. This variable is only true on
the first pass through the in block. So the contents of this if
tag will only be executed once at the beginning of the loop. The
second if tag tests for the special variable sequence-end
. This
variable is only true on the last pass through the in tag. So the
second if block will only be executed once at the end.
The paragraph between the if tags is executed each time through the loop.
Inside each if tag there is another if tag that check for the
special variables previous-sequence
and next-sequence
. The
variables are true when the current batch has previous or further
batches respectively. In other words previous-sequence
is true
for all batches except the first, and next-sequence
is true for
all batches except the last. So the DTML tests to see if there are
additional batches available, and if so it draws navigation links.
The batch navigation consists of links back to the document with a query_start variable set which indicates where the in tag should start when displaying the batch. To better get a feel for how this works, click the previous and next links a few times and watch how the URLs for the navigation links change.
Finally some statistics about the previous and next batches are
displayed using the next-sequence-size
and
previous-sequence-size
special variables.
Batch processing can be complex. A good way to work with batches is to use the Searchable Interface object to create a batching search report for you. You can then modify the DTML to fit your needs.
Copyright New Riders Publishing, 2000. All rights reserved. Last Modified Mar 13, 2001 3:32 pm
The contents of this chapter are licensed under the Open Publication License v1.0 without any options.