Advanced UI Design Using XML and XSL
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.
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.
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.
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.
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.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
function collapse(entity) {
var oImage
var i
oImage = entity.childNodes(0).all["image"]
oImage.src = entity.image
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))
}
}
}
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.
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.
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.
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.
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.
Client Operations
In order for the context menu to function, the following client-side operations are
required:
function applicationState() {
this.contextMenu = null
}
function loadContextMenu(path) {
var xmlDoc
var xslDoc
var contextMenu
if(path != "") {
xmlDoc = new ActiveXObject('Microsoft.XMLDOM')
xmlDoc.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"
}
}
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
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.
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.
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>
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
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
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.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.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);
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.load("admin/tree.xslt");
entity = xmlDoc.documentElement.selectSingleNode("//entity[@id='" +
entityID +"']");
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;
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;
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();
}
}
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 4 highlights the line within tree.xslt that inserts your onClick event during
transformation.
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.
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.
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
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;
}
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) {
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.
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.
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.
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.
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.
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.
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
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.
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.
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
function ProgressIndicator()
{
this.xmlDoc = null;
this.xslDoc = null;
this.target = null;
this.init = ProgressIndicator_init;
this.refresh = ProgressIndicator_refresh;
this.update = ProgressIndicator_update;
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 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.