Client-side data management with XML (part 3: Opera’s DOM 3 Load & Save)

On most recent browsers, you can use a non-rendered DOM Document to load, hold and modify the data of your web application. So far, I’ve been using the Sarissa wrapper to get a simple, unified syntax that works for Gecko-based browsers (Firefox..) and Internet Explorer. Unfortunately Opera and Safari were left out.

Today I’ll show how you can extend support to Opera by using its implementation of the DOM 3 Load & Save specifications.

In short, here’s what we’re trying to do:

  1. Instantiate a non-rendered DOM Document.
  2. Populate the document with XML data loaded asynchronously from the server.
  3. Use regular DOM Scripting to store and update application data in the document.

The code


if (document.implementation && document.implementation.hasFeature && document.implementation.hasFeature('LS', '3.0')) {
var domDoc = null; // will hold our XML
var parser = document.implementation.createLSParser(document.implementation.MODE_ASYNCHRONOUS,null);
parser.addEventListener("load", loadHandler , false);
try {
parser.parseURI('/time-tracker/srvc-tracker.php?taskId=0');
} catch (e) {
alert('error:'+e.code);
}
function loadHandler(e) {
domDoc = e.newDocument;
}
}

The ‘Load & Save’ parser (LSParser) instantiates the DOM Document and takes care of the asynchronous load. The document can be retrieved, once the loading is complete, in the event’s ‘newDocument’ property.

At this point you can use any DOM Scripting method to access and modify the data. See my previous post for more details.

If you need to serialize the document back to a string, use:

var serializer = document.implementation.createLSSerializer();
var xmlstring = serializer.writeToString(domDoc);
alert(xmlstring);

Note: Code samples for a synchronous load can be found on molily’s site (in german) and on Grauw’s web spot.

Technorati Tags: , ,

wForms - Server-side handling of the Repeat Behavior (part 1)

This documentation was updated on June 28th 2005 (fixed code sample)

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.

The repeat behavior generates form fields dynamically and this makes the server-side processing a bit more complex than with your usual web form. This is a two parts problem: (1) how to retrieve the submitted information and (2) how to pre-fill a form with a repeated element. This post deals with the first and before we start, here’s a short definition: The ‘repeated element’ refers to the element with the ‘repeat’ class (usually a <div>, a <fieldset> or a <tr>).

The Row Count

The wForms extension generates a hidden ‘row count’ field for each repeated element of the form. The name of the field is derived from the id of the repeated element (and you probably want to set that id attribute if you haven’t done so already).

For instance:

<fieldset id=”someid” class=”repeat” >
… some fields…
</fieldset>

wForms will generates the following markup:
<input type=”hidden” id=”someid-RC” name=”someid-RC” value=”" />

The value of the field is a number indicating the number of time the element has been repeated.

The Row Index suffix

In order to maintain the uniqueness of field names, wForms appends a dash followed by the row index to each repeated field.

Original row: <input type=”text” name=”somename” />
Second row: <input type=”text” name=”somename-2″ />
Third row: <input type=”text” name=”somename-3″ />

The first row is left unchanged. This serves two purposes: First, you don’t have to adopt a specific syntax for field names and second, the form can still be processed normally if javascript was unavailable on the client.

Now, it is important to know that there can be gaps in the sequence. For instance, if the user deletes a row before submitting the form, you’ll end up with:

Original row: <input type=”text” name=”somename” />
Third row: <input type=”text” name=”somename-3″ />

The field named ’somename-2′ is missing. In this situation the row count still indicates 3.
This is by design. It would be rather complicated to adjust the counter and renumber all field names in the browser each time a row is deleted.

In effect, the row count gives the upper bound of the loop and the presence of the field must be tested at each iteration.

PHP Code Sample

Here’s the corresponding code in PHP (from memory, let me know if it has any problem):

<?php
// first value:
$value = $_POST["somename"];
// subsequent values:
if( array_key_exists("someid-RC",$_POST)) {
for($i=2;$i < = (int) $_POST["someid-RC"];$i++) {
if( array_key_exists ("somename-".$i,$_POST)) {
$value = $_POST["somename-".$i];
}
}
}
?>

