0% found this document useful (0 votes)
8 views

Advanced UI Design Using XML and XSL

This document is part one of a series on Advanced UI Design using XML and XSL, focusing on creating a folder tree component. It details the architecture, XML structure, and XSL style sheet necessary for building a dynamic folder tree interface that allows for user interactions such as expanding and collapsing elements. The document also includes code examples and discusses future enhancements for web-based folder trees.

Uploaded by

Jaspreet Walia
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

Advanced UI Design Using XML and XSL

This document is part one of a series on Advanced UI Design using XML and XSL, focusing on creating a folder tree component. It details the architecture, XML structure, and XSL style sheet necessary for building a dynamic folder tree interface that allows for user interactions such as expanding and collapsing elements. The document also includes code examples and discusses future enhancements for web-based folder trees.

Uploaded by

Jaspreet Walia
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 40

15 Seconds : Advanced UI Design Using XML and XSL -

Part 1:
Folder Tree Creation
Joe Slovinski
09/21 / 2001

Welcome to Advanced UI Design Using XML and XSL. Each article in my series will
demonstrate the creation of a specific user interface (UI) component using eXtensible
Markup Language (XML) and eXtensible Stylesheet Language (XSL). This article
explains how to create a folder tree of unlimited depth by using XML and XSL, the style-
sheet language for XML. The folder tree discussed in this article functions by taking an
XML feed of a specific format and applying an XSL style sheet to it. The resulting
transformation is then printed to the client. The client handles all requests to expand,
collapse, maximize or minimize entities within the tree, or the who le tree itself. The
client used in this article is Internet Explorer 5.5+.

The actual folder tree demonstrated is available for download at the end of this article.

Folder -Tree-Specific Architecture

Like most Web page objects, there are several ways to build them. When developing your
specific folder tree, you should keep in mind some architectural decisions adherent to a
folder tree such as:

1. Nesting
2. Lines

Nesting
Figure 1. No nesting within the browser Figure 2. Nesting within the browser

In Both Figures 1 and 2 borders were temporarily applied to the tree so that one can more
easily see the relationships and nesting of objects within the Document Object Model
(DOM).

Lines

Lines within your tree can provide a greater visual effect and make larger tree structures
easier to read. The negative effect on having lines within your tree is performance. A tree
with 100 items may use about 300 images just to display the relationship between
entities. Caching and preloading images can help, but they still exist as individual objects
within the DOM. Without lines, simple indenting can show the relationship between
parent and child. In this article I will demonstrate the creation of a tree without lines.

XML Structure

All corporations' requirements differ from one another. Odds are the requirements I have
for my tree are much different than the requirements you may have for yours. XML
provides us with the perfect interface to describe our unique tree entities. The format you
choose will have a direct effect on your XSL style sheets and your client operations. The
format I have chosen is good for a recursive XSL style sheet, which satisfies the
requirement within this article for a folder tree of unlimited depth.

The XML document I have constructed contains a root element named "tree" that can
contain only "entity" elements. The structure of your tree is implicitly defined here by
nesting entity elements within the "contents" element of other entity elements. Below is a
listing of all elements or attributes that belong to the "entity" element.

Name Type Description


id Attribute Unique string or integer used to identify the individual entity
Description of individual entity. This is the text displayed to the
description Element
user.
onClick Element Name of the client-side function fired upon the onClick event
image Element Image displayed when entity is closed or not selected
imageOpen Element Image displayed when entity is open
Contains entity elements. The content element is used to determine
contents Element
whether or not an entity has children.

Within your individual trees, use the entity element to append specific data about each
object in your tree. This could be the ID of a record in a database that the entity is
representing. In upcoming articles I will demonstrate how to create an entity-specific
context menu that is implemented by the addition of an "oncontextmenu" element to our
entity element.

The following XML is the XML we will be using in this article. For illustration purposes
I have created a static XML document instead of implementing a dynamic XML database
query.

<?xml version="1.0"?>
<tree>
<entity id="e1">
<description>Customers</description>
<oncontextmenu></oncontextmenu>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<contents>
<entity id="e2">
<description>Microsoft</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick>displayCustomer(12345)</onClick>
<contents>
<entity id="e3">
<description>Orders</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick></onClick>
<contents/>
</entity>
</contents>
</entity>
<entity id="e4">
<description>IBM</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick>displayCustomer(12346)</onClick>
<contents>
<entity id="e5">
<description>Orders</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick></onClick>
<contents/>
</entity>
</contents>
</entity>
<entity id="e6">
<description>Sun Microsystems</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick>displayCustomer(12347)</onClick>
<contents>
<entity id="e7">
<description>Orders</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick></onClick>
<contents>
<entity id="e8">
<description>#12345</description>
<image>images/paper.gif</image>
<imageOpen>images/paper.gif</imageOpen>
<onClick></onClick>
<contents/>
</entity>
<entity id="e9">
<description>#12346</description>
<image>images/paper.gif</image>
<imageOpen>images/paper.gif</imageOpen>
<onClick></onClick>
<contents/>
</entity>
</contents>
</entity>
</contents>
</entity>
<entity id="e10">
<description>Oracle</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick>displayCustomer(12348)</onClick>
<contents>
<entity id="e11">
<description>Orders</description>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick></onClick>
<contents/>
</entity>
</contents>
</entity>
</contents>
</entity>
<entity id="e12">
<description>Reports</description>
<oncontextmenu></oncontextmenu>
<image>images/book.gif</image>
<imageOpen>images/bookOpen.gif</imageOpen>
<contents>
<entity id="e13">
<description>Income</description>
<oncontextmenu></oncontextmenu>
<image>images/paper.gif</image>
<imageOpen>images/paper.gif</imageOpen>
<contents>
</contents>
</entity>
<entity id="e14">
<description>Expenses</description>
<oncontextmenu></oncontextmenu>
<image>images/paper.gif</image>
<imageOpen>images/paper.gif</imageOpen>
<contents>
</contents>
</entity>
</contents>
</entity>
</tree>

