Ajax Makeover : Client-side data management with XML (part 1)

As I stated before, to build an application that is as responsive as traditional desktop applications, we need to cut down on server accesses. With that objective in mind, we are going to build an Ajax engine that can maintain a complete representation of the state of the Time Tracking application. The engine will process user inputs, apply changes and repaint the interface without any server access (be it a page load or a XMLHttpRequest).

Let’s clarify a bit what the application is supposed to do:

  1. Maintain a list of user-defined tasks.
  2. Keep track of the time spent since the task was started (ie. created in the application).
  3. Allow the user to ‘freeze’ a task that he has temporarily put aside.
  4. Allow the user to leave the application with running or frozen timers and come back later.
  5. Report the time spend on each task.

Loading XML Data

The state of our application will be maintained in a XML document, resident in the browser’s memory. That XML document will be populated with content loaded from the server when the application starts.

Client-Side XML Data Manipulation Diagram

For a new user with an empty task list, the XML is really simple:

<?xml version="1.0"?>
<tasklist name="" >
</tasklist>

<tasklist> is our root element. The ‘name’ attribute will be used as a title for the page.

The Ajax engine, which is nothing more than a well designed javascript, loads that XML using this code:

  var xmlTaskList = Sarissa.getDomDocument();
  xmlTaskList.onreadystatechange = loadHandler;
  xmlTaskList.load("/time-tracker/xml/tmpl_tasklist.xml");

  function loadHandler() {
    if(xmlTaskList.readyState == 4) {
       // ... paint user interface ...
    }
  }

The Sarissa object is a wrapper that offers a unified cross-browser syntax for everything XML. It’s a convenient low-level tool, not an application framework.

The Sarissa.getDomDocument method creates a new instance of a DOM document. The exact same kind of document that holds the representation of a web page, but this one is not rendered and can conveniently hold any type of XML data.

Finally, the Document Object Model offers all kind of methods to manipulate the document. We’ll be using extensively appendChild, removeChild, getAttribute, setAttribute and to a lesser extent createElement and createCDataSection, but this will covered in the second part of this article.

The next article of the Ajax Makeover series will cover the interface rendering using XSLT. That’s when the benefit of client-side XML will reveal itself, so don’t miss it :-)

Technorati Tags: , , ,

The Pagination Behavior Explained

This post is part of the wForms Documentation. wForms is an open-source javascript library that adds commonly needed behaviors to traditional web forms without the need for any programming skill.

For additional help, visit the wForms forum.

wForms allows you to easily split a long and tedious form into a multi-page
form
. The next and previous page buttons are automatically added
and the form is validated page by page. If javascript is disabled, the form
degrades gracefully in one long form.

 

Example:

Page 1


Spam Opt-inDo you want to suscribe to our daily spam ?


Page 2


Fee & Other Ignominious Charges
This is the amount you’ll be charged for your current usage of our bandwidth.




 

To create a page in the form builder, create a ‘fieldset’ and set its type to ‘page’, then move the appropriate fields in it.

To use the multi-page behavior on your own web forms, follow
the following steps:

  1. Enable the wForms extension by adding the following code to the <head> element
    of your page:
    <script type="text/javascript" src="wforms.js" ></script>
  2. Add a <div> element
    for each page. The element should have the ‘wfPage’ class and an id starting
    with ‘wfPgIndex-’ followed by the page number.
    <form>
    <div class="wfPage" id="wfPgIndex-1">
    ... form elements (inputs, fieldset, ...)
    </div>
    <div class="wfPage" id="wfPgIndex-2">
    ... other form elements (inputs, fieldset, ...)
    </div>
    <input type='submit' ... />
    </form>
  3. The wFroms extension generates the ‘Next Page’ / ‘Previous Page’ buttons.
    You can use the ‘wfPageButton’ class to style them. To change the label of
    the buttons, see the documentation
    on customization
    .

Any form element that is not within a ‘Page’ division is visible on
every page, except for the ’submit’ button, which is hidden until the last
page of the form is reached.

Please note: To prevent incomplete submission of a form, you need to disable the form submission with the ‘enter’ key using the following code (either in a customization.js file, or within a <script> tag *after* the link to wForms.js)

wf.preventSubmissionOnEnter = true;

 

Update: Comments are now closed for this post, but you can go to the wForms forum if you have any question or comment. Thanks !

Ajax Makeover: Type-Ahead Service

For this second article of the Ajax Makover serie, I will introduce the server side component of the Smart Type-Ahead functionality.

