AJAX :: Serializing HTML ::
Now let’s turn attention to serializing HTML properly in modern Web applications, working around some of the constraints you’re faced with. Problem Some of the DOM properties exhibit inconsistent behavior in particular, the innerHTML property. The purpose of innerHTML is to extract the child elements of an HTML element in a string form. The problem is that the innerHTML property doesn’t generate the correct text. You need to work around this. Theory To understand where the innerHTML property fails, let’s work through some examples and incrementally show where the problem lies. The example that will be illustrated is the dynamic addition of a button and text field. The source code for the example is as follows.
Source: /website/ROOT/ajax articles/dhtml/inconsistent.html
<html>
<head>
<title>Inconsistent .innerHTML</title>
</head>
<script language="JavaScript" src="/scripts/jaxson/common.js"></script>
<script language="JavaScript" type="text/javascript">
function DOMInserted() {
var element = document.createElement( "input");
element.type = "button";
element.value = "dynamically inserted";
element.onclick = function() {
document.getElementById( "generated").innerHTML = "hello";
}
document.getElementById( "dynamicallyinserted").appendChild( element);
element = document.createElement( "input");
element.type = "text";
element.value = "dynamically inserted";
document.getElementById( "dynamicallyinserted").appendChild( element);
document.getElementById( "output").value =
document.getElementById( "dynamicallyinserted").innerHTML;
}
</script>
<body>
<div id="dynamicallyinserted">
<input type="button" value="hello" onclick="callme()" />
</div>
<div id="generated"></div>
<textarea id="output" cols="40" rows="10"></textarea><br />
<input type="button" value="Dynamically Insert" onclick="DOMInserted()" />
</body>
</html>
The function DOMInserted adds the button and text to the div element with the ID dynamicallyinserted. The div element with the ID dynamicallyinserted has no child elements. Thus, if innerHTML were called, the returned string would contain no text. When users press the button Dynamically Insert, the function DOMInserted is called. Use the same HTML element input to create either a button or text box. What distinguishes a button from a text box is the value of the property type. To create an input element instance, use the method document.createElement. Once the element instance has been instantiated and assigned, you add it to the div element using the appendChild method. After both input instances have been added to the div element, you can use innerHTML to retrieve the value of its child elements in HTML. The value of innerHTML is assigned to the textarea element to illustrate the raw HTML value. The error is that no value attribute exists. In fact, the value attribute does exist, because you assigned it in the script and displayed it in the HTML, but with respect to the innerHTML property, the value attribute does not exist. Internet Explorer generates the correct output. Running the same code in Opera generates the same error as in Mozilla. A DHTML user interface is completely dynamic, so any changes made by users or the script need to be reflected in the property. Solution The remainder of this article focuses on how to solve the serialization problem of the HTML elements. The solution is simple in that to generate the buffer, you need to iterate all of the elements individually and generate a long buffer. The following code illustrates the complete solution.
Source: /website/ROOT/scripts/jaxson/common.js
htmlSerialize : function( element) {
var Recursive = function( element) {
var buffer = "";
if( element.nodeType == 1) {
buffer +="<" + element.nodeName + " ";
var didGenerateValue = false;
for( var i = 0; i < element.attributes.length; i ++) {
var attr = element.attributes[ i];
var name = attr.name.toLowerCase();
if(!(name.charAt( 0) == 'o' && name.charAt( 1) == 'n') &&
attr.value != null && !(typeof(attr.value) == "string" &&
attr.value.length == 0)) {
if( new String( element[ attr.name]).toLowerCase() ==
"undefined") {
buffer += attr.name + "=\"" + attr.value + "\" ";
}
else {
buffer += attr.name + "=\"" + element[ attr.name] + "\" ";
}
}
if( attr.name.toLowerCase() == "value") {
didGenerateValue = true;
}
if( element.nodeName.toLowerCase() == "input" && !didGenerateValue) {
buffer += "value=\"" + element.value + "\" ";
}
buffer += ">";
// If this is a textarea node, then inject the value directly
if( element.nodeName.toLowerCase() == "textarea") {
buffer += element.value;
}
else {
for( var i = 0; i < element.childNodes.length; i ++) {
buffer += Recursive( element.childNodes[ i]);
}
}
buffer += "</" + element.nodeName + ">";
}
else if( element.nodeType == 3) {
buffer += element.nodeValue;
}
return buffer;
}
var buffer = ""
for( var i = 0; i < element.childNodes.length; i ++) {
buffer += Recursive( element.childNodes[ i]);
}
return buffer;
}
The function Recursive is used to iterate the individual elements and can be used with all browsers. The Recursive function has a single parameter that represents an object instance that will be introspected for properties and methods. The serialization only supports two types of nodes (element.nodeType): 1 and 3. If you don’t know what the various node types are, then the numbers 1 and 3 aren’t going to help you. Node type 1 represents a plain vanilla HTML element, and node type 3 represents a piece of text. The serialization ignores comments, page directives, and the like, because generally speaking, they’re not important to the serialization. If the node type is 1, then a buffer is generated for the element identifier and all of its associated attributes. You would think that iterating all of the attributes would include the value attribute. For any browser other than Internet Explorer, the attributes that are present in the element.attributes array depend on the type of attribute. Depending on the type of input element, the value attribute might not be part of the list, meaning it isn’t accessible. It also means that you need to reference it directly using the notation element.value. The fact that some elements are displayed and others are not is a bit worrisome, because you’re left wondering what the browser is hiding. If you look closely at source code and the generated output of the inconsistent HTML page, you’ll notice that the browsers are handling one attribute inconsistently: They’re missing the onclick handler for the dynamically generated button element. The onclick attribute is generated for the statically defined button, but not for the dynamically generated button. You need to ask yourself if you want to generate the event handlers when the HTML is serialized. If you answer no, the htmlSerialize function will check for events by testing if the attribute starts with the letters o and n. But is it correct not to serialize the events? One can argue that if you’re going to serialize HTML, you need to serialize everything. However, serializing everything is difficult, because you must somehow store function references. You cannot store the implementation of a function in an attribute, because attributes cannot contain line feeds. Your decision should be based on what you most likely want to do with HTML serialization, and that is extracting state. The proposed solution is by no means complete, but it solves a specific context, and that is what you need to realize. When solving the problem of serializing the function reference, there is the question of how to serialize the function that has been assigned dynamically, as illustrated in the example HTML page. For illustration purposes, let’s try an initial serialization of functions, though you’ll quickly see that serialization of functions is rather complicated. To serialize functions, you’d rewrite the htmlSerialize function as follows:
htmlSerialize : function(element) {
var scriptBuffer = "";
var Recursive = function(element) {
var buffer = "";
if (element.nodeType == 1) {
buffer += "<" + element.nodeName + " ";
var didGenerateValue = false;
for (var i = 0; i < element.attributes.length; i ++) {
var attr = element.attributes[i];
var name = attr.name.toLowerCase();
if( name.charAt( 0) == 'o' && name.charAt( 1) == 'n') {
if( typeof( attr.value) == "string") {
buffer += attr.name + "=\"" + attr.value + "\" ";
}
}
else if ( attr.value != null && !(typeof(attr.value) == "string" &&
attr.value.length == 0)) {
if (new String(element[attr.name]).toLowerCase() ==
"undefined") {
buffer += attr.name + "=\"" + attr.value + "\" ";
}
else {
buffer += attr.name + "=\"" + element[attr.name] + "\" ";
}
}
if (attr.name.toLowerCase() == "value") {
didGenerateValue = true;
}
}
if (element.nodeName.toLowerCase() == "input" && !didGenerateValue) {
buffer += "value=\"" + element.value + "\" ";
}
if( element.onclick) {
scriptBuffer += "var " + element.id + "_onclick = " +
element.onclick;
buffer += "value=\"" + element.id + "_onclick()\"";
}
buffer += ">";
if (element.nodeName.toLowerCase() == "textarea") {
buffer += element.value;
}
else {
for (var i = 0; i < element.childNodes.length; i ++) {
buffer += Recursive(element.childNodes[i]);
}
}
buffer += "</" + element.nodeName + ">";
}
else if (element.nodeType == 3) {
buffer += element.nodeValue;
}
return buffer;
}
var buffer = ""
for (var i = 0; i < element.childNodes.length; i ++) {
buffer += Recursive(element.childNodes[i]);
}
return buffer;
}
The bold code in the modified implementation relates to the serialization of the event handlers. The first piece of bold code is the declaration of a script buffer. The script buffer is the script code that you saved with the serialized HTML. You need both pieces, as the script code will dynamically attach itself when you assign the serialized HTML using the innerHTML property. The second bold code is the serialization of the events that you declared at design time. If the element is attached dynamically, then any associated event is not serialized. The last and third piece of bold code is the problematic and touchy code. In the example HTML page, the onclick event was assigned dynamically where a button was injected dynamically. The onclick event was not a string that referenced another call. The onclick event referenced an anonymous JavaScript function. Because serialized attributes cannot contain complete function implementations, you must serialize the function and create a reference to the function resembling the serialization to the design-time HTML element declaration. The dynamically associated onclick event is an anonymous function, and therefore has no identifier. The bold code defines a function that is a concatenation of the element id and the onclick event. Though the dynamic example has no id, multiple identical onclick events will be generated. You could also generate a random identifier and then reference that random identifier in the serialized HTML. The random identifier solves the problem of the function identifier. But now there are two buffers. One buffer contains the serialized HTML code, and another buffer contains the script that you need to execute so that the function references won’t generate errors. As you can see, the code is now starting to become very complicated and rickety. This is why I recommend not serializing the events unless absolutely necessary. Remember the following points regarding this HTML serialization article: • HTML serialization is fraught with details, exceptions, and little problems. For example, when serializing using htmlSerialize, IE generates oodles of attributes that you didn’t define. This is due to defaults and makes it more difficult to extract some HTML. You should assume that the style sheet is managing defaults. • When implementing HTML serialization, don’t trust general solutions. Trust specific solutions that solve specific problems in a given context. • Properties that you assign with the method setAttribute are visible for both browsers when using the innerHTML property. • Modified HTML content that you save from the browser by using Save As is not stored consistently across all browsers. • Article 2 showed how to serialize a JavaScript object, and this article showed how to serialize DHTML. Each type of serialization is a specific serialization for a unique purpose. You need to be aware of these details and choose the appropriate serialization. |
legal disclaimer
Our website is not responsible for the information contained by this article. Web-articles is a free articles resource.
Suggestion: If you need fresh, daily updated content for your website, feel free to use our service. Click here for more information.
related articles
The focus of this article is to provide solutions to some common, general problems and questions that are bound to arise before or during development of Asynchronous JavaScript and XML (Ajax) and Representational State Transfer (REST) applications. These common questions are not always technical in nature, often leaning more toward theory or philosophy of development. The problem with these kinds of questions is that once you begin to think about them, you keep going in a circle and end up where you star...
Understanding the Definition and Philosophy of Web Services and SOA Wikipedia offers the following definition of Web services:4 The W3C defines aWeb service as a software system designed to support interoperable machine-to-machine interaction over a network. This definition encompasses many different systems, but in common usage the term refers to those services that use SOAPformatted XML envelopes and have their interfaces described by WSDL. For ex...
3. Understanding the Definition and Philosophy of REST
Understanding the Definition and Philosophy of REST REST is a controversial topic among Web service enthusiasts, because it’s considered to stand for the opposite of what Web services and SOA are trying to achieve. The problem with this thinking is that REST is not in contradiction with the abstract definition of SOA and Web services. REST is in contradiction with technologies such as SOAP, WSDL, and WS-* specifications. The following offers a quick definition of REST:...
4. The Easiest Way to Get Started with Ajax and REST
The Easiest Way to Get Started with Ajax and REST Problem You want to know the best way to get started with writing Ajax and REST. Solution When developing an Ajax and REST application, you must decide on the tools and frameworks you’ll use. The choice is simple: Use whatever you’re using today, and write some Ajax applications. You don’t need to change the tools you’re using today. Whether you’re using ASP.NET, JavaServer Pages (JSP), PHP, Ruby, or Python, you...
5. Testing a Dynamic Contract with Ajax
Coding the Contract Using Test-Driven Development Techniques Coding the contract using agile and test-driven development techniques requires writing a number of tests and implementing aMock URL layer. Problem You want to code the contract using these development techniques. Solution To demonstrate, let’s define a use case, implement the use case as a contract, write a test case(s) to implement the contract, implement the contract in the Mock URL, and finally...
6. Testing the Client Side Logic
Problem You want to effectively test your application’s client-side logic. Theory Testing GUI code tends not to be a productive task because of the complications that arise. The main complication is how to test the correctness of a user interface. Imagine a situation where clicking a button causes a table to be filled with data. Now imagine that when a check box is checked and the button is clicked again, a different table is filled with content. The fact that clicking the same button results in two ...
7. Understanding JavaScript and Types
Understanding JavaScript and Types Problem You want to work around the fact that JavaScript does not have types declared for its variables. Theory JavaScript code does not have any variables with a declared type. The lack of typed variables is apparent when you declare functions. That said, not having typed variable declarations does not mean JavaScript has no types or no type safety. Let’s start out with the simple declaration of a function, as illustrated by the following ex...