diff --git a/src/attributes.js b/src/attributes.js index c22e89b7..c207a001 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -6,7 +6,7 @@ var rclass = /[\n\t\r]/g, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, - rboolean = /^(?:autofocus|autoplay|async|checked|controls|declare|defer|disabled|draggable|formnovalidate|hidden|ismap|loop|multiple|muted|noresize|noshade|nowrap|novalidate|open|pubdate|readonly|required|reversed|scoped|seamless|selected|truespeed)$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, rinvalidChar = /\:/, formHook, boolHook; @@ -219,7 +219,8 @@ jQuery.extend({ }, select: { get: function( elem ) { - var index = elem.selectedIndex, + var value, + index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; @@ -310,15 +311,15 @@ jQuery.extend({ hooks = jQuery.attrHooks[ name ]; if ( !hooks ) { - // Use formHook for forms and if the name contains certain characters - if ( formHook && (jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) { - hooks = formHook; - // Use boolHook for boolean attributes - } else if ( rboolean.test( name ) && - (typeof value === "boolean" || value === undefined) ) { + if ( rboolean.test( name ) && + (typeof value === "boolean" || value === undefined || value.toLowerCase() === name.toLowerCase()) ) { hooks = boolHook; + + // Use formHook for forms and if the name contains certain characters + } else if ( formHook && (jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) { + hooks = formHook; } } @@ -351,6 +352,7 @@ jQuery.extend({ }, removeAttr: function( elem, name ) { + var propName; if ( elem.nodeType === 1 ) { name = jQuery.attrFix[ name ] || name; @@ -363,8 +365,8 @@ jQuery.extend({ } // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) ) { - elem[ jQuery.propFix[ name ] || name ] = false; + if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { + elem[ propName ] = false; } } }, @@ -464,13 +466,19 @@ boolHook = { undefined; }, 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 - elem[ jQuery.propFix[ name ] || name ] = value; + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = value; + } + elem.setAttribute( name, name.toLowerCase() ); } return name; @@ -487,12 +495,9 @@ if ( !jQuery.support.getSetAttribute ) { formHook = jQuery.attrHooks.name = jQuery.attrHooks.value = jQuery.valHooks.button = { get: function( elem, name ) { var ret; - if ( name === "value" && !jQuery.nodeName( elem, "button" ) ) { - return elem.getAttribute( name ); - } ret = elem.getAttributeNode( name ); - // Return undefined if not specified instead of empty string - return ret && ret.specified ? + // Return undefined if nodeValue is empty string + return ret && ret.nodeValue !== "" ? ret.nodeValue : undefined; }, diff --git a/src/data.js b/src/data.js index c2fd558f..9e5d1ab0 100644 --- a/src/data.js +++ b/src/data.js @@ -284,7 +284,7 @@ function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase(); + var name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase(); data = elem.getAttribute( name ); diff --git a/src/effects.js b/src/effects.js index 832ef5da..88661e0e 100644 --- a/src/effects.js +++ b/src/effects.js @@ -126,6 +126,9 @@ jQuery.fn.extend({ return this.each( optall.complete, [ false ] ); } + // Do not change referenced properties as per-property easing will be lost + prop = jQuery.extend( {}, prop ); + return this[ optall.queue === false ? "each" : "queue" ](function() { // XXX 'this' does not always have a nodeName when running the // test suite @@ -134,7 +137,7 @@ jQuery.fn.extend({ jQuery._mark( this ); } - var opt = jQuery.extend({}, optall), + var opt = jQuery.extend( {}, optall ), isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), name, val, p, @@ -153,10 +156,18 @@ jQuery.fn.extend({ delete prop[ p ]; } - val = prop[name]; + val = prop[ name ]; + + // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) + if ( jQuery.isArray( val ) ) { + opt.animatedProperties[ name ] = val[ 1 ]; + val = prop[ name ] = val[ 0 ]; + } else { + opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; + } if ( val === "hide" && hidden || val === "show" && !hidden ) { - return opt.complete.call(this); + return opt.complete.call( this ); } if ( isElement && ( name === "height" || name === "width" ) ) { @@ -175,7 +186,7 @@ jQuery.fn.extend({ this.style.display = "inline-block"; } else { - display = defaultDisplay(this.nodeName); + display = defaultDisplay( this.nodeName ); // inline-level elements accept inline-block; // block-level elements need to be inline with layout @@ -189,11 +200,6 @@ jQuery.fn.extend({ } } } - - // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) - opt.animatedProperties[name] = jQuery.isArray( val ) ? - val[1]: - opt.specialEasing && opt.specialEasing[name] || opt.easing || 'swing'; } if ( opt.overflow != null ) { @@ -202,19 +208,18 @@ jQuery.fn.extend({ for ( p in prop ) { e = new jQuery.fx( this, opt, p ); - - val = prop[p]; + val = prop[ p ]; if ( rfxtypes.test(val) ) { e[ val === "toggle" ? hidden ? "show" : "hide" : val ](); } else { - parts = rfxnum.exec(val); + parts = rfxnum.exec( val ); start = e.cur(); if ( parts ) { end = parseFloat( parts[2] ); - unit = parts[3] || ( jQuery.cssNumber[ name ] ? "" : "px" ); + unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); // We need to compute starting value if ( unit !== "px" ) { @@ -225,7 +230,7 @@ jQuery.fn.extend({ // If a +=/-= token was provided, we're doing a relative animation if ( parts[1] ) { - end = ((parts[1] === "-=" ? -1 : 1) * end) + start; + end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; } e.custom( start, end, unit ); @@ -501,10 +506,10 @@ jQuery.fx.prototype = { this.now = t; } else { n = t - this.startTime; - this.state = n / options.duration; + // Perform the easing function, defaults to swing - this.pos = jQuery.easing[options.animatedProperties[this.prop]](this.state, n, 0, 1, options.duration); + this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, options.duration ); this.now = this.start + ((this.end - this.start) * this.pos); } // Perform the next step of the animation diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 257d02d0..ce7775d1 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -138,13 +138,13 @@ test("attr(Hash)", function() { if ( this.getAttribute("foo") != "baz" && this.getAttribute("zoo") != "ping" ) pass = false; }); ok( pass, "Set Multiple Attributes" ); - equals( jQuery("#text1").attr({value: function() { return this.id; }})[0].value, "text1", "Set attribute to computed value #1" ); - equals( jQuery("#text1").attr({title: function(i) { return i; }}).attr("title"), "0", "Set attribute to computed value #2"); + equals( jQuery("#text1").attr({value: function() { return this.id; }})[0].value, "text1", "Set attribute to computed value #1" ); + equals( jQuery("#text1").attr({title: function(i) { return i; }}).attr("title"), "0", "Set attribute to computed value #2"); }); test("attr(String, Object)", function() { - expect(57); + expect(59); var div = jQuery("div").attr("foo", "bar"), fail = false; @@ -164,6 +164,8 @@ test("attr(String, Object)", function() { equals( jQuery("#name").attr("name"), "something", "Set name attribute" ); jQuery("#name").attr("name", null); equals( jQuery("#name").attr("name"), undefined, "Remove name attribute" ); + var $input = jQuery("", { name: "something" }); + equals( $input.attr("name"), "something", "Check element creation gets/sets the name attribute." ); jQuery("#check2").prop("checked", true).prop("checked", false).attr("checked", true); equals( document.getElementById("check2").checked, true, "Set checked attribute" ); @@ -209,7 +211,11 @@ test("attr(String, Object)", function() { $p.removeAttr("nonexisting"); var $text = jQuery("#text1").attr("autofocus", true); - equals( $text.attr("autofocus"), "autofocus", "Set boolean attributes to the same name"); + if ( "autofocus" in $text[0] ) { + equals( $text.attr("autofocus"), "autofocus", "Set boolean attributes to the same name"); + } else { + equals( $text.attr("autofocus"), undefined, "autofocus stays undefined in browsers that do not support it(F<4)"); + } equals( $text.attr("autofocus", false).attr("autofocus"), undefined, "Setting autofocus attribute to false removes it"); equals( $text.attr("data-something", true).data("something"), true, "Setting data attributes are not affected by boolean settings"); equals( $text.attr("data-another", false).data("another"), false, "Setting data attributes are not affected by boolean settings" ); @@ -238,6 +244,8 @@ test("attr(String, Object)", function() { table.attr("cellspacing", "2"); equals( table[0].cellSpacing, "2", "Check cellspacing is correctly set" ); + equals( jQuery("#area1").attr("value"), undefined, "Value attribute retrieved correctly on textarea." ); + // for #1070 jQuery("#name").attr("someAttr", "0"); equals( jQuery("#name").attr("someAttr"), "0", "Set attribute to a string of \"0\"" ); @@ -500,7 +508,7 @@ test("removeProp(String)", function() { strictEqual( ele.nonexisting, undefined, "removeProp works correctly on non DOM element nodes (bug #7500)." ); }); jQuery.each( [commentNode, textNode, attributeNode], function( i, ele ) { - $ele = jQuery( ele ); + var $ele = jQuery( ele ); $ele.prop( "nonexisting", "foo" ).removeProp( "nonexisting" ); strictEqual( ele.nonexisting, undefined, "removeProp works correctly on non DOM element nodes (bug #7500)." ); }); diff --git a/test/unit/effects.js b/test/unit/effects.js index d0518217..ea7f4e72 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -32,7 +32,8 @@ test("show()", function() { hiddendiv.css("display",""); - var pass = true, div = jQuery("#qunit-fixture div"); + var pass = true; + div = jQuery("#qunit-fixture div"); div.show().each(function(){ if ( this.style.display == "none" ) pass = false; }); @@ -582,7 +583,7 @@ jQuery.checkOverflowDisplay = function(){ equals(jQuery.css( this, "display" ), "inline", "Display shouldn't be tampered with."); start(); -} +}; test( "jQuery.fx.prototype.cur()", 6, function() { var div = jQuery( "
" ).appendTo( "#qunit-fixture" ).css({ @@ -694,8 +695,8 @@ jQuery.each( { jQuery(elem).css(prop,prop == "opacity" ? 0 : "0px"); return 0; } -}, function(fn, f){ - jQuery.each( { +}, function( fn, f ) { + jQuery.each({ "show": function(elem,prop){ jQuery(elem).hide().addClass("wide"+prop); return "show"; @@ -901,7 +902,7 @@ jQuery.makeTest = function( text ){ .after( elem ); return elem; -} +}; jQuery.makeTest.id = 1; @@ -922,34 +923,42 @@ test("jQuery.show('fast') doesn't clear radio buttons (bug #1095)", function () test("animate with per-property easing", function(){ - expect(3); + expect(5); stop(); - var _test1_called = false; - var _test2_called = false; - var _default_test_called = false; + var data = { a:0, b:0, c:0 }, + _test1_called = false, + _test2_called = false, + _default_test_called = false, + props = { + a: [ 100, "_test1" ], + b: [ 100, "_test2" ], + c: 100 + }; - jQuery.easing["_test1"] = function() { + jQuery.easing["_test1"] = function(p) { _test1_called = true; + return p; }; - jQuery.easing["_test2"] = function() { + jQuery.easing["_test2"] = function(p) { _test2_called = true; + return p; }; - jQuery.easing["_default_test"] = function() { + jQuery.easing["_default_test"] = function(p) { _default_test_called = true; + return p; }; - jQuery({a:0,b:0,c:0}).animate({ - a: [100, "_test1"], - b: [100, "_test2"], - c: 100 - }, 400, "_default_test", function(){ + jQuery(data).animate( props, 400, "_default_test", function(){ start(); - ok(_test1_called, "Easing function (1) called"); - ok(_test2_called, "Easing function (2) called"); - ok(_default_test_called, "Easing function (_default) called"); + + ok( _test1_called, "Easing function (_test1) called" ); + ok( _test2_called, "Easing function (_test2) called" ); + ok( _default_test_called, "Easing function (_default) called" ); + equal( props.a[ 1 ], "_test1", "animate does not change original props (per-property easing would be lost)"); + equal( props.b[ 1 ], "_test2", "animate does not change original props (per-property easing would be lost)"); }); }); @@ -993,3 +1002,30 @@ test("animate unit-less properties (#4966)", 2, function() { start(); }); }); + +test( "animate properties missing px w/ opacity as last (#9074)", 2, function() { + expect( 6 ); + stop(); + var div = jQuery( "
" ) + .appendTo( "#qunit-fixture" ); + function cssInt( prop ) { + return parseInt( div.css( prop ), 10 ); + } + equal( cssInt( "marginLeft" ), 0, "Margin left is 0" ); + equal( cssInt( "left" ), 0, "Left is 0" ); + div.animate({ + left: 200, + marginLeft: 200, + opacity: 0 + }, 1000); + setTimeout(function() { + var ml = cssInt( "marginLeft" ), + l = cssInt( "left" ); + notEqual( ml, 0, "Margin left is not 0 after partial animate" ); + notEqual( ml, 200, "Margin left is not 200 after partial animate" ); + notEqual( l, 0, "Left is not 0 after partial animate" ); + notEqual( l, 200, "Left is not 200 after partial animate" ); + div.stop().remove(); + start(); + }, 100); +});