The above XML file is named "tree.xml" and is available for download at the end of this
article.

XSL Style Sheet

Both Figures 1 and 2 were the result of transformations with our XML document and
separate XSL style sheets. Below is the standard XSL style sheet that is applied to our
XML document(s).

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"
language="JavaScript">

<xsl:template match="tree">
<xsl:apply-templates select="entity"/>
</xsl:template>

<xsl:template match="entity">
<div onclick="window.event.cancelBubble = true;clickOnEntity(this);"
onselectstart="return false" ondragstart="return false">
<xsl:attribute name="image"><xsl:value-of
select="image"/></xsl:attribute>
<xsl:attribute name="imageOpen"><xsl:value-of
select="imageOpen"/></xsl:attribute>
<xsl:attribute name="open">false</xsl:attribute>
<xsl:attribute name="id">f<xsl:value-of
select="@id"/></xsl:attribute>
<xsl:attribute name="open">false</xsl:attribute>
<xsl:attribute name="STYLE">
padding-left: 20px;
cursor: hand;
<xsl:if expr="depth(this) > 2">
display: none;
</xsl:if>
</xsl:attribute>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td valign="middle">
<img border="0" id="image">
<xsl:attribute name="SRC">
<xsl:value-of select="image"/>
</xsl:attribute>
</img>
</td>
<td valign="middle" nowrap="true">
<xsl:attribute name="STYLE">
padding-left: 7px;
font-family: Verdana;
font-size: 11px;
font-color: black;
</xsl:attribute>
<xsl:value-of select="description"/></td>
</tr>
</table>
<xsl:apply-templates select="contents/entity"/>
</div>
</xsl:template>

</xsl:stylesheet>

Figure 3 (below) displays the folder tree that is created using the above XSL style sheet.

Figure 3. XSL transformation

Client Operatio ns

In order for our folder tree to function, we require the following client-side operations:

1. Initialize
2. Expand
3. Collapse
4. Expand All (Maximize)
5. Collapse All (Minimize)
Below is the code that performs these five operations. This code can be found within the
"tree.js" file included with this article.
function initialize() {
var xmlDoc
var xslDoc

xmlDoc = new ActiveXObject('Microsoft.XMLDOM')


xmlDoc.async = false;

xslDoc = new ActiveXObject('Microsoft.XMLDOM')


xslDoc.async = false;

xmlDoc.load("tree/tree.xml")
xslDoc.load("tree/tree.xsl")

folderTree.innerHTML = xmlDoc.documentElement.transformNode(xslDoc)
}

function clickOnEntity(entity) {
if(entity.open == "false") {
expand(entity, true)
}
else {
collapse(entity)
}
window.event.cancelBubble = true
}

function expand(entity) {
var oImage

oImage = entity.childNodes(0).all["image"]
oImage.src = entity.imageOpen

for(i=0; i < entity.childNodes.length; i++) {


if(entity.childNodes(i).tagName == "DIV") {
entity.childNodes(i).style.display = "block"
}
}
entity.open = "true"
}

function collapse(entity) {
var oImage
var i

oImage = entity.childNodes(0).all["image"]
oImage.src = entity.image

// collapse and hide children


for(i=0; i < entity.childNodes.length; i++) {
if(entity.childNodes(i).tagName == "DIV") {
if(entity.id != "folderTree")
entity.childNodes(i).style.display = "none"
collapse(entity.childNodes(i))
}
}
entity.open = "false"
}

function expandAll(entity) {
var oImage
var i

expand(entity, false)

// expand children
for(i=0; i < entity.childNodes.length; i++) {
if(entity.childNodes(i).tagName == "DIV") {
expandAll(entity.childNodes(i))
}
}
}

Future of Web-Based Folder Trees

As Web technologies grow, they allow us to maintain state within our Web applications.
This increases the complexity of our interfaces as more operations are moved from server
to client. This is a blessing that will increase the usability of our interfaces and the
performance of our applications by taking the load off of our servers.

In future articles I may expand on the tree discussed in this article by explaining how to
create an API to dynamically insert, remove, rename, and refresh objects within your
trees.

Closing

I hope the contents of this article will help increase the quality of your Web applications
interface. If you have any questions, comments, or suggestions, please feel free to email
me at the address listed in the author section.

On a side note, I would like to thank Lee McGraw for helping me proofread and validate
this document.

The second article in this series addresses custom context menu creation.

Download

Download the example.zip file that contains the files that make up the tree, including
tree.xsl, tree.xml, tree.js, tree.css, common.js, and images.

15 Seconds : Advanced UI Design Using XML and XSL -


Part 2:
Custom Context Menu Creation
Joe Slovinski
09/27 / 2001

Welcome to Advanced UI Design Using XML and XSL. Each article in my series will
demonstrate the creation of a specific user interface (UI) component using eXtensible
Markup Language (XML) and eXtensible Stylesheet Language (XSL).

This article explains how to create a custom, object-specific context menu of unlimited
depth by using XML and XSLT. The context menu discussed here functions by taking an
XML feed of a specific format and applying an XSLT style sheet to it. The resulting
transformation is then printed to the client. The client handles all user requests to
navigate the menu. The client used in this article is Internet Explorer 5.5+.

This context menu can be applied to any object within your specific Web page(s). For
demonstration purposes within this article I am applying the context menu to objects
within a folder tree.