You may want to jump to the updated demonstration page or review the client-side implementation first. It now reproduces the full ‘google suggest’ experience.

The Type-Ahead Service accepts two kinds of request:

  1. A ‘Get’ request, containing the user input and the field id in the query string. The service returns a list of matching suggestions.
  2. A ‘Post’ request, with the user input and the field id in the posted data. The service adds the input text / field id pair to its suggestions database.

The suggestion database

The suggestion database tracks all user inputs and their frequency. Here is a MySQL table description:

--
-- Table structure for table `SUGGESTIONS`
--
CREATE TABLE `SUGGESTIONS` (
`ID` int(11) NOT NULL uto_increment,
`FIELD_ID` varchar(100) NOT NULL default '',
`VALUE` varchar(255) NOT NULL default '',
`FREQUENCY` int(11) NOT NULL default '1',
PRIMARY KEY (`ID`))
TYPE=MyISAM AUTO_INCREMENT=1 ;

Handling of the ‘GET SUGGESTION’ request

The service described here is implemented in PHP (See the full code).

header('Content-Type: text/plain');

The services will return a list of suggestion, as plain text.

$inputTxt = secureUserInput($_GET['inputtxt']);
$inputId = secureUserInput($_GET['inputid']);

secureUserInput is a ’sanitization’ function that insure than no harmful data is injected into the service.

$dbusername = "your_db_username";
$dbpassword = "your_db_password";
$link = mysql_connect("",$dbusername,$dbpassword )
or die ("error: Sorry, DB server unavailable. Please try again later or contact the webmaster.");
mysql_select_db ("your_typeahead_db")
or die ("error: Sorry, database unavailable. Please try again later or contact the webmaster.");

This is the usual database connection.

if ($inputId != '') {
if($inputTxt != '')
$query = "SELECT VALUE FROM SUGGESTIONS WHERE FIELD_ID = '$inputId' AND VALUE LIKE '$inputTxt%' ORDER BY FREQUENCY DESC LIMIT 0 , 20";
else
$query = "SELECT VALUE FROM SUGGESTIONS WHERE FIELD_ID = '$inputId' ORDER BY FREQUENCY DESC LIMIT 0 , 20";

$db_result = mysql_query($query)
or die ("error: Sorry, operation failed. Please try again later or contact the webmaster.");

while ($row = @mysql_fetch_array($db_result)) {
$suggestions .= $row["VALUE"]."&";
}

This is the core of the Type-Ahead Service. The SQL Statement returns an ordered list of suggestions, where the most commonly used one appears first.

echo substr($suggestions,0,strlen($suggestions)-1);
}

This is the part that returns the list of suggestion (an ampersand delimited list) to the client.

Handling of the ‘POST SUGGESTION’ request

else {
$inputTxt = secureUserInput($_POST['inputtxt']);
$inputId = secureUserInput($_POST['inputid']);

This time we’re retrieving posted data.

if ($inputTxt != '' && $inputId != '') {
$query = "SELECT ID, FREQUENCY FROM SUGGESTIONS WHERE FIELD_ID = '$inputId' AND VALUE = '$inputTxt'";
$db_result = mysql_query($query)
or die ("error: Sorry, operation failed. Please try again later or contact the webmaster.");
$record = @mysql_fetch_array($db_result);
$recordId = $record['ID'];
$frequency= $record['FREQUENCY'] + 1;

Before inserting the new data, we need to check if we already have a record for the text/id pair. If we do, we’ll only update the frequency count.

if(is_numeric($recordId)) {
$query = "UPDATE SUGGESTIONS SET FREQUENCY = $frequency WHERE ID = $recordId";
} else {
$query = "INSERT INTO SUGGESTIONS (FIELD_ID, VALUE) VALUES ('$inputId','$inputTxt')";
}
$result = mysql_query($query);
}
}

We’re all set, the database has been updated.

This wraps up the auto-complete part of the Ajax Makover. We’ll keep adding more building blocks to the time-tracking application in the next articles.

Do you have any suggestion on how to improve the type-ahead functionality ? Please share your remarks.

Ajax Makeover: Adding a Smart Type Ahead

[Jump to the demonstration page]

If you are like me, you probably have a hard time doing a realistic estimate
of the time you spend on any given task. That’s why Time Tracking
applications were invented… to help you manage and plan your projects
better. Unfortunately, their very existence collides with the way people
like to think and to work. Freely. Without the constraints of a ticking
clock. Making a web application that is actually helpful is going to
be quite a challenge for this makeover.

