Lazy HTML attributes wrapping in Internet Explorer
Having encountered this Internet Explorer (all versions) behavior several times previously, I eventually decided to share this most probably useless knowledge. Excuse my lengthy explanations because I am going to show the behavior along with a very simple case when one can come across it inadvertently.
Let's say I want to implement some simple templating solution in JavaScript. I wrote an HTML template with an intention to bind data to it on the client side:
Please note, that name of the “sys-template” class is just a coincidence. I do not use any ASP.NET AJAX code in this simple example.
As you can see we need to replace placeholders (property name wrapped with curly braces) with actual data. Also, as you can see, many of the placeholders are situated within attribute values and it is where the danger lies.
I am going to use <a /> element HTML as a template and replace each placeholder pattern with respective properties’ values with a little bit of jQuery like this:
You can find complete code along with the contextFormat() method definition at the end of the post.
Let’s assume that value for the name property (that we want to put in the title attribute) of the first data item is “first tooltip”. So it consists of two words. When the replacement occurred, title attribute should contain the “first tooltip” text which we are going to see as a tooltip for the <a /> element.
But let’s run the sample code in Internet Explorer and check it out. What you’ll see is that only the first word of the supposed “title” attribute’s content is shown. So, were is the rest of my attribute and what happened?
The answer is obvious once you see the result of jQuery(“.sys-template”).html() line for the given HTML markup. In IE you’ll get the following
<A id={id} class={cssClass} title={name} href="{source}" myAttr="{attr}">Link to {source}</A>
See any difference between this HTML and the one shown earlier? No? Then look carefully. While the original HTML of the <a /> element is well-formed and all the attributes are correctly quoted, when you take the same HTML back in Internet Explorer (it doesn’t matter whether you use html() method from jQuery library or IE’s innerHTML directly), you lose attributes’ quotes for some of the attributes.
Then, after replacement, we’ll get following HTML for our first data item. I marked the attribute value in question with italic:
<A id=1 class=first title=first tooltip href="first.html" myAttr="firstAttr">Link to first.html</A>
Now you can easily imagine for yourself what happens when this HTML is inserted into the document and why we do not see the second (and any subsequent words if any) of our title attribute in the tooltip.
There are still two important things to note.
The first one (and it actually the reason why I named the post “lazy wrapping” is that if value of the HTML attribute does contains spaces in the original HTML, then it WILL be wrapped with quotation marks.
For example, if I wrote following on my page (note the trailing space for the title attribute value)
<a href="{source}" title="{name} " id="{id}" myAttr="{attr}" class="{cssClass}">Link to {source}</a>
then I would have my placeholder quoted correctly and the result of the replacement would render as expected:
The second important thing to note is that there are exceptions to the lazy attributes wrapping rule in IE.
- As you can see href attribute value did not contain spaces exactly as all the other attributes with placeholders, but it was still returned correctly quoted
- Custom attribute myAttr is also quoted correctly when returned back from document, though its placeholder value does not contain spaces either.
Now, on account of the highly unlikely probability that you found this information useful and need a solution to the problem the aforementioned behavior introduces for Internet Explorer browser, I can suggest a simple workaround – manually quote the mischievous attributes prior the placeholder pattern is replaced. Using the code of contextFormat() method shown below, you would need to add following line right before the return statement:
result = result.replace(/=({([^}]+)})/g, '="$1"');
Below please find original sample code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Lazy attributes wrapping</title>
<style type="text/css"> .sys-template { display: none; } </style>
<script type="text/javascript" src="jquery-1.4.2.js"></script>
<script type="text/javascript">
String.prototype.contextFormat = function(context) {
var result = this.valueOf(), reParam = /{([^}]+)}/g;
/// result = result.replace(/=({([^}]+)})/g, '="$1"'); // fix
result = result.replace(reParam, function(match, prop) {
return context.hasOwnProperty(prop) ? context[prop] : match;
});
return result;
};
var data = [
{ source: "first.html", name: "first tooltip", id: 1, attr: "firstAttr", cssClass: "first" },
{ source: "second.html", name: "second tooltip", id: 2, attr: "secondAttr", cssClass: "second" },
{ source: "third.html", name: "third tooltip", id: 3, attr: "thirdAttr", cssClass: "third" }
];
jQuery(document).ready(function() {
var list = jQuery("<ul />").appendTo(document.body),
html = jQuery(".sys-template").html();
jQuery.each(data, function(index) {
jQuery("<li />").html(html.contextFormat(this)).appendTo(list);
});
});
</script>
</head>
<body>
<div class="sys-template">
<a href="{source}" title="{name}" id="{id}" myAttr="{attr}"
class="{cssClass}">Link to {source}</a>
</div>
</body>
</html>