In 1927, Henry Ford was producing Model Ts, but they were too expensive to accomplish his goal of making them available to the "multitude." Over the next few years, Ford accomplished his goal by using a division-of-labor approach to lower costs. The essence of the division-of-labor approach was to accomplish something complicated by breaking it up into simple, manageable chunks. In this article, I will show how use to Internet Explorer's (IE's) modal dialog functionality to divide complex Extensible Markup Language (XML) data manipulation into simple, efficient, manageable units.
Occasionally, I find myself building a single Web page that manages many different types of data from a data island. As an example, consider a single page for ordering a custom PC. The page would allow the user to specify components such as hard drives, CD-ROMs, CPUs, memory, and software titles. Such a page can quickly become so overloaded with functionality that it becomes inefficient and difficult to maintain. In such situations, I have applied the division-of-labor approach to data manipulation.
To explain this technology, we'll start with an overly simplified example. I have chosen the process of building an ice cream sundae as the example we will follow throughout this article. First, we will build a basic sundae page that calls a toppings dialog page. Next, we will continue this article by adding enhancements and functionality to the sundae and toppings pages.
Step One: Build a sundae page that calls a toppings dialog
The sundae page will contain an XML data island representing the sundae being constructed. The page will also use XSL (style-sheet language for XML) to transform the XML into HTML for a simple user interface. To keep it simple, I will embed both the XML and XSL in the sundae page. The onLoad event of the page will perform the XSLT and build the page. (XSL Transformations is a specification that defines the syntax and semantics of XSLT, which is a language for transforming XML documents into other XML documents. XSLT is designed for use as part of XSL, which is a style-sheet language for XML, see http://www.w3.org/TR/xslt.) The code for the first version of the sundae page is as follows:
As you can see, the onLoad event of the window is used to fire the transformation of the XML data island using the XSL. Also note the structure of the XML data island. The resultant HTML from the transformation is placed in the divTarget's innerHTML. This builds and displays a basic user interface. Part of that user interface is a button that calls the callModalDlg subroutine. This subroutine, for now, simply displays the toppings dialog. To see this example in action, click here -- Sundae01.htm.
The dialog used in the preceding example is based upon the following code:
Note that window.returnvalue is set to "Save" if the user selects the OK button. Otherwise, an empty string is returned. This Save is to tell the sundae page that the dialog intends its changes to take effect. Later, we'll detail how the sundae page will respond to this returned value.
Step Two: Pass the sundae XML to the toppings dialog
We used the callModalDlg subroutine in the first section of code to display the dialog via the showModalDialog method. The second parameter to this showModalDialog method, dialogArgs, allows the sundae page to pass information to the dialog. DialogArgs was an empty string. To enhance this we will use dialogArgs to pass a pointer to the dialog page. The dialog page can then use that pointer to see and alter the sundae page's data island. To accomplish this, alter the callModalDlg subroutine on the sundae page as follows:
Using the code above, the modal dialog can see the entire data island xmlMySundae.
Note: Do not attempt to pass XML to the dialog as a string. The dialogArgs parameter is limited to 4096 characters. If the XML is larger than 4096 characters, it will be truncated, resulting in invalid XML.
Step Three: Batch updates to the sundae XML
To understand why we need to update the sundae page's XML only once, in a batch, consider the following. A user opens the toppings dialog and alters the sundae data island. The user then changes their mind and cancels out of the dialog. How do we roll back the changes already made to the data island on the sundae page?
One answer would be to only make changes when the user selects the OK button on the dialog. While this approach would work for a sundae toppings dialog, complex dialogs will become too complicated to perform all their updates at once.
The best solution is to have the sundae page make a copy of its data island and then pass the copy to the dialog. Then, if the user cancels out, nothing happens. If the user selects OK, the original data island can be updated with the data in the copied XML. To accomplish this, we enhance the callModalDlg subroutine further:
Sub callModalDlg()
Dim objCopiedXML, strResponse
'Create a copy of the entire XML data island
Set objCopiedXML = CreateObject("Microsoft.XMLDOM")
objCopiedXML.loadXML xmlCustomComputer.xml
'Call the modal dialog
strResponse = window.showModalDialog("SundaeToppings.htm", _
objCopiedXML, _
"dialogHeight: 4in; dialogWidth: 5in; center: yes; " & _
"help: no; resizable: no; status: no;")
End Sub
Step Four: Limit what data the dialog can change
As our example stands now, a copy of the entire data island is passed up to the toppings dialog. So far, this works great for our simple example, but what happens when we expand the sundae data island as shown here? Clearly the toppings dialog shouldn't be able to modify the scoops data and a scoops dialog shouldn't modify toppings data.
One option is to copy and pass just the toppings data to the dialog. This would ensure that the dialog could only change data within its own subset of the data island. Yet, there is one major limitation to this approach. What if the toppings dialog needed to read data from other nodes such as scoops, scoop, or sundae? If specific scoop flavor and topping combination should be avoided, the toppings dialog must be able to read the scoop data to implement such a rule.
The dilemma is this: how do we limit the dialog to only changing its subset of data while giving it access to the entire data island? The solution is simple. We'll enhance the callModalDlg subroutine once more so that it does the following:
Creates a copy of the entire data island.
Supplies the dialog with a pointer to the toppings node in the copied XML document.
If the dialog returns "Save," then replace only the toppings node in the original XML with the toppings node passed to the dialog.
Here is the enhanced callModalDlg subroutine:
Notice that two parameters, strDialogFileName and strXPath, have been added. By adding these two parameters, this subroutine can be used to call an infinite number of dialog pages. To call the toppings page, the code would look like this:
I use the VBScript prefix here to avoid any errors that arise from IE misunderstanding which script language to process. To see this version in action, click here -- Sundae02.htm.
This enhancement resolves our earlier dilemma. The dialog can still see the scoops data because the toppings node passed in is still a part of the entire copied sundae XML document. To demonstrate, the toppings dialog could contain the following code to read scoop data:
Dim objNode, lngNumberOfScoops
lngNumberOfScoops = 0
For Each objNode In _
window.dialogArguments.selectNodes("/Sundae/Scoops/Scoop")
lngNumberOfScoops = lngNumberOfScoops + _
CLng(objNode.getAttribute("Number"))
Next
MsgBox "This sundae has a total of " & _
lngNumberOfScoops & " scoops."
Note that window.dialogArguments is a pointer used to reference the toppings node passed in by the sundae page.
Since the dialog can see data outside its toppings node, it can also update that data. Even if the dialog exhibits such bad behavior, the changes will not take effect because only the toppings node is replaced in the original XML document.
Step Five: The NewNodeTemplate
It is easy to delete toppings from within a dialog. For example, this snippet of code will remove the first topping:
With window.dialogArguments
If .selectNodes("./Topping[0]").length > 0 Then
.removeChild(.selectSingleNode("./Topping[0]"))
Else
MsgBox "There are toppings to delete."
End If
End With
Although deleting nodes is easy, adding new nodes can be tricky. How does the dialog know how to build the XML for a new node? An existing topping node could be copied and then clear out its attributes. But copying an existing node assumes that at least one topping node will always exist. If this is a new sundae with no toppings, then there will be no toppings node to copy. Moreover, copying an existing node and clearing its attributes won't handle more complicated situations where attribute defaults are required. To handle this I developed the NewNodeTemplate. This is an attribute of the parent node that defines the structure of its child nodes. The XML for an empty toppings node appears as follows:
<Toppings NewNodeTemplate="Topping Name=''">
Now that the NewNodeTemplate attribute in the toppings node defines how a topping node should work, I can use the following function to create new toppings nodes:
Function CreateNodeFromTemplate(objParentNode)
'This function takes a node, extracts its NewNodeTemplate,
'uses that template to create a new node and appends it as a
'child. Finally, it returns a pointer to the newly created
'and appended node object.
Dim objDOM, objNewNode, strNewNodeTemplate
strNewNodeTemplate = _
objParentNode.getAttribute("NewNodeTemplate")
strNewNodeTemplate = "<" & strNewNodeTemplate & "/>"
Set objDOM = CreateObject("Microsoft.XMLDOM")
objDOM.loadXML strNewNodeTemplate
Set objNewNode = objDOM.selectSingleNode("*")
Set CreateNodeFromTemplate = _
objParentNode.appendChild(objNewNode)
End Function
This function returns a pointer to the newly created child node. The following code illustrates how it can be used on the toppings dialog:
Sub AddCherry()
Dim objNode
Set objNode = CreateNodeFromTemplate(window.dialogArguments)
objNode.setAttribute "Name", "A Cherry On Top"
End Sub
Conclusion
Through our example, we have illustrated how the sundae page can pass an XML node to a dialog for editing. The sundae page does not change any data in its data island. Instead, it farms a chunk of the data island to the toppings modal dialog for processing. The toppings dialog can then perform data manipulations against its chunk of data. When finished, the altered chunk of XML can be used to update the sundae page's XML data island. To see the final copy in action, click here -- Sundae03.htm.
In more complex applications, multiple modal dialogs can be used. Encapsulating each portion of XML manipulation in its own dialog greatly simplifies the construction of complex data-entry pages. I have implemented this technology in highly complex pages that call more than a dozen dialogs. Yet, the page itself is simple because it doesn't actually update any data and it uses the callModalDlg subroutine to invoke each dialog. Furthermore, each dialog is relatively simple because it only manipulates its portion of the data. In the end, this division-of-labor approach is used to accomplish sophisticated functionality through simple, manageable chunks. And as we all know, simple chunks of code result in quicker build times, less maintenance time, and better extensibility.
About the Author
Rusty is a consultant for Versant Technologies
(www.VersantTechnologies.com). He builds custom business solutions in
the Atlanta, Georgia area. He uses VB, IIS/ASP, SQL Server,
XML/XSL/XSLT/XPath, HTML/DHTML, Macromedia Flash and other technologies in software development. He currently holds both Microsoft Visual Basic 6 certifications (70-175 and 70-176) and the Microsoft InterDev 6 certification (70-152). He can be reached at rusty.speidel@ga.prestige.net.
Stonebroom.ASP2XML(c) is an interface component designed to make building
applications that transport data in XML format much easier. It can be used
to automatically pass updates back to the original data source.
Right now the latest buzzword around town is AJAX. AJAX is an acronym for Asynchronous JavaScript and XML and is a method used to implement remote calling. The problem is that AJAX is only implemented in ASP.NET 2.0. This article will show you one way to implement remote calling without using AJAX or the XMLHttpRequest object. The technique outlined can even be used from classic ASP and is sufficient for most remote calling needs. [Read This Article][Top]
This article is the third and final installment of Alex Homer's series covering the new XML support in Microsoft SQL Server 2005. In it he covers updating the contents of xml columns, comparing traditional XML update techniques with XQuery, and using XQuery in a managed code stored procedure. [Read This Article][Top]
In the second part of his series on SQL Server 2005's new XML support, Alex Homer looks at extracting data from XML columns, comparing traditional XML data access approaches with XQuery, and combining XQuery and XSL-T.
[Read This Article][Top]
Microsoft SQL Server 2005 now offers great support for and close integration with XML as a data persistence format. In the first article of his series examining this new support, Alex Homer offers an overview of how SQL Server 2005 stores XML documents and schemas, examines how it supports querying and manipulating XML documents, and provides a simple test application that allows you to experiment with XQuery. [Read This Article][Top]
In the final article of his series on reading and writing XML in .NET 2.0, Alex Homer looks at how the updated XML document store objects XmlDocument, XmlDataDocument and PathDocument can be used to read, persist and write XML documents and fragments more easily and more efficiently than in .NET 1.x. [Read This Article][Top]
In the final article of his series on reading and writing XML in .NET 2.0, Alex Homer looks at how the updated XML document store objects XmlDocument, XmlDataDocument and PathDocument can be used to read, persist and write XML documents and fragments more easily and more efficiently than in .NET 1.x. [Read This Article][Top]
Alex Homer continues his series on reading and writing XML in .NET 2.0. In part one, we focused on the reading side of things, examining the XmlReader and XmlReaderSettings classes. In this article, we move on to look at the XmlWriter and XmlWriterSettings classes, and how they can be used to write XML documents and fragments more easily and more efficiently than in version 1.x of .NET.
[Read This Article][Top]
Alex Homer continues his series on reading and writing XML in .NET 2.0. In part one, we focused on the reading side of things, examining the XmlReader and XmlReaderSettings classes. In this article, we move on to look at the XmlWriter and XmlWriterSettings classes, and how they can be used to write XML documents and fragments more easily and more efficiently than in version 1.x of .NET. [Read This Article][Top]
In the first part of his series on reading and writing XML in .NET 2.0, Alex Homer discusses the XmlReader and XmlReaderSettings classes. The XmlReader exposes several useful new features and the all new XmlReaderSettings class makes it easy to generate single or multiple instances of an XmlReader with a range of useful properties. [Read This Article][Top]
In the first part of his series on reading and writing XML in .NET 2.0, Alex Homer discusses the XmlReader and XmlReaderSettings classes. The XmlReader exposes several useful new features and the all new XmlReaderSettings class makes it easy to generate single or multiple instances of an XmlReader with a range of useful properties. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.