The folder tree used is the same folder tree discussed in a previous 15 Seconds article
(see http://www.15seconds.com/issue/010921.htm). As noted within the referenced
article, I have added a special element to each entity within the tree's XML. This element
is called "onContextMenu" and references a specific XML file that contains the makeup
of the requested context menu.

The actual context menu demonstrated is available in the Download section below.

Context Menu Overview

Within Windows applications it is common to right click on objects and receive object-
specific context menus. For instance, right clicking on rows within an SQL Server table,
rows within an Excel spreadsheet, or simply right clicking on your computer's desktop
produces custom context menus.

The options within these context menus are specific to the application being used, and the
object within that application that was clicked on. Early Web browsers did not give
developers the capability to create context menus for our specific objects. Recent
advances within the Document Object Model (DOM) and our Web browsers now make
this possible within Web applications.
Figure 1. An example of an object-specific context menu created within MS Internet
Explorer

This screenshot uses a number system to illustrate the different levels of the menu.

XML Structure

The XML format I have chosen is good for a recursive XSLT style sheet that satisfies the
requirement within this article for a context menu of unlimited depth.

The XML document I have constructed contains a root element named "menu" that can
contain only "entity" elements. The structure of your context menu is implicitly defined
here by nesting option elements within the "contents" element of other entity eleme nts.
Below is a listing of all elements or attributes that belong to the "option" element.

Name Type Description


id Attribute Unique string or integer used to identify the individual option
Description of individual option. This is the text displayed to the
description Element
user.
onClick Element Name of the client-side function fired upon the onClick event
image Element Image displayed when option is closed or not selected
imageOpen Element Image displayed when option is selected
Contains entity elements. The content element is used to determine
contents Element
whether or not an option has a submenu.

The following XML describes the context menu that is displayed when the user right
clicks on a customer. For illustration purposes, all XML is cont ained in static XML
documents instead of being retrieved by XML database queries.
<?xml version="1.0"?>
<menu>
<entity id="c1">
<description>Add Customer</description>
<image>images/add_small.gif</image>
<imageOpen>images/add_small.gif</imageOpen>
<contents>
<entity id="c2">
<description>Business</description>
<image>images/spacer.gif</image>
<imageOpen>images/spacer.gif</imageOpen>
<contents>
</contents>
</entity>
<entity id="c3">
<description>Individual</description>
<image>images/spacer.gif</image>
<imageOpen>images/spacer.gif</imageOpen>
<contents>
</contents>
</entity>
</contents>
</entity>
<entity id="c4">
<description>Modify Customer</description>
<image>images/modify_small.gif</image>
<imageOpen>images/modify_small.gif</imageOpen>
<contents>
</contents>
</entity>
<entity id="c5">
<description>Remove Customer</description>
<image>images/x_small.gif</image>
<imageOpen>images/x_small.gif</imageOpen>
<contents>
</contents>
</entity>
</menu>

The above XML file is named "contextCustomer.xml" and is available for download at
the end of this article.

XSL Style Sheet

Below is the standard XSLT style sheet that is applied to our XML documents.

<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<xsl:template match="menu">
<div style="position: absolute;">
<div onselectstart="return false" ondragstart="return false">
<xsl:attribute name="STYLE">
position: absolute;
background-color: #6699cc;
border:1px solid #99ccff;
</xsl:attribute>
<table border="0" cellspacing="0" cellpadding="1">
<tr>
<td>
<table border="0" cellspacing="0" cellpadding="0">
<xsl:apply-templates select="entity"/>
</table>
</td>
</tr>
</table>
</div>
<xsl:apply-templates select="entity/contents"/>
</div>
</xsl:template>

<xsl:template match="entity">
<TR>
<xsl:attribute name="selected">false</xsl:attribute>
<xsl:attribute name="background">#6699cc</xsl:attribute>
<xsl:attribute name="light">#99ccff</xsl:attribute>
<xsl:attribute name="titlebar">#5389bc</xsl:attribute>
<xsl:attribute name="image">images/<xsl:value-of
select="image"/></xsl:attribute>
<xsl:attribute name="imageOpen">images/<xsl:value-of
select="imageOpen"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
<xsl:attribute name="ONCLICK"><xsl:value-of
select="onClick"/>;clean()</xsl:attribute>
<xsl:attribute name="ONMOUSEOVER">
contextHighlightRow(this);
<xsl:if test="contents/node()[count(child::*)>0]">
loadContextMenuSub(this)
</xsl:if>
</xsl:attribute>
<xsl:attribute
name="ONMOUSEOUT">contextHighlightRow(this)</xsl:attribute>
<TD VALIGN="MIDDLE" ALIGN="CENTER" NOWRAP="true">
<xsl:attribute name="ONCLICK"><xsl:value-of
select="@onmousedown"/></xsl:attribute>
<xsl:attribute name="STYLE">
background-color: #5389bc;
border-top:1px solid #5389bc;
border-bottom:1px solid #5389bc;
border-left:1px solid #5389bc;
padding-left: 4px;
padding-right: 4px;
padding-top: 4px;
padding-bottom: 3px;
cursor: default;
</xsl:attribute>
<IMG BORDER="0" HEIGHT="15" WIDTH="15">
<xsl:attribute name="SRC"><xsl:value-of
select="image"/></xsl:attribute>
</IMG></TD>
<TD NOWRAP="true">
<xsl:attribute name="ONCLICK"><xsl:value-of
select="@onmousedown"/></xsl:attribute>
<xsl:attribute name="STYLE">
font-family: Arial;
font-size: 11px;
font-weight: normal;
color: white;
background-color: #6699cc;
border-top: 1px solid #6699cc;
border-bottom: 1px solid #6699cc;
padding-top: 2px;
padding-bottom:2px;
padding-left: 6px;
padding-right: 8px;
cursor: default;
</xsl:attribute>
<xsl:value-of select="description"/></TD>
<TD VALIGN="middle" ALIGN="right" STYLE="padding-right: 6px;"
NOWRAP="true">
<xsl:attribute name="ONCLICK"><xsl:value-of
select="@onmousedown"/></xsl:attribute>
<xsl:attribute name="STYLE">
background-color: #6699cc;
border-top: 1px solid #6699cc;
border-bottom: 1px solid #6699cc;
border-right: 1px solid #6699cc;
padding-right: 5px;
</xsl:attribute>
<IMG BORDER="0" WIDTH="4">
<xsl:attribute name="SRC">
<xsl:choose>
<xsl:when test="contents/node()[count(child::*)>0]">
images/opensub.gif
</xsl:when>
<xsl:otherwise>
images/spacer.gif
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</IMG></TD>
</TR>
</xsl:template>

<xsl:template match="contents">
<xsl:if test="count(child::*)>0">
<div onselectstart="return false" ondragstart="return false">
<xsl:attribute name="STYLE">
position: absolute;
background-color: #6699cc;
border:1px solid #99ccff;
display: none;
</xsl:attribute>
<xsl:attribute name="ID"><xsl:value-of
select="../@id"/>Sub</xsl:attribute>
<table border="0" cellspacing="0" cellpadding="1">
<tr>
<td>
<table border="0" cellspacing="0" cellpadding="0">
<xsl:apply-templates select="entity"/>
</table>
</td>
</tr>
</table>
</div>
<xsl:apply-templates select="entity/contents"/>
</xsl:if>
</xsl:template>

</xsl:stylesheet>

Figure 2 displays a context menu that is created using the files included in this article.

Figure 2. XSL transformation

Client Operations

In order for the context menu to function, the following client-side operations are
required:

1. Load context menu


2. Load context submenu
3. Highlight row
4. Clean (Remove any open context menus)
Below is the code that performs these four operations. This code can be found within the
"context.js" file (see the Download section).
var appState = new applicationState()

function applicationState() {
this.contextMenu = null
}

function loadContextMenu(path) {
var xmlDoc
var xslDoc
var contextMenu

if(path != "") {
xmlDoc = new ActiveXObject('Microsoft.XMLDOM')
xmlDoc.async = false;

xslDoc = new ActiveXObject('Microsoft.XMLDOM')


xslDoc.async = false;

xmlDoc.load(path)
xslDoc.load("context/context.xsl")

if(appState.contextMenu != null)
appState.contextMenu.removeNode(true)

document.body.insertAdjacentHTML("beforeEnd",
xmlDoc.documentElement.transformNode(xslDoc))
contextMenu =
document.body.childNodes(document.body.childNodes.length-1)

contextMenu.style.left = window.event.x
contextMenu.style.top = window.event.y

appState.contextMenu = contextMenu
window.event.cancelBubble = true
}
}

function loadContextMenuSub(obj) {
var contextMenu
var parentMenu

parentMenu = returnContainer(obj)
contextMenu = document.all[obj.id + "Sub"]
contextMenu.style.display = "block"
contextMenu.style.top = obj.offsetTop + parentMenu.style.pixelTop
contextMenu.style.left = obj.offsetWidth + parentMenu.style.pixelLeft
parentMenu.subMenu = contextMenu
}

function contextHighlightRow(obj) {
var parentMenu
var subMenu
var i
parentMenu = returnContainer(obj)

if(obj.selected == "false") {
for(i=0; i < obj.childNodes.length; i++) {
obj.childNodes(i).style.borderTop = "1px solid white"
obj.childNodes(i).style.borderBottom = "1px solid white"

if(obj.childNodes(i).cellIndex == 0) {
obj.childNodes(i).style.borderLeft = "1px solid white"
}
else if (obj.childNodes(i).cellIndex == obj.cells.length-1) {
obj.childNodes(i).style.borderRight = "1px solid white"
}
}

if(parentMenu.subMenu != null && parentMenu != parentMenu.subMenu)


{
subMenu = parentMenu.subMenu

while(subMenu != null) {
subMenu.style.display = "none"
subMenu = subMenu.subMenu
}
}
obj.selected = "true"
}
else {
for(i=0; i < obj.childNodes.length; i++) {
if(i == 0) {
obj.childNodes(i).style.borderTop = "1px solid " + obj.titlebar
obj.childNodes(i).style.borderBottom = "1px solid " +
obj.titlebar
}
else {
obj.childNodes(i).style.borderTop = "1px solid " +
obj.background
obj.childNodes(i).style.borderBottom = "1px solid " +
obj.background
}

if(obj.childNodes(i).cellIndex == 0) {
obj.childNodes(i).style.borderLeft = "1px solid " +
obj.titlebar
}
else if (obj.childNodes(i).cellIndex == obj.cells.length-1) {
obj.childNodes(i).style.borderRight = "1px solid " +
obj.background
}
}
obj.selected = "false"
}
}

function clean() {
var contextMenu
// remove and destroy context menu
if(appState.contextMenu != null) {
contextMenu = appState.contextMenu.removeNode(true)
contextMenu = null
}
}

function returnContainer(container) {
while(container.tagName != "DIV") {
container = container.parentNode
}
return container
}

Conclusion

I hope the contents of this article will help increase the quality of your Web applications'
interface. If you have any questions, comments, or suggestions, please feel free to e- mail
me at the address listed in the About the Author section.

On a side note, I would like to thank Lee McGraw for helping me proofread and validate
this document.

The third article in this series expands upon the first article to illustrate how to insert,
modify, delete, and rename items within the folder tree.

Download

View a live demo of the Context Menu (Works only in IE 5.5+ browsers)

Download the example.zip file that contains the makeup of the context menu, including
all XML, XSL, JS, CSS, HTML, and graphics files

15 Seconds : Advanced UI Design Using XML and XSL -


Part 3:
Folder Tree Administration
Joe Slovinski
11/13 / 2001

Welcome to Advanced UI Design Using XML and XSL. Each article in my series will
demonstrate the creation of a specific user interface (UI) component using eXtensible
Markup Language (XML) and eXtensible Stylesheet Language (XSL). This article
expands on a previous article in this series, Folder Tree Creation (see
http://www.15seconds.com/issue/010921.htm). The folder tree discussed in this article
illustrates how to insert, modify, delete, and rename items within the tree. All operations
are performed on the client, thus reducing the load on your server. The client used in this
article is Internet Explorer 5.5+. The actual folder tree demonstrated is available in the
Download section below.

Folder Tree Updates

Since the last article, I have made the following updates.

1. Automated IDs
2. Unlimited Metadata Support
3. Insert/Update Capability
4. Rename Capability
5. Delete Capability

Automated IDs

Each entity within our tree requires a unique ID. In the previous article the task of
assigning these ID's was left to the user. In this article I have automated this task at
runtime.

In this version of the folder tree, unique IDs are assigned via an XSL transformation
immediately before rendering the tree.

<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dt="urn:schemas-microsoft-com:datatypes">

<xsl:template match="/tree">
<xsl:element name="tree">
<xsl:apply-templates select="entity"/>
</xsl:element>
</xsl:template>

<xsl:template match="entity">
<xsl:element name="entity">
<xsl:attribute name="id">
<xsl:value-of select="generate-id(.)"/>
</xsl:attribute>
<xsl:for-each select="*">
<xsl:if test="name() = 'contents'">
<xsl:element name="contents">
<xsl:apply-templates select="entity"/>
</xsl:element>
</xsl:if>
<xsl:if test="name() != 'contents'">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

The style sheet traverses our tree.xml file and uses the generate- id() method to assign a
unique ID to each individual entity.

We should note that if this were an n-tier application and our entities were being pulled
out of a database, we would not need this process because each entity representing a row
in our database would already have a unique identity field.

Unlimited Metadata Support

All tree operations allow for descriptive metadata specific to your requirements. This
means elements of any name and value can be added to describe the entity.

For example, below is customer XML from the last article. Also, you'll notice the
difference between the two is the customer-specific metadata, such as contact, address
and phone. My folder tree will automatically read and interpret this new metadata. Also
note that due to "image" being a reserved word I was required to change the "image"
entities name to "imageBase".

<entity id="e2">
<description>Microsoft</description>
<imageBase>images/book.gif</imageBase>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick>displayCustomer(12345)</onClick>
<onContextMenu>context/contextCustomer.xml</onContextMenu>
<contents>
</contents>
</entity>

Below is sample customer XML from this article.


<entity id="e2">
<description>Microsoft</description>
<imageBase>images/book.gif</imageBase>
<imageOpen>images/bookOpen.gif</imageOpen>
<onClick>displayCustomer(12345)</onClick>
<onContextMenu>context/contextCustomer.xml</onContextMenu>
<contact>Bill Gates</contact>
<address1>1234 Microsoft Dr.</address>
<address2>Suite 123</address>
<city>Microsoft City</city>
<state>MS</state>
<zip>12345</zip>
<phone>(123)132-1234</phone>
<contents>
</contents>
</entity>
You'll notice the difference between the two is the customer-specific metadata, such as
contact, address, and phone. My folder tree will automatically read and interpret this new
metadata.

With the current folder tree architecture, child entities inherit from their parents. For
instance, when you right-click on the Customer Folder and select Insert, you will get a
child with all the metadata that the Customer Folder has.

Insert/Update Capability

Inserting and updating are very similar because both operations deal with the exact same
set of data and use the exact same form. The exceptions are an insert form initializing
blank, meaning with empty input elements, and an update form initializing with values.
Because of these similarities I was able to combine both insert and update functionality in
one XSLT style sheet and one display function, and then separate operational calls

Insert Update XSLT Forms

The actual XSLT style sheet that creates our forms follows.

<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<xsl:param name="action"/>
<xsl:param name="selectedEntity"/>

<xsl:template match="entity">
<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="1" WIDTH="100%">
<xsl:for-each select="*">
<xsl:if test="name() != 'contents'">
<TR>
<TD CLASS="dataLabel" STYLE="border-right: 1px solid black;
border-bottom: 1px solid black;">
<xsl:value-of select="name()"/>
</TD>
<TD STYLE="border-right: 1px solid black; border-bottom:
1px solid black;" WIDTH="100%">
<INPUT CLASS="dataInput"
ONFOCUS="document.body.onselectstart = null"
ONBLUR="document.body.onselectstart = returnFalse;">
<xsl:attribute name="NAME">
<xsl:value-of select="name()"/>
</xsl:attribute>
<xsl:if test="$action = 'update'">
<xsl:attribute name="VALUE">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:if>
</INPUT>
</TD>
</TR>
</xsl:if>
</xsl:for-each>
<TR>
<TD STYLE="padding-right: 0px;"/>
<TD STYLE="padding-top: 0px;padding-left: 0px;">
<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0">
<TR>
<TD>
<INPUT TYPE="button" CLASS="buttonOff" NAME="Save"
VALUE="Save" ONFOCUS="this.blur();"
ONMOUSEOVER="swapClass(this, 'buttonOver')" ONMOUSEOUT="swapClass(this,
'buttonOff')">
<xsl:attribute name="ONCLICK">
<xsl:value-of select="$action"/>Entity('<xsl:value-of
select="$selectedEntity"/>')
</xsl:attribute>
</INPUT>
</TD>
<TD STYLE="padding-left: 2px;">
<INPUT TYPE="button" CLASS="buttonOff" NAME="Cancel"
VALUE="Cancel"
ONFOCUS="this.blur();" ONMOUSEOVER="swapClass(this, 'buttonOver')"
ONMOUSEOUT="swapClass(this, 'buttonOff')" ONCLICK="content.innerHTML =
'';"/>
</TD>
</TR>
</TABLE>
</TD>
</TR>
</TABLE>
</xsl:template>
</xsl:stylesheet>

This XSLT style sheet accepts two parameters called "action" and "selectedEntity". Valid
values for the action parameter are "insert" and "update". The selectedEntity parameter
takes an entity ID va lue.
Figure 1. Resulting XSLT Transformation

Insert/Update Display Function

This function receives an action parameter of value "insert" or "update." It then uses the
selectedEntity variable to figure out which entity the insert or update will be performed
on. It uses the above XSLT style sheet to render the appropriate form.

function insertUpdateDisplay(action) {
var xslDoc
var xslTemplate;
var xslProc;
var entity;

xslDoc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument')


xslDoc.async = false;

xslTemplate = new ActiveXObject('MSXML2.XSLTemplate')

xslDoc.load("admin/insertUpdate.xslt");
xslTemplate.stylesheet = xslDoc;
xslProc = xslTemplate.createProcessor();
entity = xmlDoc.documentElement.selectSingleNode("//entity[@id='" +
selectedEntity +"']");
xslProc.input = entity;

xslProc.addParameter("action", action);
xslProc.addParameter("selectedEntity", selectedEntity);
xslProc.transform();

content.innerHTML = xslProc.output;
}

Insert Operation

This function receives the entity ID of the "parent to be." The function then creates a
replica of the parent entity. Once the child is created, it is appended to the parent's
contents collection.

function insertEntity(parentEntityID) {
var entity;
var newEntity;
var element;
var attribute;
var xslDoc;
var i;

xslDoc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument')


xslDoc.async = false;

xslDoc.load("admin/tree.xslt");

entity = xmlDoc.documentElement.selectSingleNode("//entity[@id='" +
parentEntityID +"']");
newEntity = xmlDoc.createElement("entity");
attribute = xmlDoc.createAttribute("id");
attribute.text = document.uniqueID;
newEntity.attributes.setNamedItem(attribute);

for(i=0; i < entity.childNodes.length; i++) {


element = xmlDoc.createElement(entity.childNodes(i).baseName);
if(entity.childNodes(i).baseName != "contents") {
element.text = eval(entity.childNodes(i).baseName + ".value")
}
newEntity.appendChild(element)
}
entity.selectSingleNode("contents").appendChild(newEntity);
document.all[parentEntityID].insertAdjacentHTML("beforeEnd",
newEntity.transformNode(xslDoc));
document.all[parentEntityID].lastChild.style.display = "block";

if(document.all[parentEntityID].open == "false") {
document.all[parentEntityID].onclick();
}
saveXML();
}

Update Operation
This function receives the ID of the entity that needs updating. The function then loops
through the entities while updating values pulled from the current form. Once the entity
XML is updated, it is retransformed and swapped with the existing node in the browser
DOM.

function updateEntity(entityID) {
var entity;
var xslDoc;
var container;
var i;

xslDoc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument')


xslDoc.async = false;

xslDoc.load("admin/tree.xslt");

entity = xmlDoc.documentElement.selectSingleNode("//entity[@id='" +
entityID +"']");

for(i=0; i < entity.childNodes.length; i++) {


if(entity.childNodes(i).baseName != "contents") {
entity.childNodes(i).text = eval(entity.childNodes(i).baseName +
".value")
}
}
container = document.createElement("DIV");
container.innerHTML = entity.transformNode(xslDoc)

container.childNodes(0).style.display = "block";
document.all[entityID].swapNode(container.childNodes(0));
container = null;
saveXML();
}

Rename Capability

When a user selects the rename option of an entity the renameEntityBegin() function is
called. It then uses the selectedEntity variable to determine which entity is to be renamed.

The method first gains a reference called "name" to the browser object that displays the
current name of the entity. Once this reference is established, we then set the
contentEditable property to true. This allows the user to freely type within this space.

We also set the focus to this element, which moves the cursor to the beginning of the
name. After setting several events that restrict keystrokes and allow selecting of text, the
user is ready to rename the entity.

function renameEntityBegin() {
var name;

name = document.all[selectedEntity + "name"]


name.contentEditable = true;
name.focus();
name.style.cursor = "text";
name.onkeypress = function anonymous() {
renameKeyPress(selectedEntity) };
name.onblur = function anonymous() { renameEntityEnd(selectedEntity)
};
name.onselectstart = null;

document.all[selectedEntity].onclick = null;
}

If the user presses the Enter key or fires the onBlur event, the renameEntityEnd method is
called.

This method receives the ID of the entity that is to be renamed. It makes the appropriate
changes to both the XML Document Object Model (DOM) and the browser DOM.

function renameEntityEnd(entityID) {
var entity;
var name;

entity = xmlDoc.documentElement.selectSingleNode("//entity[@id='" +
entityID +"']");
name = document.all[entityID + "name"]
name.style.cursor = "hand";
name.contentEditable = false;
name.onselectstart = function anonymous() { return false };

entity.selectSingleNode("description").text = name.innerText;

document.all[entityID].onclick = function anonymous() {


clickOnEntity(document.all[entityID]) };
document.body.onselectstart = returnFalse;
saveXML();
}
Figure 2. Renaming an Entity

Delete Capability

The delete entity receives the ID of the entity that is to be deleted. The function first
checks to make sure the entity does not contain children. If the entity contains children,
an error will be displayed.

After performing this check, this function then removes the entities node in both the
XML DOM and the browser DOM.

function deleteEntity() {
var entity;

entity = xmlDoc.documentElement.selectSingleNode("//entity[@id='" +
selectedEntity +"']");

if(entity.selectSingleNode("contents").childNodes.length > 0) {
displayError("You cannot remove an entity that contains children.
First remove all children."

., 8000);
}
else {
entity = entity.parentNode.removeChild(entity)
document.all[selectedEntity].removeNode(true)
saveXML();
}
}

Redirecting the User

The version of the tree available for download within this article includes a simple
redirect method. This method receives a URL parameter of type string. You must call the
redirect method from within your tree XML file. Figure 3 highlights the location of the
onClick element used to call the redirect method.

Figure 3. Location of onClick Element

Figure 4 highlights the line within tree.xslt that inserts your onClick event during
transformation.

Figure 4 Inserting the onClick Event

Interfacing with a Database


If you are required to interface with a database, I can make the following
recommendations. Use the XMLHTTP object within the init(), insertEntity(),
updateEntity(), deleteEntity(), and renameEntityEnd() methods. These methods should
contact your Web service and retrieve or send only the XML that is required to perform
the requested operation.

This approach will reduce load on your server tremendously. Instead of having to do a
round trip back to the server, request the whole tree, and then render the whole tree again,
you can make one easy behind-the-scenes call that updates your data, then updates only
the area of the client browser that is required for your operation.

Closing

I hope the contents of this article will help increase the quality of your Web applications
interface. If you have any questions, comments, or suggestions, please feel free to email
me at the address listed in the author section.

Demo and Download

View a live demo of the folder tree (Works only in IE 5.5+ browsers).

Download the newest version of the folder tree by clicking the example.zip hyperlink.
This version includes both administration and viewing versions of the folder tree.

15 Seconds : Advanced UI Design Using XML and XSL -


Part 4:
Folder Tree Drag and Drop
Joe Slovinski
11/29 / 2001

Welcome to Advanced UI Design Using XML and XSL. Each article in my series will
demonstrate the creation of a specific user interface (UI) component using eXtensible
Markup Language (XML) and eXtensible Stylesheet Language (XSL). This article
expands on a previous one in this series, Folder Tree Creation (see
http://www.15seconds.com/issue/010921.htm). This article explains how to drag and
drop entities of a given folder tree within that tree or into another separate tree.

Drag-and-Drop Control

Our drag-and-drop control exists on the client and is defined as follows:

function DragControl() {
this.entity = null;
this.target = null;
this.origin = null;
this.enabled = false;

this.beginX = null;
this.beginY = null;

this.beginDrag = beginDrag;
this.endDrag = endDrag;
this.setTarget = setTarget;
this.setPosition = setPosition;
this.move = move;
this.reset = reset;
}

Our control has the following members:

Properties

entity
The entity property contains a reference to the object which is to be moved from one
location to another, whether within its current tree or within another. This property is set
within the beginDrag() method.

target
The target property contains a reference to the current target of the user's drag and drop.
The property is changed as the user moves over entities. This property is set within the
setTarget() method.

origin
The origin property contains a reference to the folder from which the object was dragged.
Upon the release of the left mouse button without a target, the entity is reverted back to
this location. The origin property is set with the beginDrag() method.

enabled
The enabled property initiates the actual drag and drop. This property is set to true when
the user drags their mouse a certain amount of pixels with a selected entity. Within this
code version, our drag range for initiation is 5 pixels. This property is changed within the
move() method.

beginX
This property contains the X coordinate of the mouse at the time of the initiation of the
drag-and-drop routine. The beginX property is set within the beginDrag() method.

beginY
This property contains the Y coordinate of the mouse at the time of the initiation of the
drag-and-drop routine. The beginY property is set within the beginDrag() method.

Methods
beginDrag()
The beginDrag() method is defined as follows.

function beginDrag(obj) {
if(window.event.button == 1) {

document.onmousemove = function anonymous() { dragControl.move(obj)


};

this.origin = obj.parentNode;
this.entity = obj;

this.beginX = window.event.x;
this.beginY = window.event.y;

window.event.cancelBubble = true;
}
}

This method is attached to our entities within our XSL Transformations (XSLT) style
sheet.

Figure 1. beginDrag() method placement

This method first checks to ensure that the left mouse button fired the event, instead of
the right (which is used for context menus). It then sets the proper property values and
cancels the bubbling of events within the client browser.

endDrag()
The endDrag() method is defined follows:

function endDrag() {
if(this.entity != null) {
this.entity.style.position = "static";
this.entity.style.left = null;
this.entity.style.top = null;
this.entity = this.entity.removeNode(true);

if(this.target != null) {
dragControl.target.appendChild(this.entity);
if(this.target.open != "true") {
clickOnEntity(this.target);
}
}
else {
this.origin.appendChild(this.entity);
}

document.onmousemove = null;
document.onmouseup = null;

this.entity = null;
this.target = null;
this.enabled = false;
}
}

This method first ensures that there is an entity selected. Depending on whether or not the
user currently has a target selected, it either returns the entity to its origin (given no
target) or moves the entity to its new location (given a target).

This method is attached to our entities within our XSLT style sheet.

Figure 2. endDrag() method placement

setTarget()
The setTarget() method is defined as follows:

function setTarget(obj) {
if(this.entity != null && this.entity != obj) {
this.target = obj;
}
window.event.cancelBubble = true;
}

This method first ensures that there is a current entity and that the selected entity is not
the entity being moved over. It then proceeds to set the target property of the
DragControl, after which it cancels the bubbling of events within the client browser.
This method is attached to our entities within our XSLT style sheet.

Figure 3. setTarget() method placement

setPosition()
The setPosition() method is defined as follows:

function setPosition() {
this.entity.style.left = window.event.x;
this.entity.style.top = window.event.y - 10;
}

This method simply sets the selected entity's X and Y coordinates to the current mouse
event's X and Y coordinates. This method is called from within the move() method.

This method is attached to our entities within our tree.js file.

Figure 4. setPosition() method placement

move()
The move() method is defined as follows.

function move(obj) {
if(window.event.x < this.beginX - 5 || window.event.x > this.beginX +
5 ||
window.event.y < this.beginY -5 || window.event.y > this.beginY + 5 &&
this.enabled == false) {
obj.style.position = "absolute";
obj.style.filter = "alpha(opacity='60')";
this.setPosition();
this.enabled = true;

obj = obj.removeNode(true);
document.body.appendChild(obj);
document.onmouseup = function anonymous() { dragControl.endDrag()
};
}
else if(this.enabled == true) {
this.setPosition();
}
}

This method ensures that the user is actually dragging the ent ity by at least 5 pixels in any
direction whether X, Y, positive, or negative. It then moves the entity out of the current
tree and into the body of the document. It is continuously called via the "onmousemove"
event of the document object and ensures that the entity is always displayed next to the
mouse cursor.

This method is attached to our entities within the tree.js file.

Figure 5. move() method placement

reset()
The reset() method is defined as follows:

function reset() {
document.onmouseup = null;
document.onmousemove = null;

this.entity = null;
this.origin = null;
this.target = null;
}

This method simply cleans up the document and the DragControl object. It is called
whenever a drag and drop is completed or canceled.
This method is attached to our entities within our XSLT style sheet.

Figure 6. re set() method placement

View a live demo of the folder tree drag-and-drop control. For demonstrations purposes I
have created two trees and placed entities titled numerically "One" to "Six" beneath them.
Try dragging them so that "Two" is nested beneath "One," "Three" is nested beneath
"Two," and so on. Below is a screen shot of the entity named "One" being dragged from
Tree 1 into Tree 2 after all numbers have been logically ordered (nested) beneath one
another by the user.
Figure 7. Dragging and dropping from one tree to another

• Download complete source code.


• View a live demo Closing

I hope the contents of this article will help increase the quality of your Web applications.
If you have any questions, comments, suggestions, or requests, please feel free to email
me at the address listed in the author section.

15 Seconds : Advanced UI Design Using XML and XSL -


Part 5:
Progress Indicator Creation
Joe Slovinski
12/12 / 2001
Welcome to Advanced UI Design Using XML and XSL. Each article in my series will
demonstrate the creation of a specific user interface (UI) component using eXtensible
Markup Language (XML) and eXtensible Stylesheet Language (XSL).

This article discusses a Progress Indicator object. Below is the screenshot of the Progress
Indicator demo located deeper within this article. This screenshot captures the progress
indicator in 50 percent.

Figure 1. Progress Indicator demo

Progress Indicator Uses

The Progress Indicator object has the following uses for showing progress (but is not
limited to):

1. Data binding
2. Preloading images
3. Charting and graphing

Progress Indicator Control

Our drag-and-drop control exists on the client and is defined as follows:

function ProgressIndicator()
{
this.xmlDoc = null;
this.xslDoc = null;
this.target = null;

this.init = ProgressIndicator_init;
this.refresh = ProgressIndicator_refresh;
this.update = ProgressIndicator_update;

this.xmlObj = new ActiveXObject('MSXML2.DOMDocument');


this.xmlObj.async = false;

this.xslObj = new ActiveXObject('MSXML2.DOMDocument');


this.xslObj.async = false;
}

Our control has the following members:

Properties

xmlDoc
The xmlDoc property contains the path to the Progress Indicator configuration file. These
XML documents are discussed later in this article.

xslDoc
The xslDoc property contains the path to the Progress Indicator XSLT (eXtensible
Stylesheet Language Transformations) file. This XSL document is discussed later in this
article.

target
The target property contains a reference to the object that will contain the Progress
Indicator upon its initialization.

xmlObj
The xmlObj property contains a reference to the actual XML Parser that contains our
Progress Indicator XML configuration document.

xslObj
The xslObj property contains a reference to the actual XML Parser that contains our
Progress Indicator XSLT document.

Methods

init()
The init() method is defined as follows:

function ProgressIndicator_init()
{
this.xmlObj.load(this.xmlDoc);
this.xslObj.load(this.xslDoc);
this.refresh();
}

refresh()
The refresh() method is defined as follows:
function ProgressIndicator_refresh()
{
this.target.innerHTML =
this.xmlObj.documentElement.transformNode(this.xslObj);
}

This method applies our XSLT style sheet to our configuration file and places the
resulting transformation within the innerHTML property of our target element.

update()
The update() method is defined as follows:

function ProgressIndicator_update(percent)
{
this.percentComplete = percent;
this.xmlObj.documentElement.selectSingleNode("percentComplete").text
= this.percentComplete;

this.refresh();
}

This method receives an integer representing a percent, which for practical purposes is
normally a range of 0 through 100. The method then proceeds to set the percentComplete
property of the Progress Indicator object, after which it updates the XML configuration
file. After the XML configuration file mirrors this percent, the refresh method is called to
perform a new transformation and update the client browser.

XML Configuration File

Below is a valid Progress Indicator configuration file.

<?xml version="1.0"?>
<ProgressIndicator>
<styles>
<leftBackgroundColor>gray</leftBackgroundColor>
<leftFontColor>white</leftFontColor>
<rightBackgroundColor>white</rightBackgroundColor>
<rightFontColor>black</rightFontColor>
<borderTop>1px solid black</borderTop>
<borderLeft>1px solid black</borderLeft>
<borderBottom>1px solid black</borderBottom>
<borderRight>1px solid black</borderRight>
<height>25</height>
<width>200</width>
</styles>
<percentComplete>0</percentComplete>
</ProgressIndicator>

This file defines your styles and default percent of your Progress Indicator.

XSLT File

Below is the XSLT document that is applied to a Progress Indicators XML during
initialization and updating.

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<xsl:template match="ProgressIndicator">
<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0">
<xsl:attribute name="STYLE">
border-top: <xsl:value-of select="styles/borderTop"/>;
border-left: <xsl:value-of select="styles/borderLeft"/>;
border-bottom: <xsl:value-of select="styles/borderBottom"/>;
border-right: <xsl:value-of select="styles/borderRight"/>;
height: <xsl:value-of select="styles/height"/>;
width: <xsl:value-of select="styles/width"/>;
</xsl:attribute>
<TR>
<TD ALIGN="right">
<xsl:attribute name="STYLE">
background-color: <xsl:value-of
select="styles/leftBackgroundColor" />;
color: <xsl:value-of select="styles/leftFontColor" />;
font-family: arial;
font-size: 11px;
font-weight: bold;
width: <xsl:value-of select="styles/width div 100 *
percentComplete" />px;
</xsl:attribute>
<xsl:if test="percentComplete > 50">
<span style="padding-left: 5px; padding-right: 5px;">
<xsl:value-of select="percentComplete" />%
</span>
</xsl:if>
</TD>
<TD ALIGN="left">
<xsl:attribute name="STYLE">
background-color: <xsl:value-of
select="styles/rightBackgroundColor" />;
color: <xsl:value-of select="styles/rightFontColor" />;
font-family: arial;
font-size: 11px;
font-weight: bold;
width: <xsl:value-of select="styles/width - (styles/width
div 100 * percentComplete)" />px;
</xsl:attribute>
<xsl:if test="percentComplete <= 50">
<span style="padding-left: 5px; padding-right: 5px;">
<xsl:value-of select="percentComplete" />%
</span>
</xsl:if>
</TD>
</TR>
</TABLE>
</xsl:template>
</xsl:stylesheet>

This style sheet creates a table with two cells. As the percent grows from 0 to 50, the
placement of the percentage label (contained in an HTML "SPAN" element) is switched
from the right cell to the left. The formula for calculating the left and right cells varies.
These formulas are displayed above.

Demo

The demo Progress Indicator provided within this article is set to update in tens of
percents every second. When attaching the Progress Indicator included in this article to a
data-bound table and updating it with a simple record/row formula you can achieve a
real-time update of the progress of any client-side Data--Binding process.

You might also like