Our blog, keeping you up-to-date on our latest news.

 

A Bug in JQuery Latest Release(v1.6.2)

July 16, 2011 at 7:33 pm | Blog | 2 comments

 

One of My colleagues asked for help on a JavaScript feature. He had several input boxes, and a button. By default, all input boxes are not editable, only when the user clicks the button. It is a very simple feature, and he had already implemented. The problem was, he could not get it work in IE. (IE 8 support is a “must” in the company) .

Here is a tailored version of his code (simplified):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<html>
<head>
<title>pengbo's readonly test</title>
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.js"
	type="text/javascript"></script>
</head>
<body>
<center>
<h2>Readonly?</h2>
<table>
	<tr>
		<td>username</td>
		<td><input name="username" type="text" value="pengbos" size=70 id="username"
			class="inputbox" /></td>
	</tr>
	<tr>
		<td>mail_to_adderss</td>
		<td><input name="mail_to_address" type="text" id="mail_to_address"
			value="pengbos.com@gmail.com" size=70 class="inputbox" /></td>
	</tr>
	<tr>
		<td align="center" colspan="2"><input type="button" value="edit"
			onclick="submitClick(this)"></td>
	</tr>
</table>
</center>
</body>
<script>
	function submitClick(b) {
		if (b.value == "edit") {
			b.value = "submit";
			$(".inputbox").attr("readonly", false);
 
		} else if (b.value == "submit") {
			b.value = "edit";
			$(".inputbox").attr("readonly", true);
		}
	}
	$("document").ready(function() {
		$(".inputbox").attr("readonly", true);
	});
</script>
</html>

it worked in Firefox, but it just did not work in IE 8 (or other IE versions like IE 6,7 ).

Test it against FF/IE: Demo

When i use “Developer Tools” to debug, i found that, when i clicked edit, one of the element’s attribute “isContentEditable” is still false.

If we use raw javascript(without jQuery) to implement this feature, we will write:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	function submitClick(b) {
		if (b.value == "edit") {
			b.value = "submit";
			//$(".inputbox").attr("readonly", false);
			document.getElementById("mail_to_address").setAttribute("readOnly","");
 
			document.getElementById("username").setAttribute("readOnly","");
 
		} else if (b.value == "submit") {
			b.value = "edit";
			//$(".inputbox").attr("readonly", true);
			document.getElementById("mail_to_address").setAttribute("readOnly","readonly");
                        document.getElementById("username").setAttribute("readOnly","readonly");
		}
	}

And, if we use the debug tool to look into the element, we will find that:

The difference is that the “isContentEditable” is changed to “true”, which is the expected result. So why does jQuery 1.6.2 not change the value to “true”?

Let’s exam jQuery’s source code. first, let’s look at the “attr” function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
attr: function( elem, name, value, pass ) {
		var nType = elem.nodeType;
 
		// don't get/set attributes on text, comment and attribute nodes
		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
			return undefined;
		}
 
		if ( pass && name in jQuery.attrFn ) {
			return jQuery( elem )[ name ]( value );
		}
 
		// Fallback to prop when attributes are not supported
		if ( !("getAttribute" in elem) ) {
			return jQuery.prop( elem, name, value );
		}
 
		var ret, hooks,
			notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
 
		// Normalize the name if needed
		if ( notxml ) {
			name = jQuery.attrFix[ name ] || name;
 
			hooks = jQuery.attrHooks[ name ];
 
			if ( !hooks ) {
				// Use boolHook for boolean attributes
				if ( rboolean.test( name ) ) {
 
					hooks = boolHook;
 
				// Use formHook for forms and if the name contains certain characters
				} else if ( formHook && name !== "className" &&
					(jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) {
 
					hooks = formHook;
				}
			}
		}
 
		if ( value !== undefined ) {
 
			if ( value === null ) {
				jQuery.removeAttr( elem, name );
				return undefined;
 
			} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
				return ret;
 
			} else {
				elem.setAttribute( name, "" + value );
				return value;
			}
 
		} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
			return ret;
 
		} else {
 
			ret = elem.getAttribute( name );
 
			// Non-existent attributes return null, we normalize to undefined
			return ret === null ?
				undefined :
				ret;
		}
	},

