Accessibility
Kenneth Berger

Massimo Foti

massimocorner.com

Created:
9 April 2007
User Level:
Intermediate
Products:
Dreamweaver

Creating an interface for editing data in Ajax

Note: This tutorial was originally written for From A To Web. From A to Web, a training convention dedicated to the most modern technologies for the web, is now at its sixth edition. It is organized by Inside, a very well known training center. The next edition is planned for fall 2007—anyone who would like to suggest a topic is welcome to e-mail info@insidesrl.it!

Since the first release of the Spry framework for Ajax, Adobe also provided several interesting demo applications of considerable visual impact. Spry provides, among other benefits, a complete independence from server side programming platforms (ColdFusion, ASP.NET, PHP, etc)—therefore the use of these platforms within the Adobe demo applications is almost completely absent. The result is that the demos are by nature "read-only"—that is, they are limited to show data that already exists in static, predefined XML files and structures. The objective of this tutorial is to demonstrate that Spry can effectively be used to to visualize not only static data, but also dynamic data. In order to demonstrate this, I will work from the existing Adobe "Products" demo, and convert it into a simple example of how Ajax can also be effective in the manipulation of database-driven XHTML.

Requirements:

  • The execution of this tutorial requires ColdFusion Server Advanced version 6.1 or later. You can download the free version of ColdFusion from the Adobe website; after the trial version's 30 day limit is reached, it will automatically revert to the Developer Edition, and will continue to work free of charge.
  • This tutorial also requires Spry, a framework for Ajax currently in development at Adobe. Spry is available free of charge (released under the BSD software license) and is available for download on the Adobe Labs website. At the time of writing this tutorial, Spry version 1.4 was available. Version 1.5 is currently under development, and updates are being released frequently. The steps in this tutorial make use of prerelease version 1.4; any subsequent changes to the architecture of the Spry framework could cause this application to malfunction. The principles illustrated in this tutorial should, however, remain valid in future releases of the Spry data framework.

Sample files:

The database

The original demo makes use of "static" XML rows containing the data relative to the products. The first step for having "dynamic" data is to transfer the data to a database and use a server-side language (in our case, ColdFusion), in order to generate XML rows in a "dynamic" way. The database contains two tables, one contains product, while the other contains the names of the categories to which they are associated. In the enclosed sample files linked above, you will find a Microsoft Access database, named products.mdb. To be able to use it in the application, you must first create an appropriate data source through the ColdFusion administrator—the data source that we will call "spry".

The configuration screen for the ColdFusion data source. More details are available in the official documentation.

Figure 1. The configuration screen for the ColdFusion data source. More details are available in the official documentation.

Generating XML from a database

Generating XML markup using language side-server templates like CFML is natural to anyone with experience in the dynamic generation of XHTML code. You only have to keep in mind the more restrictive syntactic rules that regulate the use of the XML.

We will start creating a file called "products.cfm" that, once completed, will take the place of "products.xml" inside of the Spry demo. The CFML code from the file, really quite simple, will look like this:

