Templating an Existing Tcl Page
Templating System : MigrationIn a Nutshell
When templatizing a legacy Tcl page, your task is to separate code and graphical presentation. The latter goes into an ADP file; it contains essentially HTML, augmented by a few special tags and the@variable@
construct. The code goes into a
Tcl script. In other words, a templated page consists of two files,
a Tcl part that puts its results in data sources, and an ADP page
(the template), into which these data sources will be interpolated
to yield a complete HTML page.
General
As usual, the Tcl page should start with a call to
ad_page_contract. In its -properties
block you promise the data sources that your script will provide;
they were earlier called page properties, hence the name
of the option. Then your script performs all the computations and
queries needed to define these data sources. There are special
mechanisms for handling multirow data sources; see below.
At the end of the Tcl script, you should call
ad_return_template. The template runs after the Tcl
script, and can use these data sources.
Make sure that the fancy adp parser is enabled in your AOL ini file.
[ns/server/myserver/adp]
DefaultParser=fancy
A few more hints
- Do not write to the connection directly. Avoid
ns_puts,ns_writeetc., which don't wait till the headers are written or the page is completed; they may act differently than you expect. - If you can, put code in the Tcl file, not between
<% %>in the adp page. - Put HTML in the adp page, not in the Tcl program. Put reusable
HTML fragments in a separate adp file (think of it as a widget)
that will be
<include>d from several pages. Prefer this to writing a Tcl proc that returns HTML. - Remember to remove backslashes where you had to escape special
characters, as in
Nuts \$2.70 \[<a href="\"shoppe\">buy</a>\]
Forms
There is nothing special about building forms; just use the<form>
tag as always. All HTML tags can be used
in the ADP file (template).
A simple page
First I take a page from the news package as an example. For
simplicity, I pick item-view, which does not use a
<form>. I reformatted it a bit to make three
panes fit next to each other and to line up corresponding code.
| old Tcl code | new | |
|---|---|---|
packages/news/www/item-view.tcl | packages/news/www/item-view.adp |
|
# /packages/news/admin/index.tcl
ad_page_contract {
View a news item.
@author Jon Salz (jsalz@arsdigita.com)
@creation-date 11 Aug 2000
@cvs-id $Id$
} {
news_item_id:integer,notnull
}
db_1row news_item_select {
select *
from news_items
where news_item_id = :news_item_id
}
set body "
[ad_header $title]
<h2>$title</h2>
[ad_context_bar [list "" "News"] $title]
<hr>
<p>Released $release_date:
<blockquote>
$body
</blockquote>
[ad_footer]
"
doc_return 200 text/html $body
return
|
ad_page_contract {
View a news item.
@author Jon Salz (jsalz@arsdigita.com)
@creation-date 11 Aug 2000
@cvs-id $Id$
} {
news_item_id:integer,notnull
} -properties {
body:onevalue
release_date:onevalue
title:onevalue
header:onevalue
context_bar:onevalue
footer:onevalue
}
db_1row news_item_select {
select *
from news_items
where news_item_id = :news_item_id
}
set context_bar [ad_context_bar \
[list "" "News"] $title]
ad_return_template
|
<master>
<property name="doc(title)">@title@</property>
<property name="context">@context;literal@</property>
<hr>
<p>Released @release_date@:
<blockquote>
@body@
</blockquote>
|
Multi-Row Data Sources
Technically, the result of a query that may return multiple rows is stored in several arrays. This datasource is filled by a call todb_multirow
, and the repeating part of the HTML output
is produced by the <multiple>
tag. The following
example shows the part of the index
page of the News
module that uses the mechanism, not a whole page.
| old Tcl code | new | |
|---|---|---|
packages/news/www/index.tcl | packages/news/www/index.adp |
|
ad_page_contract {
Displays a list of
available news items.
@param archive_p show archived
news items?
@author Jon Salz (jsalz@mit.edu)
@creation-date 11 Aug 2000
@cvs-id $Id$
} {
} |
ad_page_contract {
Displays a list of available
news items.
@param archive_p show archived
news items?
@author Jon Salz (jsalz@mit.edu)
@creation-date 11 Aug 2000
@cvs-id $Id$
} {
} -properties {
header:onevalue
context_bar:onevalue
subsite_id:onevalue
subsite:multirow
item:multirow
footer:onevalue
}
| |
| ... | ... | ... |
append body "
<ul>
"
db_foreach news_items_select {
select news_item_id, title
from news_items_obj
where context_id = :subsite_id
and sysdate >= release_date
and ( expiration_date is null
or expiration_date > sysdate)
} {
append body "<li><a href="
\"item-view?news_item_id="\
"$news_item_id\"
>$title</a>\n"
} if_no_rows {
append body "<li>There are
currently no news items
available.\n"
}
append body "
<p><li>You can use the <a href=
\"admin/\">administration
interface</a> to post a new
item (there's currently no
security in place).
</ul>
" |
db_multirow item
news_items_select {
select news_item_id, title
from news_items_obj
where context_id = :subsite_id
and sysdate >= release_date
and ( expiration_date is null
or expiration_date > sysdate)
}
|
<ul>
<multiple name=item>
<li><a href=
"item-view?news_item_id=<%
%>@item.news_item_id@"
>@item.title@</a>
</multiple>
<if @item:rowcount@ eq 0>
<li>There are
currently no news items
available.
</if>
<p><li>You can use the <a href=
"admin/">administration
interface</a> to post a new
item (there's currently no
security in place).
</ul>
|
- I use the general
<if>construct to handle the case when no lines are returned. (The<multiple>loop just executes zero times.) - For a list of the available tags, refer to the templating documentation.
- Blue color marks additional syntax necessary to wrap lines short.
- The proc
db_multirowdoes have a code block and an optionalif_no_rowsblock, just likedb_foreach. They aren't used in the example, though.
If you have a more complicated db_foreach, where logic is performed inside the body, then it might be helpful to build your own multirow variable. In the excert below, taken from /pvt/alerts.tcl and /pvt/alerts.adp, the foreach logic made it hard to use the db_multirow because it needed a combination of the output from sql and also the output of Tcl procedures using that value.
| old Tcl code | new | |
|---|---|---|
packages/acs-core-ui/www/pvt/alerts.tcl | packages/acs-core-ui/www/pvt/alerts.adp |
|
ad_page_contract {
@cvs-id $Id: migration.html,v 1.4.2.2 2022/04/27 12:22:29 gustafn Exp $
} {
} |
ad_page_contract {
@cvs-id $Id: migration.html,v 1.4.2.2 2022/04/27 12:22:29 gustafn Exp $
} {
} -properties {
header:onevalue
decorate_top:onevalue
ad_footer:onevalue
discussion_forum_alert_p:onevalue
bboard_keyword_p:onevalue
bboard_rows:multirow
classified_email_alert_p:onevalue
classified_rows:multirow
gc_system_name:onevalue
}
| |
| ... | ... | ... |
if { [db_table_exists "bboard_email_alerts"] } {
set counter 0
db_foreach alerts_list "
select bea.valid_p, bea.frequency,
bea.keywords, bt.topic, bea.rowid
from bboard_email_alerts bea, bboard_topics bt
where bea.user_id = :user_id
and bea.topic_id = bt.topic_id
order by bea.frequency" {
incr counter
if { $valid_p == "f" } {
# alert has been disabled
set status "Disabled"
set action "
<a href="\"/bboard/alert-reenable\">
" Re-enable</a>"
} else {
# alert is enabled
set status "
<font color=red>Enabled</font>"
set action "
<a href="\"/bboard/alert-disable\">
" Disable</a>"
}
append existing_alert_rows "<tr>
<td>$status</td>
<td>$action</td>
<td>$topic</td>
<td>$frequency</td>"
if { [bboard_pls_blade_installed_p] == 1 } {
append existing_alert_rows "
<td>\"$keywords\"</td>"
}
append existing_alert_rows "</tr>\n"
}
if { $counter > 0 } {
set wrote_something_p 1
set keyword_header ""
if { [bboard_pls_blade_installed_p] == 1 } {
set keyword_header "<th>Keywords</th>"
}
append page_content "
<h3>Your discussion forum alerts</h3>
<blockquote>
<table>
<tr>
<th>Status</th>
<th>Action</th>
<th>Topic</th>
<th>Frequency</th>
$keyword_header
</tr>
$existing_alert_rows
</table>
</blockquote>
"
}
}
|
set discussion_forum_alert_p 0
if { [db_table_exists "bboard_email_alerts"] } {
set discussion_forum_alert_p 1
set rownum 0
if { [bboard_pls_blade_installed_p] == 1 } {
set bboard_keyword_p 1
} else {
set bboard_keyword_p 0
}
db_foreach alerts_list "
select bea.valid_p, bea.frequency,
bea.keywords, bt.topic, bea.rowid
from bboard_email_alerts bea, bboard_topics bt
where bea.user_id = :user_id
and bea.topic_id = bt.topic_id
order by bea.frequency" {
incr rownum
if { $valid_p == "f" } {
# alert has been disabled for some reasonset bboard_rows:[set rownum](status) "disable"
set bboard_rows:[set rownum](action_url) "
/bboard/alert-reenable"
} else {
# alert is enabledset bboard_rows:[set rownum](status) "enable"
set bboard_rows:[set rownum](action_url) "
/bboard/alert-disable"
}
set bboard_rows:[set rownum](topic) $topic
set bboard_rows:[set rownum](frequency) $frequency
set bboard_rows:[set rownum](keywords) $keywords
} if_no_rows {
set discussion_forum_alert_p 0
}
set bboard_rows:rowcount $rownum
}
|
<if @discussion_forum_alert_p@ eq 1>
<h3>Your discussion forum alerts</h3>
<blockquote>
<table>
<tr><th>Status</th>
<th>Action</th>
<th>Topic</th>
<th>Frequency</th>
<if @bboard_keyword_p@ eq 1>
<th>Keyword</th>
</if>
</tr>
<multiple name=bboard_rows>
<tr>
<if @bboard_rows.status@ eq "enabled">
<td><font color=red>Enabled</font></td>
<td><a href="@bboard_rows.action_url@">
Disable</a></td>
</if>
<else>
<td>Disabled</td>
<td><a href="@bboard_rows.action_url@">
Re-enable</a></td>
</else>
<td>@bboard_rows.topic@</td>
<td>@bboard_rows.frequency@</td>
<if @bboard_rows.bboard_keyword_p@ eq 1>
<td>@keyword</td>
</if>
</tr>
</multiple>
</table>
</blockquote>
</if>
|
Christian Brechbühler, Hiro Iwashima Last modified: $Id: migration.html,v 1.4.2.2 2022/04/27 12:22:29 gustafn Exp $