From 9f8cd6c499844451468257140e71f611abb3a040 Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Tue, 14 Dec 2010 21:53:04 -0500 Subject: [PATCH 01/91] Fixing $.proxy to work like (and use) Function.prototype.bind (ticket #7783) http://bugs.jquery.com/ticket/7783 --- src/core.js | 49 +++++++++++++++++++++++++++++------------------ src/event.js | 44 ++++++++++++++++++++++++++---------------- test/unit/core.js | 11 ++++++++--- 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/core.js b/src/core.js index 346e52d7..74ec4ea0 100644 --- a/src/core.js +++ b/src/core.js @@ -740,31 +740,42 @@ jQuery.extend({ // A global GUID counter for objects guid: 1, - proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 ) { - if ( typeof proxy === "string" ) { - thisObject = fn; - fn = thisObject[ proxy ]; - proxy = undefined; + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy; - } else if ( proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( ! jQuery.isFunction( fn ) ) { + return undefined; + } + + if ( jQuery.isFunction( Function.prototype.bind ) ) { + // Native bind + args = slice.call( arguments, 1 ); + proxy = Function.prototype.bind.apply( fn, args ); + } else { + // Simulated bind + args = slice.call( arguments, 2 ); + if ( args.length ) { + proxy = function() { + return arguments.length ? + fn.apply( context, args.concat( slice.call( arguments ) ) ) : + fn.apply( context, args ); + }; + } else { + proxy = function() { + return arguments.length ? + fn.apply( context, arguments ) : + fn.call( context ); + }; } } - if ( !proxy && fn ) { - proxy = function() { - return fn.apply( thisObject || this, arguments ); - }; - } - // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - // So proxy can be declared as an argument return proxy; }, diff --git a/src/event.js b/src/event.js index fd470e71..82e9b52d 100644 --- a/src/event.js +++ b/src/event.js @@ -891,6 +891,8 @@ if ( document.addEventListener ) { jQuery.each(["bind", "one"], function( i, name ) { jQuery.fn[ name ] = function( type, data, fn ) { + var handler; + // Handle object literals if ( typeof type === "object" ) { for ( var key in type ) { @@ -904,10 +906,15 @@ jQuery.each(["bind", "one"], function( i, name ) { data = undefined; } - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } if ( type === "unload" && name !== "one" ) { this.one( type, data, fn ); @@ -971,24 +978,27 @@ jQuery.fn.extend({ toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, - i = 1; + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); + args[ i++ ].guid = guid; } - return this.click( jQuery.proxy( fn, function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); + return this.click( toggler ); }, hover: function( fnOver, fnOut ) { diff --git a/test/unit/core.js b/test/unit/core.js index 70577837..9f9078a4 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -858,7 +858,7 @@ test("jQuery.isEmptyObject", function(){ }); test("jQuery.proxy", function(){ - expect(4); + expect(5); var test = function(){ equals( this, thisObject, "Make sure that scope is set properly." ); }; var thisObject = { foo: "bar", method: test }; @@ -872,8 +872,13 @@ test("jQuery.proxy", function(){ // Make sure it doesn't freak out equals( jQuery.proxy( null, thisObject ), undefined, "Make sure no function was returned." ); - // Use the string shortcut - jQuery.proxy( thisObject, "method" )(); + // Partial application + var test2 = function( a ){ equals( a, "pre-applied", "Ensure arguments can be pre-applied." ); }; + jQuery.proxy( test2, null, "pre-applied" )(); + + // Partial application w/ normal arguments + var test3 = function( a, b ){ equals( b, "normal", "Ensure arguments can be pre-applied and passed as usual." ); }; + jQuery.proxy( test3, null, "pre-applied" )( "normal" ); }); test("jQuery.parseJSON", function(){ From 5b1b57850cfd4c92a4f9231581dff7faac489566 Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Wed, 15 Dec 2010 18:31:10 -0500 Subject: [PATCH 02/91] Add a quick test to $.support for native bind. As per the suggestion by ajpiano: https://github.com/gf3/jquery/commit/9f8cd6c499844451468257140e71f611abb3a040#commitcomment-218658 --- src/core.js | 2 +- src/support.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core.js b/src/core.js index 74ec4ea0..a6e2a46f 100644 --- a/src/core.js +++ b/src/core.js @@ -751,7 +751,7 @@ jQuery.extend({ return undefined; } - if ( jQuery.isFunction( Function.prototype.bind ) ) { + if ( jQuery.support.nativeBind ) { // Native bind args = slice.call( arguments, 1 ); proxy = Function.prototype.bind.apply( fn, args ); diff --git a/src/support.js b/src/support.js index 67b41c4a..2913d217 100644 --- a/src/support.js +++ b/src/support.js @@ -60,6 +60,9 @@ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, + // Test for native Function#bind. + nativeBind: jQuery.isFunction( Function.prototype.bind ), + // Will be defined later deleteExpando: true, optDisabled: false, From 1ebb5ab3e1c670e04024cd949fa6f341e93c4487 Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Thu, 16 Dec 2010 16:04:23 -0500 Subject: [PATCH 03/91] Added list of browsers that currently support Function#bind. --- src/support.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/support.js b/src/support.js index 2913d217..1fd1c2a6 100644 --- a/src/support.js +++ b/src/support.js @@ -60,7 +60,8 @@ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, - // Test for native Function#bind. + // Test for presence of native Function#bind. + // Currently in: Chrome 7, FireFox 4 nativeBind: jQuery.isFunction( Function.prototype.bind ), // Will be defined later From 6bc9fc7c10a6dd2c8c7132307e63323a1f59d35f Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Sat, 18 Dec 2010 19:17:37 -0500 Subject: [PATCH 04/91] Perf. improvement based on fearphage's suggestion (direct vs call vs apply). --- src/core.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core.js b/src/core.js index a6e2a46f..2ffcb911 100644 --- a/src/core.js +++ b/src/core.js @@ -754,7 +754,11 @@ jQuery.extend({ if ( jQuery.support.nativeBind ) { // Native bind args = slice.call( arguments, 1 ); - proxy = Function.prototype.bind.apply( fn, args ); + if ( args.length ) { + proxy = Function.prototype.bind.apply( fn, args ); + } else { + proxy = fn.bind( context ); + } } else { // Simulated bind args = slice.call( arguments, 2 ); From ade531cfaa194eb13fa6307368d1e715f3be8326 Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Sat, 18 Dec 2010 19:26:36 -0500 Subject: [PATCH 05/91] Noted which browsers don't support Function#bind. --- src/support.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/support.js b/src/support.js index 1fd1c2a6..55c19d80 100644 --- a/src/support.js +++ b/src/support.js @@ -61,7 +61,7 @@ optSelected: opt.selected, // Test for presence of native Function#bind. - // Currently in: Chrome 7, FireFox 4 + // Not in: >= Chrome 6, >= FireFox 3, Safari 5?, IE 9?, Opera 11? nativeBind: jQuery.isFunction( Function.prototype.bind ), // Will be defined later From 574ae3b1be555dbd5242532739cd8e0a34e0569c Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Fri, 21 Jan 2011 10:33:50 -0500 Subject: [PATCH 06/91] added: Backcompatibility with old proxy syntax. --- src/core.js | 6 ++++++ test/unit/core.js | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core.js b/src/core.js index 3bdedf53..ea3506a6 100644 --- a/src/core.js +++ b/src/core.js @@ -739,6 +739,12 @@ jQuery.extend({ proxy: function( fn, context ) { var args, proxy; + // XXX BACKCOMPAT: Support old string method. + if ( typeof context === "string" ) { + fn = fn[ context ]; + context = arguments[0]; + } + // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( ! jQuery.isFunction( fn ) ) { diff --git a/test/unit/core.js b/test/unit/core.js index 7638554a..332fc51e 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -869,7 +869,7 @@ test("jQuery.isEmptyObject", function(){ }); test("jQuery.proxy", function(){ - expect(5); + expect(6); var test = function(){ equals( this, thisObject, "Make sure that scope is set properly." ); }; var thisObject = { foo: "bar", method: test }; @@ -890,6 +890,10 @@ test("jQuery.proxy", function(){ // Partial application w/ normal arguments var test3 = function( a, b ){ equals( b, "normal", "Ensure arguments can be pre-applied and passed as usual." ); }; jQuery.proxy( test3, null, "pre-applied" )( "normal" ); + + // Test old syntax + var test4 = { meth: function( a ){ equals( a, "boom", "Ensure old syntax works." ); } }; + jQuery.proxy( test4, "meth" )( "boom" ); }); test("jQuery.parseJSON", function(){ From ed48787ec58c12917faf47e3b95e043b2b6ded10 Mon Sep 17 00:00:00 2001 From: Timmy Willison Date: Mon, 24 Jan 2011 16:18:19 -0500 Subject: [PATCH 07/91] Fix bug #2773, jQuery.fn.is to accept JQuery and node objects, and a small fix for winnow getting an undefined selector --- src/traversing.js | 5 ++-- test/unit/traversing.js | 60 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/traversing.js b/src/traversing.js index 90601df5..f8d08fa9 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -60,7 +60,8 @@ jQuery.fn.extend({ }, is: function( selector ) { - return !!selector && jQuery.filter( selector, this ).length > 0; + return !!selector && (typeof selector === "string" ? jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0); }, closest: function( selectors, context ) { @@ -286,7 +287,7 @@ function winnow( elements, qualifier, keep ) { return retVal === keep; }); - } else if ( qualifier.nodeType ) { + } else if ( qualifier && qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { return (elem === qualifier) === keep; }); diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 56fed220..a74ee08b 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -13,8 +13,8 @@ test("find(String)", function() { same( jQuery("#main").find("> #foo > p").get(), q("sndp", "en", "sap"), "find child elements" ); }); -test("is(String)", function() { - expect(26); +test("is(String|undefined)", function() { + expect(27); ok( jQuery('#form').is('form'), 'Check for element: A form must be a form' ); ok( !jQuery('#form').is('div'), 'Check for element: A form is not a div' ); ok( jQuery('#mark').is('.blog'), 'Check for class: Expected class "blog"' ); @@ -33,11 +33,13 @@ test("is(String)", function() { ok( !jQuery('#foo').is(':has(ul)'), 'Check for child: Did not expect "ul" element' ); ok( jQuery('#foo').is(':has(p):has(a):has(code)'), 'Check for childs: Expected "p", "a" and "code" child elements' ); ok( !jQuery('#foo').is(':has(p):has(a):has(code):has(ol)'), 'Check for childs: Expected "p", "a" and "code" child elements, but no "ol"' ); + ok( !jQuery('#foo').is(0), 'Expected false for an invalid expression - 0' ); ok( !jQuery('#foo').is(null), 'Expected false for an invalid expression - null' ); ok( !jQuery('#foo').is(''), 'Expected false for an invalid expression - ""' ); ok( !jQuery('#foo').is(undefined), 'Expected false for an invalid expression - undefined' ); - + ok( !jQuery('#foo').is({ plain: "object" }), 'Check passing invalid object' ); + // test is() with comma-seperated expressions ok( jQuery('#en').is('[lang="en"],[lang="de"]'), 'Comma-seperated; Check for lang attribute: Expect en or de' ); ok( jQuery('#en').is('[lang="de"],[lang="en"]'), 'Comma-seperated; Check for lang attribute: Expect en or de' ); @@ -45,6 +47,49 @@ test("is(String)", function() { ok( jQuery('#en').is('[lang="de"] , [lang="en"]'), 'Comma-seperated; Check for lang attribute: Expect en or de' ); }); +test("is(Object)", function() { + expect(18); + ok( jQuery('#form').is( jQuery('form') ), 'Check for element: A form is a form' ); + ok( !jQuery('#form').is( jQuery('div') ), 'Check for element: A form is not a div' ); + ok( jQuery('#mark').is( jQuery('.blog') ), 'Check for class: Expected class "blog"' ); + ok( !jQuery('#mark').is( jQuery('.link') ), 'Check for class: Did not expect class "link"' ); + ok( jQuery('#simon').is( jQuery('.blog.link') ), 'Check for multiple classes: Expected classes "blog" and "link"' ); + ok( !jQuery('#simon').is( jQuery('.blogTest') ), 'Check for multiple classes: Expected classes "blog" and "link", but not "blogTest"' ); + ok( jQuery('#en').is( jQuery('[lang="en"]') ), 'Check for attribute: Expected attribute lang to be "en"' ); + ok( !jQuery('#en').is( jQuery('[lang="de"]') ), 'Check for attribute: Expected attribute lang to be "en", not "de"' ); + ok( jQuery('#text1').is( jQuery('[type="text"]') ), 'Check for attribute: Expected attribute type to be "text"' ); + ok( !jQuery('#text1').is( jQuery('[type="radio"]') ), 'Check for attribute: Expected attribute type to be "text", not "radio"' ); + ok( jQuery('#text2').is( jQuery(':disabled') ), 'Check for pseudoclass: Expected to be disabled' ); + ok( !jQuery('#text1').is( jQuery(':disabled') ), 'Check for pseudoclass: Expected not disabled' ); + ok( jQuery('#radio2').is( jQuery(':checked') ), 'Check for pseudoclass: Expected to be checked' ); + ok( !jQuery('#radio1').is( jQuery(':checked') ), 'Check for pseudoclass: Expected not checked' ); + ok( jQuery('#foo').is( jQuery(':has(p)') ), 'Check for child: Expected a child "p" element' ); + ok( !jQuery('#foo').is( jQuery(':has(ul)') ), 'Check for child: Did not expect "ul" element' ); + ok( jQuery('#foo').is( jQuery(':has(p):has(a):has(code)') ), 'Check for childs: Expected "p", "a" and "code" child elements' ); + ok( !jQuery('#foo').is( jQuery(':has(p):has(a):has(code):has(ol)') ), 'Check for childs: Expected "p", "a" and "code" child elements, but no "ol"' ); +}); + +test("is(node Object)", function() { + expect(17); + ok( jQuery('#form').is( jQuery('form')[0] ), 'Check for element: A form is a form' ); + ok( !jQuery('#form').is( jQuery('div')[0] ), 'Check for element: A form is not a div' ); + ok( jQuery('#mark').is( jQuery('.blog')[0] ), 'Check for class: Expected class "blog"' ); + ok( !jQuery('#mark').is( jQuery('.link')[0] ), 'Check for class: Did not expect class "link"' ); + ok( jQuery('#simon').is( jQuery('.blog.link')[0] ), 'Check for multiple classes: Expected classes "blog" and "link"' ); + ok( !jQuery('#simon').is( jQuery('.blogTest')[0] ), 'Check for multiple classes: Expected classes "blog" and "link", but not "blogTest"' ); + ok( jQuery('#en').is( jQuery('[lang="en"]')[1] ), 'Check for attribute: Expected attribute lang to be "en"' ); + ok( !jQuery('#en').is( jQuery('[lang="de"]')[0] ), 'Check for attribute: Expected attribute lang to be "en", not "de"' ); + ok( jQuery('#text1').is( jQuery('[type="text"]')[0] ), 'Check for attribute: Expected attribute type to be "text"' ); + ok( !jQuery('#text1').is( jQuery('[type="radio"]')[0] ), 'Check for attribute: Expected attribute type to be "text", not "radio"' ); + ok( jQuery('#text2').is( jQuery(':disabled')[0] ), 'Check for pseudoclass: Expected to be disabled' ); + ok( !jQuery('#text1').is( jQuery(':disabled')[0] ), 'Check for pseudoclass: Expected not disabled' ); + ok( !jQuery('#radio1').is( jQuery(':checked')[0] ), 'Check for pseudoclass: Expected not checked' ); + ok( jQuery('#foo').is( jQuery(':has(p)')[4] ), 'Check for child: Expected a child "p" element' ); + ok( !jQuery('#foo').is( jQuery(':has(ul)')[0] ), 'Check for child: Did not expect "ul" element' ); + ok( jQuery('#foo').is( jQuery(':has(p):has(a):has(code)')[4] ), 'Check for childs: Expected "p", "a" and "code" child elements' ); + ok( !jQuery('#foo').is( jQuery(':has(p):has(a):has(code):has(ol)')[0] ), 'Check for childs: Expected "p", "a" and "code" child elements, but no "ol"' ); +}); + test("index()", function() { expect(1); @@ -82,11 +127,16 @@ test("index(Object|String|undefined)", function() { equals( jQuery('#radio2').index('#form :text') , -1, "Check for index not found within a selector" ); }); -test("filter(Selector)", function() { - expect(5); +test("filter(Selector|undefined)", function() { + expect(9); same( jQuery("#form input").filter(":checked").get(), q("radio2", "check1"), "filter(String)" ); same( jQuery("p").filter("#ap, #sndp").get(), q("ap", "sndp"), "filter('String, String')" ); same( jQuery("p").filter("#ap,#sndp").get(), q("ap", "sndp"), "filter('String,String')" ); + + same( jQuery('p').filter(null).get(), [], "filter(null) should return an empty jQuery object"); + same( jQuery('p').filter(undefined).get(), [], "filter(undefined) should return an empty jQuery object"); + same( jQuery('p').filter(0).get(), [], "filter(0) should return an empty jQuery object"); + same( jQuery('p').filter('').get(), [], "filter('') should return an empty jQuery object"); // using contents will get comments regular, text, and comment nodes var j = jQuery("#nonnodes").contents(); From c8a887af066a62840ec1910604ddb7e0c38728e8 Mon Sep 17 00:00:00 2001 From: Jordan Boesch Date: Sun, 27 Feb 2011 12:47:35 -0600 Subject: [PATCH 08/91] Bug 2616; Adding object support to jQuery.map --- src/core.js | 28 +++++++++++++++++++++------- test/unit/core.js | 12 +++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/core.js b/src/core.js index 036b2db6..3ff12085 100644 --- a/src/core.js +++ b/src/core.js @@ -712,15 +712,29 @@ jQuery.extend({ // arg is for internal usage only map: function( elems, callback, arg ) { - var ret = [], value; + var ret = [], + value, + length = elems.length, + // same object detection used in jQuery.each, not full-proof but very speedy. + isObj = length === undefined; - // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0, length = elems.length; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); + if ( isObj ) { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); - if ( value != null ) { - ret[ ret.length ] = value; + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } else { + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } } } diff --git a/test/unit/core.js b/test/unit/core.js index bce0de0f..d77e6a97 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -608,7 +608,7 @@ test("first()/last()", function() { }); test("map()", function() { - expect(2);//expect(6); + expect(6); same( jQuery("#ap").map(function(){ @@ -625,26 +625,24 @@ test("map()", function() { q("ap","ap","ap"), "Single Map" ); - - return;//these haven't been accepted yet - + //for #2616 var keys = jQuery.map( {a:1,b:2}, function( v, k ){ return k; - }, [ ] ); + }); equals( keys.join(""), "ab", "Map the keys from a hash to an array" ); var values = jQuery.map( {a:1,b:2}, function( v, k ){ return v; - }, [ ] ); + }); equals( values.join(""), "12", "Map the values from a hash to an array" ); var scripts = document.getElementsByTagName("script"); var mapped = jQuery.map( scripts, function( v, k ){ return v; - }, {length:0} ); + }); equals( mapped.length, scripts.length, "Map an array(-like) to a hash" ); From d4e4414451e15d23d7174e8eeddaa952ed0e4d73 Mon Sep 17 00:00:00 2001 From: jeresig Date: Sun, 6 Mar 2011 22:47:40 -0500 Subject: [PATCH 09/91] Very crude first pass at splitting apart the attr/prop logic. Also adding in attrHooks/propHooks. All of it is completely untested. --- src/attributes.js | 274 ++++++++++++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 106 deletions(-) diff --git a/src/attributes.js b/src/attributes.js index 59972105..e5425a05 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -3,38 +3,37 @@ var rclass = /[\n\t\r]/g, rspaces = /\s+/, rreturn = /\r/g, - rspecialurl = /^(?:href|src|style)$/, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rradiocheck = /^(?:radio|checkbox)$/i; -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); }, - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); + removeAttr: function( name ) { + return this.each(function() { if ( this.nodeType === 1 ) { this.removeAttribute( name ); } }); }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, addClass: function( value ) { if ( jQuery.isFunction(value) ) { @@ -275,6 +274,21 @@ jQuery.extend({ height: true, offset: true }, + + // 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 @@ -286,104 +300,152 @@ jQuery.extend({ return jQuery(elem)[name](value); } - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; - + var ret, + notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + hooks; + // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; + name = notxml && jQuery.attrFix[ name ] || name; + + hooks = jQuery.attrHooks[ name ]; + + if ( value !== undefined ) { + + if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value )) !== undefined ) { + return ret; + + } else { + // convert the value to a string (all browsers do this but IE) see #1070 + value = "" + value; + + elem.setAttribute( name, value ); + + return value; + } + + } else { + + if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem )) !== undefined ) { + return ret; + + } else { + + if ( !jQuery.hasAttr( elem, name ) ) { + return undefined; + } - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); + var attr = elem.getAttribute( name ); - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } + // Non-existent attributes return null, we normalize to undefined + 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 )); + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); } } + }, + + // 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: { + get: function( elem ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } - - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } - } - - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; - } - - // 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/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); - - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + }, + + // TODO: Check to see if we really need any here. + propFix: {}, + + prop: function( elem, name, value ) { + var ret, hooks; + + // Try to normalize/fix the name + name = notxml && jQuery.propFix[ name ] || name; + + hooks = jQuery.propHooks[ name ]; + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value )) !== undefined ) { + return ret; + + } else { + return (elem[ name ] = value); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem )) !== undefined ) { + return ret; + + } else { return elem[ name ]; } - - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } - - return elem.style.cssText; - } - - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } - - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; } - // Handle everything which isn't a DOM element node - if ( set ) { - elem[ name ] = value; - } - return elem[ name ]; - } + }, + + propHooks: {} }); +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "style" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + return elem.getAttribute( name, 2 ); + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + return elem.style.cssText; + }, + + set: function( elem, value ) { + return (elem.style.cssText = "" + value); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.attrHooks.selected = { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + })( jQuery ); From 8246347b7191e79ed09bf1fc4c4a0a58211cf345 Mon Sep 17 00:00:00 2001 From: timmywil Date: Sun, 13 Mar 2011 21:12:10 -0400 Subject: [PATCH 10/91] Starting with adding the test --- test/unit/traversing.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/unit/traversing.js b/test/unit/traversing.js index f5108bde..9216bb5e 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -13,6 +13,15 @@ test("find(String)", function() { same( jQuery("#main").find("> #foo > p").get(), q("sndp", "en", "sap"), "find child elements" ); }); +test("find(node|jQuery object)", function() { + expect( 2 ); + + var $blog = jQuery('.blogTest'), + blog = $blog[0]; + equals( jQuery('#foo').find( $blog ).text(), 'Yahoo', 'Find with blog jQuery object' ); + equals( jQuery('#foo').find( blog ).text(), 'Yahoo', 'Find with blog node' ); +}); + test("is(String)", function() { expect(26); ok( jQuery('#form').is('form'), 'Check for element: A form must be a form' ); From 7a69e34a5cd4df37762cbee9c9468c96c0ac3017 Mon Sep 17 00:00:00 2001 From: timmywil Date: Wed, 16 Mar 2011 01:16:32 -0400 Subject: [PATCH 11/91] 2773: first pass adding node/jQuery object support to jQuery.fn.find; unit tests added --- src/core.js | 2 +- src/traversing.js | 23 ++++++++++++++++++----- test/unit/traversing.js | 25 ++++++++++++++++++++----- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/core.js b/src/core.js index 9312ee28..812ecde5 100644 --- a/src/core.js +++ b/src/core.js @@ -88,7 +88,7 @@ jQuery.fn = jQuery.prototype = { if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; - this.selector = "body"; + this.selector = selector; this.length = 1; return this; } diff --git a/src/traversing.js b/src/traversing.js index fe2e33d8..fa6aae2f 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -17,17 +17,30 @@ var runtil = /Until$/, jQuery.fn.extend({ find: function( selector ) { - var ret = this.pushStack( "", "find", selector ), - length = 0; + var self = this, + ret, i, l; - for ( var i = 0, l = this.length; i < l; i++ ) { + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + ret = this.pushStack( "", "find", selector ); + + var length, n, r; + for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique - for ( var n = length; n < ret.length; n++ ) { - for ( var r = 0; r < length; r++ ) { + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 9216bb5e..76b93d84 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -14,12 +14,27 @@ test("find(String)", function() { }); test("find(node|jQuery object)", function() { - expect( 2 ); + expect( 11 ); + + var $foo = jQuery('#foo'), + $blog = jQuery('.blogTest'), + $first = jQuery('#first'), + $two = $blog.add( $first ), + $fooTwo = $foo.add( $blog ); + + equals( $foo.find( $blog ).text(), 'Yahoo', 'Find with blog jQuery object' ); + equals( $foo.find( $blog[0] ).text(), 'Yahoo', 'Find with blog node' ); + equals( $foo.find( $first ).length, 0, '#first is not in #foo' ); + equals( $foo.find( $first[0]).length, 0, '#first not in #foo (node)' ); + ok( $foo.find( $two ).is('.blogTest'), 'Find returns only nodes within #foo' ); + ok( $fooTwo.find( $blog ).is('.blogTest'), 'Blog is part of the collection, but also within foo' ); + ok( $fooTwo.find( $blog[0] ).is('.blogTest'), 'Blog is part of the collection, but also within foo(node)' ); + + equals( $two.find( $foo ).length, 0, 'Foo is not in two elements' ); + equals( $two.find( $foo[0] ).length, 0, 'Foo is not in two elements(node)' ); + equals( $two.find( $first ).length, 0, 'first is in the collection and not within two' ); + equals( $two.find( $first ).length, 0, 'first is in the collection and not within two(node)' ); - var $blog = jQuery('.blogTest'), - blog = $blog[0]; - equals( jQuery('#foo').find( $blog ).text(), 'Yahoo', 'Find with blog jQuery object' ); - equals( jQuery('#foo').find( blog ).text(), 'Yahoo', 'Find with blog node' ); }); test("is(String)", function() { From 929792834f77e075e4b5397fb4b25b1a2dcbd49a Mon Sep 17 00:00:00 2001 From: timmywil Date: Wed, 16 Mar 2011 14:41:26 -0400 Subject: [PATCH 12/91] Organizing vars --- src/traversing.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/traversing.js b/src/traversing.js index fa6aae2f..30d60435 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -18,7 +18,7 @@ var runtil = /Until$/, jQuery.fn.extend({ find: function( selector ) { var self = this, - ret, i, l; + i, l; if ( typeof selector !== "string" ) { return jQuery( selector ).filter(function() { @@ -30,9 +30,8 @@ jQuery.fn.extend({ }); } - ret = this.pushStack( "", "find", selector ); - - var length, n, r; + var ret = this.pushStack( "", "find", selector ), + length, n, r; for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); From 2407690ef9b3ec59920917935dc1a312f6c2b56e Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Mon, 21 Mar 2011 08:10:27 -0700 Subject: [PATCH 13/91] Fixes 2616; Pull in #252 by jboesch: jQuery.map with object support --- test/qunit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/qunit b/test/qunit index d404faf8..cc8460c7 160000 --- a/test/qunit +++ b/test/qunit @@ -1 +1 @@ -Subproject commit d404faf8f587fcbe6b8907943022e6318dd51e0c +Subproject commit cc8460c7b44f023c4f84ab1810b72bf6c6ee4542 From e38f074d14fd65b3f8b0e1bd7956cd75b3dafe2b Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Mon, 21 Mar 2011 08:39:53 -0700 Subject: [PATCH 14/91] jQuery.map to conform with style guidelines - improved size/DRY code --- src/core.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/core.js b/src/core.js index 1077d38c..a0dd7b5b 100644 --- a/src/core.js +++ b/src/core.js @@ -706,29 +706,31 @@ jQuery.extend({ // arg is for internal usage only map: function( elems, callback, arg ) { - var ret = [], - value, + var ret = [], value, i = 0, length = elems.length, // same object detection used in jQuery.each, not full-proof but very speedy. isObj = length === undefined; - - if ( isObj ) { - for ( key in elems ) { - value = callback( elems[ key ], key, arg ); - + + // the work for the loops - run elems[x] through callback + inLoop = function( key ) { + value = callback( elems[ key ], key, arg ); + if ( value != null ) { ret[ ret.length ] = value; } } - } else { - // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - if ( value != null ) { - ret[ ret.length ] = value; - } + // Go thorugh every key on the object + if ( isObj ) { + for ( key in elems ) { + inLoop( key ); + } + + // Go through the array, translating each of the items to their + // new value (or values). + } else { + for ( ; i < length; i++ ) { + inLoop( i ); } } From d832f4f71ec51e67d4cae2557221ef582818f607 Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Mon, 21 Mar 2011 12:12:31 -0700 Subject: [PATCH 15/91] jQuery.map to iterate over objects with a .length property --- src/core.js | 40 ++++++++++++++++++++-------------------- test/unit/core.js | 12 +++++++----- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/core.js b/src/core.js index a0dd7b5b..951f1b55 100644 --- a/src/core.js +++ b/src/core.js @@ -704,33 +704,33 @@ jQuery.extend({ return ret; }, - // arg is for internal usage only + // arg is for internal usage only map: function( elems, callback, arg ) { - var ret = [], value, i = 0, - length = elems.length, - // same object detection used in jQuery.each, not full-proof but very speedy. - isObj = length === undefined; - - // the work for the loops - run elems[x] through callback - inLoop = function( key ) { - value = callback( elems[ key ], key, arg ); - + var value, ret = [], + i = 0, + length = elems.length, + // process .length if it's just an object member + isArray = length !== undefined && ( elems[ length - 1 ] || jQuery.isArray( elems ) ); + + // Go through the array, translating each of the items to their + // new value (or values). + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + if ( value != null ) { ret[ ret.length ] = value; } } - // Go thorugh every key on the object - if ( isObj ) { + // Go thorugh every key on the object, + } else { for ( key in elems ) { - inLoop( key ); - } - - // Go through the array, translating each of the items to their - // new value (or values). - } else { - for ( ; i < length; i++ ) { - inLoop( i ); + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } } } diff --git a/test/unit/core.js b/test/unit/core.js index c1ffe10b..08d80400 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -608,7 +608,7 @@ test("first()/last()", function() { }); test("map()", function() { - expect(6); + expect(7); same( jQuery("#ap").map(function(){ @@ -630,26 +630,28 @@ test("map()", function() { var keys = jQuery.map( {a:1,b:2}, function( v, k ){ return k; }); - equals( keys.join(""), "ab", "Map the keys from a hash to an array" ); var values = jQuery.map( {a:1,b:2}, function( v, k ){ return v; }); - equals( values.join(""), "12", "Map the values from a hash to an array" ); + + // object with length prop + var values = jQuery.map( {a:1,b:2, length:3}, function( v, k ){ + return v; + }); + equals( values.join(""), "123", "Map the values from a hash with a length property to an array" ); var scripts = document.getElementsByTagName("script"); var mapped = jQuery.map( scripts, function( v, k ){ return v; }); - equals( mapped.length, scripts.length, "Map an array(-like) to a hash" ); var flat = jQuery.map( Array(4), function( v, k ){ return k % 2 ? k : [k,k,k];//try mixing array and regular returns }); - equals( flat.join(""), "00012223", "try the new flatten technique(#2616)" ); }); From 00dd6013b6b53455ef7b788801a5dc0616651580 Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Mon, 21 Mar 2011 12:24:53 -0700 Subject: [PATCH 16/91] Clean up tab spacing --- src/core.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core.js b/src/core.js index 951f1b55..49c191b9 100644 --- a/src/core.js +++ b/src/core.js @@ -707,10 +707,10 @@ jQuery.extend({ // arg is for internal usage only map: function( elems, callback, arg ) { var value, ret = [], - i = 0, - length = elems.length, - // process .length if it's just an object member - isArray = length !== undefined && ( elems[ length - 1 ] || jQuery.isArray( elems ) ); + i = 0, + length = elems.length, + // process .length if it's just an object member + isArray = length !== undefined && ( elems[ length - 1 ] || jQuery.isArray( elems ) ); // Go through the array, translating each of the items to their // new value (or values). From e09d8898d8a8df27bb72ebc64a4cd08c11f21ddd Mon Sep 17 00:00:00 2001 From: timmywil Date: Mon, 21 Mar 2011 20:59:20 -0400 Subject: [PATCH 17/91] Add node and jQuery object support to $.fn.closest --- src/traversing.js | 20 ++++++++++++-------- test/unit/traversing.js | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/traversing.js b/src/traversing.js index 30d60435..49197c1f 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -32,6 +32,7 @@ jQuery.fn.extend({ var ret = this.pushStack( "", "find", selector ), length, n, r; + for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); @@ -77,7 +78,8 @@ jQuery.fn.extend({ closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; - + + // Array if ( jQuery.isArray( selectors ) ) { var match, selector, matches = {}, @@ -87,8 +89,8 @@ jQuery.fn.extend({ for ( i = 0, l = selectors.length; i < l; i++ ) { selector = selectors[i]; - if ( !matches[selector] ) { - matches[selector] = jQuery.expr.match.POS.test( selector ) ? + if ( !matches[ selector ] ) { + matches[ selector ] = POS.test( selector ) ? jQuery( selector, context || this.context ) : selector; } @@ -96,9 +98,9 @@ jQuery.fn.extend({ while ( cur && cur.ownerDocument && cur !== context ) { for ( selector in matches ) { - match = matches[selector]; + match = matches[ selector ]; - if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { ret.push({ selector: selector, elem: cur, level: level }); } } @@ -110,9 +112,11 @@ jQuery.fn.extend({ return ret; } - - var pos = POS.test( selectors ) ? - jQuery( selectors, context || this.context ) : null; + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; for ( i = 0, l = this.length; i < l; i++ ) { cur = this[i]; diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 76b93d84..bd05f470 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -182,6 +182,20 @@ test("closest(Array)", function() { same( jQuery("body").closest(["span","html"]), [{selector:"html", elem:document.documentElement, level:2}], "closest([body, html])" ); }); +test("closest(jQuery)", function() { + expect(7); + var $child = jQuery("#nothiddendivchild"), + $parent = jQuery("#nothiddendiv"), + $main = jQuery("#main"); + ok( $child.closest( $parent ).is('#nothiddendiv'), "closest( jQuery('#nothiddendiv') )" ); + ok( $child.closest( $parent[0] ).is('#nothiddendiv'), "closest( jQuery('#nothiddendiv') ) :: node" ); + ok( $child.closest( $child ).is('#nothiddendivchild'), "child is included" ); + ok( $child.closest( $child[0] ).is('#nothiddendivchild'), "child is included :: node" ); + equals( $child.closest( document.createElement('div') ).length, 0, "created element is not related" ); + equals( $child.closest( $main ).length, 0, "Main not a parent of child" ); + equals( $child.closest( $main[0] ).length, 0, "Main not a parent of child :: node" ); +}); + test("not(Selector)", function() { expect(7); equals( jQuery("#main > p#ap > a").not("#google").length, 2, "not('selector')" ); From b8013581ced78fb6c2005e76b44211e01fc2e466 Mon Sep 17 00:00:00 2001 From: timmywil Date: Wed, 23 Mar 2011 15:56:05 -0400 Subject: [PATCH 18/91] Closest unit tests: add one for passing a jQuery collection with multiple elements --- test/unit/traversing.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/traversing.js b/test/unit/traversing.js index bd05f470..adca5f40 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -183,10 +183,11 @@ test("closest(Array)", function() { }); test("closest(jQuery)", function() { - expect(7); + expect(8); var $child = jQuery("#nothiddendivchild"), $parent = jQuery("#nothiddendiv"), - $main = jQuery("#main"); + $main = jQuery("#main"), + $body = jQuery("body"); ok( $child.closest( $parent ).is('#nothiddendiv'), "closest( jQuery('#nothiddendiv') )" ); ok( $child.closest( $parent[0] ).is('#nothiddendiv'), "closest( jQuery('#nothiddendiv') ) :: node" ); ok( $child.closest( $child ).is('#nothiddendivchild'), "child is included" ); @@ -194,6 +195,7 @@ test("closest(jQuery)", function() { equals( $child.closest( document.createElement('div') ).length, 0, "created element is not related" ); equals( $child.closest( $main ).length, 0, "Main not a parent of child" ); equals( $child.closest( $main[0] ).length, 0, "Main not a parent of child :: node" ); + ok( $child.closest( $body.add($parent) ).is('#nothiddendiv'), "Closest ancestor retrieved." ); }); test("not(Selector)", function() { From 85232c97bf1f129b2bc246cf674dc3d5582aaecb Mon Sep 17 00:00:00 2001 From: timmywil Date: Wed, 23 Mar 2011 16:04:12 -0400 Subject: [PATCH 19/91] Traversing unit tests: added tests for passing invalid arguments to $.fn.not (should have no effect on existing object rather than return an empty object as filter does) --- test/unit/traversing.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 9eb0d78b..7d15f4c1 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -47,8 +47,8 @@ test("is(String|undefined)", function() { ok( jQuery('#en').is('[lang="de"] , [lang="en"]'), 'Comma-seperated; Check for lang attribute: Expect en or de' ); }); -test("is(Object)", function() { - expect(18); +test("is(jQuery)", function() { + expect(24); ok( jQuery('#form').is( jQuery('form') ), 'Check for element: A form is a form' ); ok( !jQuery('#form').is( jQuery('div') ), 'Check for element: A form is not a div' ); ok( jQuery('#mark').is( jQuery('.blog') ), 'Check for class: Expected class "blog"' ); @@ -67,27 +67,14 @@ test("is(Object)", function() { ok( !jQuery('#foo').is( jQuery(':has(ul)') ), 'Check for child: Did not expect "ul" element' ); ok( jQuery('#foo').is( jQuery(':has(p):has(a):has(code)') ), 'Check for childs: Expected "p", "a" and "code" child elements' ); ok( !jQuery('#foo').is( jQuery(':has(p):has(a):has(code):has(ol)') ), 'Check for childs: Expected "p", "a" and "code" child elements, but no "ol"' ); -}); - -test("is(node Object)", function() { - expect(17); + + // Some raw elements ok( jQuery('#form').is( jQuery('form')[0] ), 'Check for element: A form is a form' ); ok( !jQuery('#form').is( jQuery('div')[0] ), 'Check for element: A form is not a div' ); ok( jQuery('#mark').is( jQuery('.blog')[0] ), 'Check for class: Expected class "blog"' ); ok( !jQuery('#mark').is( jQuery('.link')[0] ), 'Check for class: Did not expect class "link"' ); ok( jQuery('#simon').is( jQuery('.blog.link')[0] ), 'Check for multiple classes: Expected classes "blog" and "link"' ); ok( !jQuery('#simon').is( jQuery('.blogTest')[0] ), 'Check for multiple classes: Expected classes "blog" and "link", but not "blogTest"' ); - ok( jQuery('#en').is( jQuery('[lang="en"]')[1] ), 'Check for attribute: Expected attribute lang to be "en"' ); - ok( !jQuery('#en').is( jQuery('[lang="de"]')[0] ), 'Check for attribute: Expected attribute lang to be "en", not "de"' ); - ok( jQuery('#text1').is( jQuery('[type="text"]')[0] ), 'Check for attribute: Expected attribute type to be "text"' ); - ok( !jQuery('#text1').is( jQuery('[type="radio"]')[0] ), 'Check for attribute: Expected attribute type to be "text", not "radio"' ); - ok( jQuery('#text2').is( jQuery(':disabled')[0] ), 'Check for pseudoclass: Expected to be disabled' ); - ok( !jQuery('#text1').is( jQuery(':disabled')[0] ), 'Check for pseudoclass: Expected not disabled' ); - ok( !jQuery('#radio1').is( jQuery(':checked')[0] ), 'Check for pseudoclass: Expected not checked' ); - ok( jQuery('#foo').is( jQuery(':has(p)')[4] ), 'Check for child: Expected a child "p" element' ); - ok( !jQuery('#foo').is( jQuery(':has(ul)')[0] ), 'Check for child: Did not expect "ul" element' ); - ok( jQuery('#foo').is( jQuery(':has(p):has(a):has(code)')[4] ), 'Check for childs: Expected "p", "a" and "code" child elements' ); - ok( !jQuery('#foo').is( jQuery(':has(p):has(a):has(code):has(ol)')[0] ), 'Check for childs: Expected "p", "a" and "code" child elements, but no "ol"' ); }); test("index()", function() { @@ -208,8 +195,8 @@ test("closest(Array)", function() { same( jQuery("body").closest(["span","html"]), [{selector:"html", elem:document.documentElement, level:2}], "closest([body, html])" ); }); -test("not(Selector)", function() { - expect(7); +test("not(Selector|undefined)", function() { + expect(11); equals( jQuery("#main > p#ap > a").not("#google").length, 2, "not('selector')" ); same( jQuery("p").not(".result").get(), q("firstp", "ap", "sndp", "en", "sap", "first"), "not('.class')" ); same( jQuery("p").not("#ap, #sndp, .result").get(), q("firstp", "en", "sap", "first"), "not('selector, selector')" ); @@ -218,6 +205,12 @@ test("not(Selector)", function() { same( jQuery('#ap *').not('code').get(), q("google", "groups", "anchor1", "mark"), "not('tag selector')" ); same( jQuery('#ap *').not('code, #mark').get(), q("google", "groups", "anchor1"), "not('tag, ID selector')" ); same( jQuery('#ap *').not('#mark, code').get(), q("google", "groups", "anchor1"), "not('ID, tag selector')"); + + var all = jQuery('p').get(); + same( jQuery('p').not(null).get(), all, "not(null) should have no effect"); + same( jQuery('p').not(undefined).get(), all, "not(undefined) should have no effect"); + same( jQuery('p').not(0).get(), all, "not(0) should have no effect"); + same( jQuery('p').not('').get(), all, "not('') should have no effect"); }); test("not(Element)", function() { From e6da0fa6a96c9b4314303e251fd6efac98813ab8 Mon Sep 17 00:00:00 2001 From: timmywil Date: Fri, 25 Mar 2011 23:46:29 -0400 Subject: [PATCH 20/91] Bug #7369: Add test for disconnected node in closest when passing attribute selector; this was recently fixed in 1.5.2rc --- test/unit/traversing.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/traversing.js b/test/unit/traversing.js index adca5f40..f5cd8aac 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -148,7 +148,7 @@ test("filter(jQuery)", function() { }) test("closest()", function() { - expect(11); + expect(12); same( jQuery("body").closest("body").get(), q("body"), "closest(body)" ); same( jQuery("body").closest("html").get(), q("html"), "closest(html)" ); same( jQuery("body").closest("div").get(), [], "closest(div)" ); @@ -168,6 +168,8 @@ test("closest()", function() { // Test on disconnected node equals( jQuery("

").find("p").closest("table").length, 0, "Make sure disconnected closest work." ); + // Bug #7369 + equals( jQuery('
').closest('[foo]').length, 1, "Disconnected nodes with attribute selector" ); }); test("closest(Array)", function() { From e93ca40aa7ec4337a57fcdbc699d900e01b4c67e Mon Sep 17 00:00:00 2001 From: timmywil Date: Fri, 25 Mar 2011 23:52:36 -0400 Subject: [PATCH 21/91] Bug #7369: Check non-existent attribute as well to be sure --- test/unit/traversing.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/traversing.js b/test/unit/traversing.js index f5cd8aac..43a9f22a 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -148,7 +148,7 @@ test("filter(jQuery)", function() { }) test("closest()", function() { - expect(12); + expect(13); same( jQuery("body").closest("body").get(), q("body"), "closest(body)" ); same( jQuery("body").closest("html").get(), q("html"), "closest(html)" ); same( jQuery("body").closest("div").get(), [], "closest(div)" ); @@ -170,6 +170,7 @@ test("closest()", function() { equals( jQuery("

").find("p").closest("table").length, 0, "Make sure disconnected closest work." ); // Bug #7369 equals( jQuery('
').closest('[foo]').length, 1, "Disconnected nodes with attribute selector" ); + equals( jQuery('
').closest('[lang]').length, 0, "Disconnected nodes with non-existent attribute selector" ); }); test("closest(Array)", function() { From 1a167767305202797cf4c839eb64bd7adfb00182 Mon Sep 17 00:00:00 2001 From: timmywil Date: Wed, 30 Mar 2011 23:23:38 -0400 Subject: [PATCH 22/91] Remove test for bug #7369 to move the fix to a separate branch for a sooner pull --- src/traversing.js | 4 ++-- test/unit/traversing.js | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/traversing.js b/src/traversing.js index 49197c1f..91bc017e 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -112,7 +112,7 @@ jQuery.fn.extend({ return ret; } - + // String var pos = POS.test( selectors ) || typeof selectors !== "string" ? jQuery( selectors, context || this.context ) : @@ -135,7 +135,7 @@ jQuery.fn.extend({ } } - ret = ret.length > 1 ? jQuery.unique(ret) : ret; + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; return this.pushStack( ret, "closest", selectors ); }, diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 43a9f22a..117e4600 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -168,9 +168,6 @@ test("closest()", function() { // Test on disconnected node equals( jQuery("

").find("p").closest("table").length, 0, "Make sure disconnected closest work." ); - // Bug #7369 - equals( jQuery('
').closest('[foo]').length, 1, "Disconnected nodes with attribute selector" ); - equals( jQuery('
').closest('[lang]').length, 0, "Disconnected nodes with non-existent attribute selector" ); }); test("closest(Array)", function() { From a807451a23577ad04140a32f7888e6c7b26a8838 Mon Sep 17 00:00:00 2001 From: timmywil Date: Wed, 30 Mar 2011 23:39:19 -0400 Subject: [PATCH 23/91] Fixes #7369 - Using an attribute selector for a non-existent attribute raised an exception on disconnected nodes --- src/traversing.js | 2 +- test/unit/traversing.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/traversing.js b/src/traversing.js index fe2e33d8..468aa097 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -112,7 +112,7 @@ jQuery.fn.extend({ } else { cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context ) { + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { break; } } diff --git a/test/unit/traversing.js b/test/unit/traversing.js index f5108bde..4cccd650 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -124,7 +124,7 @@ test("filter(jQuery)", function() { }) test("closest()", function() { - expect(11); + expect(13); same( jQuery("body").closest("body").get(), q("body"), "closest(body)" ); same( jQuery("body").closest("html").get(), q("html"), "closest(html)" ); same( jQuery("body").closest("div").get(), [], "closest(div)" ); @@ -144,6 +144,10 @@ test("closest()", function() { // Test on disconnected node equals( jQuery("

").find("p").closest("table").length, 0, "Make sure disconnected closest work." ); + + // Bug #7369 + equals( jQuery('
').closest('[foo]').length, 1, "Disconnected nodes with attribute selector" ); + equals( jQuery('
text
').closest('[lang]').length, 0, "Disconnected nodes with text and non-existent attribute selector" ); }); test("closest(Array)", function() { From 64a0005a3b5a168a421efa1d8b253de234620229 Mon Sep 17 00:00:00 2001 From: timmywil Date: Sat, 2 Apr 2011 17:05:04 -0400 Subject: [PATCH 24/91] A more modest valHooks proposal - The main difference is that this does not allow arbitrarily adding hooks to any collection of elements. - Modularizes val into a set of easily maintainable and conditional hooks. - valHooks is placed at jQuery.valHooks + This could technically be extended, but I do not see it being used except in very rare cases since you can only apply valHooks to nodeNames and input types, and not a collection of elements as before. We could theoretically privatize valHooks taking it off of jQuery and only use it internally for our own convenience, but again, I do not believe this patch carries with it the dangers of the first proposal. - Slightly improved performance of val on radios and checkboxes for browsers that support checkOn, given the conditional attachment of its hook. --- src/attributes.js | 178 +++++++++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 75 deletions(-) diff --git a/src/attributes.js b/src/attributes.js index 59972105..4c59a675 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -154,82 +154,35 @@ jQuery.fn.extend({ }, val: function( value ) { + var hooks, val, + elem = this[0]; + if ( !arguments.length ) { - var elem = this[0]; - if ( elem ) { - if ( jQuery.nodeName( elem, "option" ) ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (val = hooks.get( elem )) !== undefined ) { + return val; } - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { - - // Get the specific value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if ( one && !values.length && options.length ) { - return jQuery( options[ index ] ).val(); - } - - return values; - } - - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { - return elem.getAttribute("value") === null ? "on" : elem.value; - } - - // Everything else, we just grab the value return (elem.value || "").replace(rreturn, ""); - } return undefined; } - var isFunction = jQuery.isFunction(value); + var isFunction = jQuery.isFunction( value ); - return this.each(function(i) { - var self = jQuery(this), val = value; + return this.each(function( i ) { + var self = jQuery(this); if ( this.nodeType !== 1 ) { return; } + val = value; if ( isFunction ) { - val = value.call(this, i, self.val()); + val = value.call( this, i, self.val() ); } // Treat null/undefined as ""; convert numbers to string @@ -237,27 +190,16 @@ jQuery.fn.extend({ val = ""; } else if ( typeof val === "number" ) { val += ""; - } else if ( jQuery.isArray(val) ) { - val = jQuery.map(val, function (value) { + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } - if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { - this.checked = jQuery.inArray( self.val(), val ) >= 0; + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; - } else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(val); - - jQuery( "option", this ).each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - this.selectedIndex = -1; - } - - } else { + // If set returns undefined, fall back to normal setting + if ( !hooks || ("set" in hooks && hooks.set( this, val ) === undefined) ) { this.value = val; } }); @@ -265,6 +207,71 @@ jQuery.fn.extend({ }); jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + attrFn: { val: true, css: true, @@ -386,4 +393,25 @@ jQuery.extend({ } }); +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); + } + } + }); +}); + })( jQuery ); From ab4e300919c5b3335e6d6c99ce9b1c314d116f19 Mon Sep 17 00:00:00 2001 From: jeresig Date: Sun, 6 Mar 2011 22:47:40 -0500 Subject: [PATCH 25/91] Very crude first pass at splitting apart the attr/prop logic. Also adding in attrHooks/propHooks. All of it is completely untested. --- src/attributes.js | 274 ++++++++++++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 106 deletions(-) diff --git a/src/attributes.js b/src/attributes.js index 59972105..e5425a05 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -3,38 +3,37 @@ var rclass = /[\n\t\r]/g, rspaces = /\s+/, rreturn = /\r/g, - rspecialurl = /^(?:href|src|style)$/, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rradiocheck = /^(?:radio|checkbox)$/i; -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); }, - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); + removeAttr: function( name ) { + return this.each(function() { if ( this.nodeType === 1 ) { this.removeAttribute( name ); } }); }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, addClass: function( value ) { if ( jQuery.isFunction(value) ) { @@ -275,6 +274,21 @@ jQuery.extend({ height: true, offset: true }, + + // 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 @@ -286,104 +300,152 @@ jQuery.extend({ return jQuery(elem)[name](value); } - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; - + var ret, + notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + hooks; + // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; + name = notxml && jQuery.attrFix[ name ] || name; + + hooks = jQuery.attrHooks[ name ]; + + if ( value !== undefined ) { + + if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value )) !== undefined ) { + return ret; + + } else { + // convert the value to a string (all browsers do this but IE) see #1070 + value = "" + value; + + elem.setAttribute( name, value ); + + return value; + } + + } else { + + if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem )) !== undefined ) { + return ret; + + } else { + + if ( !jQuery.hasAttr( elem, name ) ) { + return undefined; + } - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); + var attr = elem.getAttribute( name ); - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } + // Non-existent attributes return null, we normalize to undefined + 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 )); + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); } } + }, + + // 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: { + get: function( elem ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } - - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } - } - - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; - } - - // 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/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); - - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + }, + + // TODO: Check to see if we really need any here. + propFix: {}, + + prop: function( elem, name, value ) { + var ret, hooks; + + // Try to normalize/fix the name + name = notxml && jQuery.propFix[ name ] || name; + + hooks = jQuery.propHooks[ name ]; + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value )) !== undefined ) { + return ret; + + } else { + return (elem[ name ] = value); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem )) !== undefined ) { + return ret; + + } else { return elem[ name ]; } - - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } - - return elem.style.cssText; - } - - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } - - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; } - // Handle everything which isn't a DOM element node - if ( set ) { - elem[ name ] = value; - } - return elem[ name ]; - } + }, + + propHooks: {} }); +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "style" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + return elem.getAttribute( name, 2 ); + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + return elem.style.cssText; + }, + + set: function( elem, value ) { + return (elem.style.cssText = "" + value); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.attrHooks.selected = { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + })( jQuery ); From de79e8c7e0d1d2243447d59c70a9058eabf56bc3 Mon Sep 17 00:00:00 2001 From: timmywil Date: Tue, 8 Mar 2011 12:07:05 -0500 Subject: [PATCH 26/91] Make the new attr/prop changes pass the test suite (in Webkit). There are still errors in IE. + Added hooks for selected, checked, readonly, disabled to removeAttr if set to falsey + Removed all attrs from attrFix, these aren't needed for setAttribute + If prop is used for class, do we want a propFix for class? - We could just assume the user should know to use className with prop. I've done the latter for now. + Created tests for $.fn.prop and $.fn.removeProp - Actually all I did was change broken attr tests to prop where it made sense. --- src/attributes.js | 48 ++++++----- src/manipulation.js | 2 +- test/unit/attributes.js | 177 ++++++++++++++++++++------------------ test/unit/manipulation.js | 6 +- 4 files changed, 124 insertions(+), 109 deletions(-) diff --git a/src/attributes.js b/src/attributes.js index e5425a05..cb8128d2 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 27f81cc2..81a55cb8 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 8cf47bed..d4284556 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("