we can also debug the code step by step in the developer tool. the passed in parameters are: name is “readOnly” value is “false”
when the regular express rboolean test this name readOnly, it returns true as it is a boolean attribute. So there is a boolean hook involved: boolHook (see the highlighted line by yellow in the following diagram).

as the passed in value is false, and there is a boolHook, the code runs into the line 49.

the boolean hook calls set function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Hook for boolean attributes
boolHook = {
	set: function( elem, value, name ) {
		var propName;
		if ( value === false ) {
			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else {
			// value is true since we know at this point it's type boolean and not false
			// Set boolean attributes to the same name and set the DOM property
			propName = jQuery.propFix[ name ] || name;
			if ( propName in elem ) {
				// Only set the IDL specifically if it already exists on the element
				elem[ propName ] = true;
			}
 
			elem.setAttribute( name, name.toLowerCase() );
		}
		return name;
	}
};

The boolean hook is really simple, if the value is false, remove the attribute, and if the value is true, it set attribute. In our case, it calls “jQuery.removeAttr( elem, name );”

following is the remove Attr function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	removeAttr: function( elem, name ) {
		var propName;
		if ( elem.nodeType === 1 ) {
			name = jQuery.attrFix[ name ] || name;
 
			if ( jQuery.support.getSetAttribute ) {
				// Use removeAttribute in browsers that support it
				elem.removeAttribute( name );
			} else {
				jQuery.attr( elem, name, "" );
				elem.removeAttributeNode( elem.getAttributeNode( name ) );
			}
 
			// Set corresponding property to false for boolean attributes
			if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) {
				elem[ propName ] = false;
			}
		}
	},

In IE the criteria: jQuery.support.getSetAttribute is false. So the code runs into
jQuery.attr( elem, name, “” );
elem.removeAttributeNode( elem.getAttributeNode( name ) );

and it calls attr function again. This time, the passed in value is an empty string instead of false.

but when the code runs into the boolean regular expression check, it only checks the name. (code line 30 in the attr function) and it get the boolHook again. And then the code runs into hoolHook’s set function. It is different from the first invocation, at this time, the passed in value is an empty string, instead of false. However, boolHook takes this values as “true”(see code at line 8 of boolHook’s set function). And it call elem.setAttribute( name, name.toLowerCase() ); it is totally not expected.

As in the attr function (line 30), it does not check if the value is boolean or string. but in fact, we need to check the value as well, if value is a boolen, then introduce hook, otherwise, don’t use boolHook.

If change the code to check the value type as well, the hooks will not be assigned a boolHook. And the code will run to line 53 in attr function. the result is just the same as our raw javascript way. And it will change the “isContentEditable” value during the removeAttr call.

so the code in line 30 in the attr function can be changed to:

1
2
3
4
5
6
// Use boolHook for boolean attributes
if ( rboolean.test( name ) &&
	(typeof value === "boolean" || value === undefined || value.toLowerCase() === name.toLowerCase()) ) {
 
	hooks = boolHook;
}

Actually, jQuery 1.6.1 is doing this way. I am not sure why does version 1.6.2 have this change.

Thats’ what i found. Hope it help you with “readOnly” issues too.

<< Back to Blog Discuss this post

 

2 Comments to “A Bug in JQuery Latest Release(v1.6.2)”

  1. css says:

    hi, this is the colleague who raised this question, hehe. thanks for your investigation pengbo, and it’s pretty clear.

    And it’s amazing that you are running your website as your professional column.

    BTW, what kind of developer tool are you using to trace this issue?

    admin Reply:

    IE 8 has a tool called developer tool.