diff --git a/src/attributes.js b/src/attributes.js index e5425a051..cb8128d25 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -36,10 +36,10 @@ jQuery.fn.extend({ }, addClass: function( value ) { - if ( jQuery.isFunction(value) ) { + if ( jQuery.isFunction( value ) ) { return this.each(function(i) { var self = jQuery(this); - self.addClass( value.call(this, i, self.attr("class")) ); + self.addClass( value.call(this, i, self.attr("class") || "") ); }); } @@ -278,19 +278,10 @@ jQuery.extend({ // TODO: Check to see if any of these are needed anymore? // If not, it may be good to standardize on all-lowercase names instead attrFix: { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" }, attr: function( elem, name, value, pass ) { + // don't get/set attributes on text, comment and attribute nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { return undefined; @@ -314,6 +305,10 @@ jQuery.extend({ if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value )) !== undefined ) { return ret; + } else if ( value === null ) { + elem.removeAttribute( name ); + return undefined; + } else { // convert the value to a string (all browsers do this but IE) see #1070 value = "" + value; @@ -329,7 +324,7 @@ jQuery.extend({ return ret; } else { - + if ( !jQuery.hasAttr( elem, name ) ) { return undefined; } @@ -337,16 +332,14 @@ jQuery.extend({ var attr = elem.getAttribute( name ); // Non-existent attributes return null, we normalize to undefined - return attr === null ? - undefined : - attr; + return attr === null ? undefined : attr; } } }, hasAttr: function( elem, name ) { // Blackberry 4.7 returns "" from getAttribute #6938 - return elem.attributes[ name ] || (elem.hasAttribute && elem.hasAttribute( name )); + return elem && elem.attributes[ name ] || (elem.hasAttribute && elem.hasAttribute( name )); }, attrHooks: { @@ -361,7 +354,7 @@ jQuery.extend({ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - tabIndex: { + tabindex: { get: function( elem ) { var attributeNode = elem.getAttributeNode( "tabIndex" ); @@ -375,10 +368,11 @@ jQuery.extend({ }, // TODO: Check to see if we really need any here. - propFix: {}, + propFix: { + }, prop: function( elem, name, value ) { - var ret, hooks; + var ret, hooks, notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ); // Try to normalize/fix the name name = notxml && jQuery.propFix[ name ] || name; @@ -406,6 +400,20 @@ jQuery.extend({ propHooks: {} }); +// Remove certain attrs if set to false +jQuery.each([ "selected", "checked", "readonly", "disabled" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( !value ) { // '', undefined, false, null will remove attr + elem.removeAttribute( name ); + return false; + } + elem.setAttribute( name, value ); + return value; + } + }); +}); + // Some attributes require a special call on IE if ( !jQuery.support.hrefNormalized ) { jQuery.each([ "href", "src", "style" ], function( i, name ) { diff --git a/src/manipulation.js b/src/manipulation.js index 27f81cc24..81a55cb89 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -377,7 +377,7 @@ function cloneCopyEvent( src, dest ) { } } -function cloneFixAttributes(src, dest) { +function cloneFixAttributes( src, dest ) { // We do not need to do anything for non-Elements if ( dest.nodeType !== 1 ) { return; diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 8cf47bed6..d4284556b 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -3,38 +3,64 @@ module("attributes", { teardown: moduleTeardown }); var bareObj = function(value) { return value; }; var functionReturningObj = function(value) { return (function() { return value; }); }; -test("jQuery.props: itegrity test", function() { - - expect(1); - - // This must be maintained and equal jQuery.props - // Ensure that accidental or erroneous property - // overwrites don't occur - // This is simply for better code coverage and future proofing. - var propsShouldBe = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" - }; - - same(propsShouldBe, jQuery.props, "jQuery.props passes integrity check"); +// test("jQuery.props: integrity test", function() { +// +// expect(1); +// +// // This must be maintained and equal jQuery.props +// // Ensure that accidental or erroneous property +// // overwrites don't occur +// // This is simply for better code coverage and future proofing. +// var propsShouldBe = { +// "for": "htmlFor", +// "class": "className", +// readonly: "readOnly", +// maxlength: "maxLength", +// cellspacing: "cellSpacing", +// rowspan: "rowSpan", +// colspan: "colSpan", +// tabindex: "tabIndex", +// usemap: "useMap", +// frameborder: "frameBorder" +// }; +// +// same(propsShouldBe, jQuery.props, "jQuery.props passes integrity check"); +// +// }); +test("prop", function() { + equals( jQuery('#text1').prop('value'), "Test", 'Check for value attribute' ); + equals( jQuery('#text1').prop('value', "Test2").prop('defaultValue'), "Test", 'Check for defaultValue attribute' ); + equals( jQuery('#select2').prop('selectedIndex'), 3, 'Check for selectedIndex attribute' ); + equals( jQuery('#foo').prop('nodeName').toUpperCase(), 'DIV', 'Check for nodeName attribute' ); + equals( jQuery('#foo').prop('tagName').toUpperCase(), 'DIV', 'Check for tagName attribute' ); + equals( jQuery("").prop("selected"), false, "Check selected attribute on disconnected element." ); + + var body = document.body, $body = jQuery( body ); + ok( $body.prop('nextSibling') === null, 'Make sure a null expando returns null' ); + body.foo = 'bar'; + equals( $body.prop('foo'), 'bar', 'Make sure the expando is preferred over the dom attribute' ); + body.foo = undefined; + ok( $body.attr('foo') === undefined, 'Make sure the expando is preferred over the dom attribute, even if undefined' ); + + var select = document.createElement("select"), optgroup = document.createElement("optgroup"), option = document.createElement("option"); + optgroup.appendChild( option ); + select.appendChild( optgroup ); + equals( jQuery(option).prop("selected"), true, "Make sure that a single option is selected, even when in an optgroup." ); + equals( jQuery(document).prop("nodeName"), "#document", "prop works correctly on document nodes (bug #7451)." ); + + var attributeNode = document.createAttribute("irrelevant"), + commentNode = document.createComment("some comment"), + textNode = document.createTextNode("some text"), + obj = {}; + jQuery.each( [document, attributeNode, commentNode, textNode, obj, "#firstp"], function( i, ele ) { + strictEqual( jQuery(ele).prop("nonexisting"), undefined, "prop works correctly for non existing attributes (bug #7500)." ); + }); }); test("attr(String)", function() { - expect(37); + expect(20); - // This one sometimes fails randomly ?! - equals( jQuery('#text1').attr('value'), "Test", 'Check for value attribute' ); - - equals( jQuery('#text1').attr('value', "Test2").attr('defaultValue'), "Test", 'Check for defaultValue attribute' ); equals( jQuery('#text1').attr('type'), "text", 'Check for type attribute' ); equals( jQuery('#radio1').attr('type'), "radio", 'Check for type attribute' ); equals( jQuery('#check1').attr('type'), "checkbox", 'Check for type attribute' ); @@ -51,55 +77,31 @@ test("attr(String)", function() { equals( jQuery('#text1').attr('maxlength'), '30', 'Check for maxlength attribute' ); equals( jQuery('#text1').attr('maxLength'), '30', 'Check for maxLength attribute' ); equals( jQuery('#area1').attr('maxLength'), '30', 'Check for maxLength attribute' ); - equals( jQuery('#select2').attr('selectedIndex'), 3, 'Check for selectedIndex attribute' ); - equals( jQuery('#foo').attr('nodeName').toUpperCase(), 'DIV', 'Check for nodeName attribute' ); - equals( jQuery('#foo').attr('tagName').toUpperCase(), 'DIV', 'Check for tagName attribute' ); // using innerHTML in IE causes href attribute to be serialized to the full path jQuery('').attr({ 'id': 'tAnchor5', 'href': '#5' }).appendTo('#main'); equals( jQuery('#tAnchor5').attr('href'), "#5", 'Check for non-absolute href (an anchor)' ); - equals( jQuery("").attr("selected"), false, "Check selected attribute on disconnected element." ); - - // Related to [5574] and [5683] var body = document.body, $body = jQuery(body); ok( $body.attr('foo') === undefined, 'Make sure that a non existent attribute returns undefined' ); - ok( $body.attr('nextSibling') === null, 'Make sure a null expando returns null' ); body.setAttribute('foo', 'baz'); equals( $body.attr('foo'), 'baz', 'Make sure the dom attribute is retrieved when no expando is found' ); - body.foo = 'bar'; - equals( $body.attr('foo'), 'bar', 'Make sure the expando is preferred over the dom attribute' ); - $body.attr('foo','cool'); equals( $body.attr('foo'), 'cool', 'Make sure that setting works well when both expando and dom attribute are available' ); - body.foo = undefined; - ok( $body.attr('foo') === undefined, 'Make sure the expando is preferred over the dom attribute, even if undefined' ); - body.removeAttribute('foo'); // Cleanup var select = document.createElement("select"), optgroup = document.createElement("optgroup"), option = document.createElement("option"); optgroup.appendChild( option ); select.appendChild( optgroup ); - equals( jQuery(option).attr("selected"), true, "Make sure that a single option is selected, even when in an optgroup." ); - ok( jQuery("
").attr("doesntexist") === undefined, "Make sure undefined is returned when no attribute is found." ); ok( jQuery().attr("doesntexist") === undefined, "Make sure undefined is returned when no element is there." ); - equals( jQuery(document).attr("nodeName"), "#document", "attr works correctly on document nodes (bug #7451)." ); - - var attributeNode = document.createAttribute("irrelevant"), - commentNode = document.createComment("some comment"), - textNode = document.createTextNode("some text"), - obj = {}; - jQuery.each( [document, attributeNode, commentNode, textNode, obj, "#firstp"], function( i, ele ) { - strictEqual( jQuery(ele).attr("nonexisting"), undefined, "attr works correctly for non existing attributes (bug #7500)." ); - }); }); if ( !isLocal ) { @@ -133,7 +135,7 @@ test("attr(Hash)", function() { }); test("attr(String, Object)", function() { - expect(30); + expect(24); var div = jQuery("div").attr("foo", "bar"), fail = false; @@ -153,7 +155,7 @@ test("attr(String, Object)", function() { jQuery("#name").attr('name', 'something'); equals( jQuery("#name").attr('name'), 'something', 'Set name attribute' ); jQuery("#name").attr('name', null); - equals( jQuery("#name").attr('title'), '', 'Remove name attribute' ); + equals( jQuery("#name").attr('name'), undefined, 'Remove name attribute' ); jQuery("#check2").attr('checked', true); equals( document.getElementById('check2').checked, true, 'Set checked attribute' ); jQuery("#check2").attr('checked', false); @@ -163,28 +165,28 @@ test("attr(String, Object)", function() { jQuery("#text1").attr('readonly', false); equals( document.getElementById('text1').readOnly, false, 'Set readonly attribute' ); jQuery("#name").attr('maxlength', '5'); - equals( document.getElementById('name').maxLength, '5', 'Set maxlength attribute' ); + equals( document.getElementById('name').maxLength, 5, 'Set maxlength attribute' ); jQuery("#name").attr('maxLength', '10'); - equals( document.getElementById('name').maxLength, '10', 'Set maxlength attribute' ); + equals( document.getElementById('name').maxLength, 10, 'Set maxlength attribute' ); - var attributeNode = document.createAttribute("irrelevant"), - commentNode = document.createComment("some comment"), - textNode = document.createTextNode("some text"), - obj = {}; - jQuery.each( [document, obj, "#firstp"], function( i, ele ) { - var $ele = jQuery( ele ); - $ele.attr( "nonexisting", "foo" ); - equal( $ele.attr("nonexisting"), "foo", "attr(name, value) works correctly for non existing attributes (bug #7500)." ); - }); - jQuery.each( [commentNode, textNode, attributeNode], function( i, ele ) { - var $ele = jQuery( ele ); - $ele.attr( "nonexisting", "foo" ); - strictEqual( $ele.attr("nonexisting"), undefined, "attr(name, value) works correctly on comment and text nodes (bug #7500)." ); - }); - //cleanup - jQuery.each( [document, "#firstp"], function( i, ele ) { - jQuery( ele ).removeAttr("nonexisting"); - }); + // var attributeNode = document.createAttribute("irrelevant"), + // commentNode = document.createComment("some comment"), + // textNode = document.createTextNode("some text"), + // obj = {}; + // jQuery.each( [document, obj, "#firstp"], function( i, ele ) { + // var $ele = jQuery( ele ); + // $ele.attr( "nonexisting", "foo" ); + // equal( $ele.attr("nonexisting"), "foo", "attr(name, value) works correctly for non existing attributes (bug #7500)." ); + // }); + // jQuery.each( [commentNode, textNode, attributeNode], function( i, ele ) { + // var $ele = jQuery( ele ); + // $ele.attr( "nonexisting", "foo" ); + // strictEqual( $ele.attr("nonexisting"), undefined, "attr(name, value) works correctly on comment and text nodes (bug #7500)." ); + // }); + // //cleanup + // jQuery.each( [document, "#firstp"], function( i, ele ) { + // jQuery( ele ).removeAttr("nonexisting"); + // }); var table = jQuery('#table').append("