Code sample updated (see comments below)

You’re welcome to share code samples for different environments, or continue reading: how to prefill a form with a repeated element.

 

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 : Client-side data management with XML (part 2)

XML Data Manipulation

In the first part of this article I explained how to create a DOM Document to maintain application data on the client-side. I will now explain how we can manipulate such data, using the DOM and Javascript. If you haven’t done so already, you may want to read about the personal time tracking application I am building, before continuing.

Adding a new task

In this application, the DOM document holds a list of tasks. Let’s see the XML for one task:

<task status="hold" edit="true" id="" >
<timetrack>
<timeslice startdate="" enddate="" />
</timetrack>
<label></label>
<category></category>
</task>

The status of a task defines whether its timer is running or not. The edit attribute determines if the task characteristics are editable or read-only. By default, a new task is editable. The Timetrack element will be used to record the periods of time during which the task was running.

I set up this XML as a separate file so that I can load it in the engine and keep it as a template for every new task.
var xmlNewTask= Sarissa.getDomDocument();
xmlNewTask.onreadystatechange = loadHandler;
xmlNewTask.load("/time-tracker/xml/tmpl_task.xml");

To add a new task, I simply need to clone the XML contained in xmlNewTask and insert it under the tasklist element of xmlTaskList.

function newTask() {
var task = xmlTaskList.firstChild.appendChild(xmlNewTask.firstChild.cloneNode(true));
task.setAttribute('startdate',(new Date()).toLocaleString());
task.setAttribute('id',randomId());
// ...re-paint user interface ...
}

Note that xmlTaskList and xmlNewTask are references to DOCUMENT_NODEs. In order to manipulate document trees we need to work with ELEMENT_NODEs. <tasklist>, the root element of xmlTaskList, is obtained with xmlTaskList.firstChild (same with xmlNewTask).

Modifying a task

setAttribute is the standard DOM method to modify an attribute. It will conveniently create the attribute if it does not exist. For instance, this will set the starting time of a task:

task.setAttribute('startdate',(new Date()).toLocaleString());

Modifying the text value of an element is a bit more complex. Take the <label> for instance. I first need to get a reference to the element, then retrieve its text node (usually its first child) and finally change the nodeValue property.

If I have already a reference to the task. I can use getElementsByTagName to find the <label> element. getElementsByTagName returns an array of elements, but since we will have only one label per task, the array will always have only one row, getElementsByTagName[0].

task.getElementsByTagName('label')[0].firstChild.NodeValue = ' new task label ';

Deleting a task

To delete a task I just need to remove the corresponding node from the document tree. With a reference to the node to delete and its parent, I can use:

parentnode.removeChild(nodetodelete);

In my XML, all task elements have the same parent, the root element ‘TaskList’, which I can access by xmlTaskList.firstChild.

function deleteTask(taskId) {
if(confirm('Are you sure you want to delete this task ?')) {
xmlTaskList.firstChild.removeChild(getTaskById(taskId));
}
//.. repaint interface ..
}

getTaskById is a custom function that, given a task id, returns a reference to the task element in the xmlTaskList DOM tree. I cannot use getElementById because this method doesn’t work in the context of a non-HTML DOM Document (at least for most browsers).

So I simply iterate the task elements until I find one with the given id attribute.
function getTaskById(taskId) {
for(var i=0;i < xmlTaskListRoot.childNodes.length; i++) {
if (xmlTaskListRoot.childNodes[i].nodeType == 1 ) {
if(xmlTaskListRoot.childNodes[i].getAttribute('id') == taskId)
return xmlTaskListRoot.childNodes[i];
}
}
return null;
}

The Ajax engine has now the basic methods to handle data. Next time, we will (finally) deal with the application interface, using XSL templates and XSL Transformation (XSLT).

Thanks for reading.

Technorati Tags: , , ,

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

Search the Blog Archive

 

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