The first aspect of time tracking application I’m going to cover
is the task input. Chances are, there is a somewhat small set of tasks
that are part of your daily routine: deleting spams, writing status reports,
attending meetings, debugging code, etc… If you were able to make
an exhaustive list, you could populate a <select> field and that
would be your task input.
Of course (or hopefully), your job is not that predictable, and there are
lots of other way for you to spend your time at work. To describe these tasks,
you need a free text input.

The point of the type-ahead functionality (or auto-complete) is to combine
these two needs (predefined list and free text) in one field. The type-ahead
becomes ’smart’ when it learns from previous inputs and other
users and guesses your intent after just a couple keystrokes.

Before going into technical details, you may want to take a look at the demonstration
page.

A Basic Type-Ahead

The functionality core is similar to what Nicholas
C. Zakas presented at webreference.com
. Here is a simplified outline
of the javascript implementation. Cross-browser considerations and advanced
unobtrusive techniques are intentionally
left out
here, so be sure
to check out the final
implementation
for a complete view of the code.

Creating an array of suggestions

var suggestions = ["deleting spam","attending meeting", "writing status report", "debugging code"];

Getting a reference on the task input field

var taskInput = document.getElementById('taskInput');

Capturing the key press event

taskInput.onkeyup = function (evt) {
if(evt.keyCode == 8 ) return; // no type-ahead on backspace

Finding a match in the suggestions array

for (var i=0; i < suggestions.length && suggestions[i].toLowerCase().indexOf(taskInput.value.toLowerCase()) != 0; i++);
if(suggestions[i]) {

Replacing the user input with the suggestion

var userInputLength = self.inputElement.value.length;
taskInput.value= suggestions[i];

And finally, selecting the added text

taskInput.setSelectionRange(userInputLength,suggestions[i].length);
}
}

Adding the Ajax Touch

Querying a suggestion service

We are going to load the suggestion array using an asynchronous call to
a ’suggestion service’ hosted on the server. The idea is to keep the
array short and accurate by querying the server whenever we fall short of
suggestions matching the user’s input.

if (!suggestionsArray[i]) {
var HTTPReq = new XMLHttpRequest();
HTTPReq.onreadystatechange = function() {
suggestions = HTTPReq.responseText.split("&");
}
HTTPReq.open("GET", typeAheadServiceURL + "?inputid="+encodeURIComponent("taskInput")+"&inputtxt="+encodeURIComponent(taskInput.value),true);
HTTPReq.send(null);
}

We are passing two values to the suggestion service: the name of the input
field, and the user input. The service responds with a list of suggestions
(separated by the ampersand ‘&’ character).

Feeding the suggestion service

Now, we want our suggestion service to learn from our user input, so whenever
the form is submitted we do a second call to the service, this time
using the ‘POST’ method.

var HTTPReqPost = new XMLHttpRequest();
HTTPReqPost.onreadystatechange = self.populateSuggestions;
HTTPReqPost.open("POST", self.typeAheadServiceURL, true);
HTTPReqPost.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
HTTPReqPost.send("inputid="+encodeURIComponent('taskInput')+"&inputtxt="+encodeURIComponent(taskInput.value));

 

The Suggestion Service will be presented in the next article. In the meantime
you can play around with the demo page and share your thoughts.

Update: The Type-Ahead functionality now includes a drop-down list of suggestions (’google-suggest’-type).

Update (7/29/2005): A newer version of the script is available.

Ajax Makeover : Time Tracking Application (Introduction)

UPDATE (7/5/05): The Time-Tracker application is now live: www.formassembly.com/time-tracker

Thanks to the readers who suggested ideas for the Ajax Makeover. I eventually chose to go for a simple Time Tracking tool, as you may find in any good project management application. This application will serve as a support for demonstrating and discussing good practices for web application development. I hope you will enjoy it.

The code of the time tracking application is (will be) provided for educational purpose. Unless otherwise specified, it cannot be reused or published without the author (that would be me) consent.

The specifications are as follow:

  • Single-screen application,
  • Input hours by task and project,
  • Add, edit or delete entries,
  • Type-Ahead feature on project and task input fields,
  • Auto-save.

If you think of additional specifications that could be useful, leave a comment.

Ajax Makeover Roadmap

I’ll kick off on Wednesday with an ajax-driven type ahead functionality (a.k.a. auto-complete or auto-suggest).

Technorati Tags: , ,

You are currently browsing The Form Assembly weblog archives for May, 2005.

Search the Blog Archive

 

The Form Assembly blog is powered by WordPress ~ Entries (RSS) and Comments (RSS).