<cfcontent type="text/xml"><?xml version="1.0" encoding="iso-8859-1"?>
<cfquery name="products" datasource="spry">
SELECT *
FROM Products_view
</cfquery>
<products>
    <cfoutput query="products">
    <product>
        <id>#products.prod_id#</id>
        <name>#products.prod_name#</name>
        <category>#products.cat_name#</category>
        <boximage>images/#products.prod_image#</boximage>
        <desc><![CDATA[#products.prod_description#]]></desc>
    </product>
    </cfoutput>
</products>

Proceeding from the first line, we find a tag whose scope is to make the web server send the content of the file using the appropriate MIME-TYPE (and not the one used for normal XHTML pages). It is a simple yet essential detail, and its absence may create unexpected and difficult to diagnose problems.

Following is the extraction of the data through a <cfquery> tag that reads a view in the database (Microsoft Access uses the term "query" instead of the commonly used "view"). The tag <cfoutput> then allows us to "write" our data.

Before passing to the successive phase, make sure of the correct operation of products.cfm, specifically because the nature of Ajax makes the debug more difficult compared to the traditional web applications, so it is a good rule to proceed with small steps and make sure that everything works as expected.

Aiming the browser at the products.cfm page, we should visualize the XML markup dynamically generated from ColdFusion:

The XML markup dynamically generated from ColdFusion

Figure 2. The XML markup dynamically generated from ColdFusion

At this point we can proceed to the modification of the Spry demo in order to be able to load the data generated through the database. We need to edit the JavaScript code that initializes the two datasets in order to load the data from the recently created file:

var dsProducts = new Spry.Data.XMLDataSet(
"products.cfm",
"products/product")
var dsProductFeatures = new Spry.Data.XMLDataSet(
"products.cfm", 
"products/product[name = '{dsProducts::name}']/features/feature")

Once this is done, it's necessary to verify the code in a browser to assure the demo still works correctly.

The form

The original demo makes use of the classic "master-detail" development pattern with the substantial difference (compared to traditional web applications) that instead of using two separate pages, only a single page is used (divided in two areas) and the page is not reloaded every time we need to visualize a different record.

In the "details" area on the space originally occupied by the accordion, we insert a form, which in turn will contain the Spry "detailregion" that allows us to view the data of every single record through specific fields (a textfield, a textarea, and a hidden field). The code, with minimal modifications compared with the original code, is:

<form action="action.cfm" method="post">
<div id="sidebar">
    <p spry:detailregion="dsProducts" id="boxshot">
<img src="{boximage}" 
alt="product box shot" 
width="238" 
height="130" /></p>
    <div id="Acc1" class="Accordion" tabindex="0">
        <div class="AccordionPanel">
            <div class="AccordionPanelTab">
                <h3>Edit Data</h3>
            </div>
            <div spry:detailregion="dsProducts" 
class="AccordionPanelContent">
                <div>
<input name="prod_name" 
type="text" 
value="{name}" />
</div>
                <div>
<textarea name="prod_description">
{desc}
</textarea>
            </div>
                <div><input type="submit" value="Save data" class="submit" /></div>
                <input name="prod_id" type="hidden" value="{id}" />
            </div>
        </div>
    </div>
</div>
</form>

Always following a policy of taking small steps, we open this in a browser and verify the data is correctly displayed inside the form:

Using a browser to verify the data is correctly displayed inside the form

Figure 3. Using a browser to verify the data is correctly displayed inside the form

Action.cfm

The next step is to create the CFM file that will contain the necessary code to edit the data—one really minimalistic "action page":

<cfif NOT StructIsEmpty(form)>
    <cfquery datasource="spry">
    UPDATE Products_tbl 
    SET 
        prod_name = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.prod_name#">,
        prod_description = <cfqueryparam cfsqltype="cf_sql_longvarchar" value="#form.prod_description#"> 
    WHERE prod_id = <cfqueryparam cfsqltype="cf_sql_numeric" value="#form.prod_id#">
    </cfquery>
</cfif>

This is very simple SQL code that updates the database based on the content of the fields in the form. The only worthy detail to notice is the use of the <cfqueryparam> tag, which guarantees some security against the use of potential "SQL injection" attempts.

Ajax libraries

At the moment Spry does not offer a specific tool to create forms in Ajax, therefore we will use a set of libraries conceived for this scope. The entire set of libraries is around 20 kb and is spread inside of four modular JS files, which we will include inside of index.html:

<script type="text/javascript" src="includes/tmt_core.js"></script>
<script type="text/javascript" src="includes/tmt_net.js"></script>
<script type="text/javascript" src="includes/tmt_form.js"></script>
<script type="text/javascript" src="includes/tmt_ajaxform.js"></script>

The use of the ajaxform library requires one custom attribute added to the <form> tag:

<form action="action.cfm" method="post" tmt:ajaxform="true">

The library, while loading the page, will automatically recognize the presence of this attribute. The form will then not be sent in the traditional way, but through an asynchronous HTTP request by way of XMLHttpRequest. This modification sends the content of the form to the action.cfm template, which in turn will update the data without the need to "refresh" the page.

However, there is still an essential problem to be solved; with every transfer of data from the form it is necessary to update the records view. In other words, instead of forcing a refresh of the entire page, we will make Spry read the new data contained inside products.cfm and consequently update the "dynamic region".

To start we modify the JavaScript code that defines the Spry dataset so as to disable the cache:

<script type="text/javascript">
var dsProducts = new Spry.Data.XMLDataSet("products.cfm", "products/product", { useCache: false });
var dsProductFeatures = new Spry.Data.XMLDataSet("products.cfm", "products/product[name = '{dsProducts::name}']/features/feature");
</script>

Then we will make use of a second custom attribute from the ajaxform library:

<form action="action.cfm" method="post" tmt:ajaxform="true" tmt:ajaxformcallback="dsRefresh">

The attribute "tmt:ajaxformcallback," as you'd guess from the name, allows you to assign a callback function that will be invoked from the library every time the submission of the form is successfully completed.

The scope of the callback function is to invoke the loadData() method comprised in the API of the object dataset created by Spry:

<script type="text/javascript">
function dsRefresh(){
    dsProducts.loadData();
}
</script>

This last step completes the implementation of our small demo.

Where to go from here

For more information about Spry, visit the Spry pages on Adobe Labs.

About the author

Massimo Foti lives in Lugano, Switzerland. He is a freelance webdeveloper with many years of experience in ColdFusion, CSS, SQL, database design, JavaScript and AJAX. You can find more about Massimo at www.massimocorner.com.