Landing pull request 511. Adding a little Makefile jQuery sizing utility to easily...
[jquery.git] / src / attributes.js
blobdd985b4940538a18152eb75efac13ca4874474a7
1 (function( jQuery ) {
3 var rclass = /[\n\t\r]/g,
4         rspace = /\s+/,
5         rreturn = /\r/g,
6         rtype = /^(?:button|input)$/i,
7         rfocusable = /^(?:button|input|object|select|textarea)$/i,
8         rclickable = /^a(?:rea)?$/i,
9         rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
10         nodeHook, boolHook;
12 jQuery.fn.extend({
13         attr: function( name, value ) {
14                 return jQuery.access( this, name, value, true, jQuery.attr );
15         },
17         removeAttr: function( name ) {
18                 return this.each(function() {
19                         jQuery.removeAttr( this, name );
20                 });
21         },
22         
23         prop: function( name, value ) {
24                 return jQuery.access( this, name, value, true, jQuery.prop );
25         },
26         
27         removeProp: function( name ) {
28                 name = jQuery.propFix[ name ] || name;
29                 return this.each(function() {
30                         // try/catch handles cases where IE balks (such as removing a property on window)
31                         try {
32                                 this[ name ] = undefined;
33                                 delete this[ name ];
34                         } catch( e ) {}
35                 });
36         },
38         addClass: function( value ) {
39                 var classNames, i, l, elem,
40                         setClass, c, cl;
42                 if ( jQuery.isFunction( value ) ) {
43                         return this.each(function( j ) {
44                                 jQuery( this ).addClass( value.call(this, j, this.className) );
45                         });
46                 }
48                 if ( value && typeof value === "string" ) {
49                         classNames = value.split( rspace );
51                         for ( i = 0, l = this.length; i < l; i++ ) {
52                                 elem = this[ i ];
54                                 if ( elem.nodeType === 1 ) {
55                                         if ( !elem.className && classNames.length === 1 ) {
56                                                 elem.className = value;
58                                         } else {
59                                                 setClass = " " + elem.className + " ";
61                                                 for ( c = 0, cl = classNames.length; c < cl; c++ ) {
62                                                         if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
63                                                                 setClass += classNames[ c ] + " ";
64                                                         }
65                                                 }
66                                                 elem.className = jQuery.trim( setClass );
67                                         }
68                                 }
69                         }
70                 }
72                 return this;
73         },
75         removeClass: function( value ) {
76                 var classNames, i, l, elem, className, c, cl;
78                 if ( jQuery.isFunction( value ) ) {
79                         return this.each(function( j ) {
80                                 jQuery( this ).removeClass( value.call(this, j, this.className) );
81                         });
82                 }
84                 if ( (value && typeof value === "string") || value === undefined ) {
85                         classNames = (value || "").split( rspace );
87                         for ( i = 0, l = this.length; i < l; i++ ) {
88                                 elem = this[ i ];
90                                 if ( elem.nodeType === 1 && elem.className ) {
91                                         if ( value ) {
92                                                 className = (" " + elem.className + " ").replace( rclass, " " );
93                                                 for ( c = 0, cl = classNames.length; c < cl; c++ ) {
94                                                         className = className.replace(" " + classNames[ c ] + " ", " ");
95                                                 }
96                                                 elem.className = jQuery.trim( className );
98                                         } else {
99                                                 elem.className = "";
100                                         }
101                                 }
102                         }
103                 }
105                 return this;
106         },
108         toggleClass: function( value, stateVal ) {
109                 var type = typeof value,
110                         isBool = typeof stateVal === "boolean";
112                 if ( jQuery.isFunction( value ) ) {
113                         return this.each(function( i ) {
114                                 jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
115                         });
116                 }
118                 return this.each(function() {
119                         if ( type === "string" ) {
120                                 // toggle individual class names
121                                 var className,
122                                         i = 0,
123                                         self = jQuery( this ),
124                                         state = stateVal,
125                                         classNames = value.split( rspace );
127                                 while ( (className = classNames[ i++ ]) ) {
128                                         // check each className given, space seperated list
129                                         state = isBool ? state : !self.hasClass( className );
130                                         self[ state ? "addClass" : "removeClass" ]( className );
131                                 }
133                         } else if ( type === "undefined" || type === "boolean" ) {
134                                 if ( this.className ) {
135                                         // store className if set
136                                         jQuery._data( this, "__className__", this.className );
137                                 }
139                                 // toggle whole className
140                                 this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
141                         }
142                 });
143         },
145         hasClass: function( selector ) {
146                 var className = " " + selector + " ";
147                 for ( var i = 0, l = this.length; i < l; i++ ) {
148                         if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
149                                 return true;
150                         }
151                 }
153                 return false;
154         },
156         val: function( value ) {
157                 var hooks, ret,
158                         elem = this[0];
159                 
160                 if ( !arguments.length ) {
161                         if ( elem ) {
162                                 hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
164                                 if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
165                                         return ret;
166                                 }
168                                 ret = elem.value;
170                                 return typeof ret === "string" ? 
171                                         // handle most common string cases
172                                         ret.replace(rreturn, "") : 
173                                         // handle cases where value is null/undef or number
174                                         ret == null ? "" : ret;
175                         }
177                         return undefined;
178                 }
180                 var isFunction = jQuery.isFunction( value );
182                 return this.each(function( i ) {
183                         var self = jQuery(this), val;
185                         if ( this.nodeType !== 1 ) {
186                                 return;
187                         }
189                         if ( isFunction ) {
190                                 val = value.call( this, i, self.val() );
191                         } else {
192                                 val = value;
193                         }
195                         // Treat null/undefined as ""; convert numbers to string
196                         if ( val == null ) {
197                                 val = "";
198                         } else if ( typeof val === "number" ) {
199                                 val += "";
200                         } else if ( jQuery.isArray( val ) ) {
201                                 val = jQuery.map(val, function ( value ) {
202                                         return value == null ? "" : value + "";
203                                 });
204                         }
206                         hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
208                         // If set returns undefined, fall back to normal setting
209                         if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
210                                 this.value = val;
211                         }
212                 });
213         }
216 jQuery.extend({
217         valHooks: {
218                 option: {
219                         get: function( elem ) {
220                                 // attributes.value is undefined in Blackberry 4.7 but
221                                 // uses .value. See #6932
222                                 var val = elem.attributes.value;
223                                 return !val || val.specified ? elem.value : elem.text;
224                         }
225                 },
226                 select: {
227                         get: function( elem ) {
228                                 var value,
229                                         index = elem.selectedIndex,
230                                         values = [],
231                                         options = elem.options,
232                                         one = elem.type === "select-one";
234                                 // Nothing was selected
235                                 if ( index < 0 ) {
236                                         return null;
237                                 }
239                                 // Loop through all the selected options
240                                 for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
241                                         var option = options[ i ];
243                                         // Don't return options that are disabled or in a disabled optgroup
244                                         if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
245                                                         (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
247                                                 // Get the specific value for the option
248                                                 value = jQuery( option ).val();
250                                                 // We don't need an array for one selects
251                                                 if ( one ) {
252                                                         return value;
253                                                 }
255                                                 // Multi-Selects return an array
256                                                 values.push( value );
257                                         }
258                                 }
260                                 // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
261                                 if ( one && !values.length && options.length ) {
262                                         return jQuery( options[ index ] ).val();
263                                 }
265                                 return values;
266                         },
268                         set: function( elem, value ) {
269                                 var values = jQuery.makeArray( value );
271                                 jQuery(elem).find("option").each(function() {
272                                         this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
273                                 });
275                                 if ( !values.length ) {
276                                         elem.selectedIndex = -1;
277                                 }
278                                 return values;
279                         }
280                 }
281         },
283         attrFn: {
284                 val: true,
285                 css: true,
286                 html: true,
287                 text: true,
288                 data: true,
289                 width: true,
290                 height: true,
291                 offset: true
292         },
293         
294         attrFix: {
295                 // Always normalize to ensure hook usage
296                 tabindex: "tabIndex"
297         },
298         
299         attr: function( elem, name, value, pass ) {
300                 var nType = elem.nodeType;
301                 
302                 // don't get/set attributes on text, comment and attribute nodes
303                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
304                         return undefined;
305                 }
307                 if ( pass && name in jQuery.attrFn ) {
308                         return jQuery( elem )[ name ]( value );
309                 }
311                 // Fallback to prop when attributes are not supported
312                 if ( !("getAttribute" in elem) ) {
313                         return jQuery.prop( elem, name, value );
314                 }
316                 var ret, hooks,
317                         notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
319                 // Normalize the name if needed
320                 if ( notxml ) {
321                         name = jQuery.attrFix[ name ] || name;
323                         hooks = jQuery.attrHooks[ name ];
325                         if ( !hooks ) {
326                                 // Use boolHook for boolean attributes
327                                 if ( rboolean.test( name ) ) {
328                                         hooks = boolHook;
330                                 // Use nodeHook if available( IE6/7 )
331                                 } else if ( nodeHook ) {
332                                         hooks = nodeHook;
333                                 }
334                         }
335                 }
337                 if ( value !== undefined ) {
339                         if ( value === null ) {
340                                 jQuery.removeAttr( elem, name );
341                                 return undefined;
343                         } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
344                                 return ret;
346                         } else {
347                                 elem.setAttribute( name, "" + value );
348                                 return value;
349                         }
351                 } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
352                         return ret;
354                 } else {
356                         ret = elem.getAttribute( name );
358                         // Non-existent attributes return null, we normalize to undefined
359                         return ret === null ?
360                                 undefined :
361                                 ret;
362                 }
363         },
365         removeAttr: function( elem, name ) {
366                 var propName;
367                 if ( elem.nodeType === 1 ) {
368                         name = jQuery.attrFix[ name ] || name;
370                         jQuery.attr( elem, name, "" );
371                         elem.removeAttribute( name );
373                         // Set corresponding property to false for boolean attributes
374                         if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) {
375                                 elem[ propName ] = false;
376                         }
377                 }
378         },
380         attrHooks: {
381                 type: {
382                         set: function( elem, value ) {
383                                 // We can't allow the type property to be changed (since it causes problems in IE)
384                                 if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
385                                         jQuery.error( "type property can't be changed" );
386                                 } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
387                                         // Setting the type on a radio button after the value resets the value in IE6-9
388                                         // Reset value to it's default in case type is set after value
389                                         // This is for element creation
390                                         var val = elem.value;
391                                         elem.setAttribute( "type", value );
392                                         if ( val ) {
393                                                 elem.value = val;
394                                         }
395                                         return value;
396                                 }
397                         }
398                 },
399                 // Use the value property for back compat
400                 // Use the nodeHook for button elements in IE6/7 (#1954)
401                 value: {
402                         get: function( elem, name ) {
403                                 if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
404                                         return nodeHook.get( elem, name );
405                                 }
406                                 return name in elem ?
407                                         elem.value :
408                                         null;
409                         },
410                         set: function( elem, value, name ) {
411                                 if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
412                                         return nodeHook.set( elem, value, name );
413                                 }
414                                 // Does not return so that setAttribute is also used
415                                 elem.value = value;
416                         }
417                 }
418         },
420         propFix: {
421                 tabindex: "tabIndex",
422                 readonly: "readOnly",
423                 "for": "htmlFor",
424                 "class": "className",
425                 maxlength: "maxLength",
426                 cellspacing: "cellSpacing",
427                 cellpadding: "cellPadding",
428                 rowspan: "rowSpan",
429                 colspan: "colSpan",
430                 usemap: "useMap",
431                 frameborder: "frameBorder",
432                 contenteditable: "contentEditable"
433         },
434         
435         prop: function( elem, name, value ) {
436                 var nType = elem.nodeType;
438                 // don't get/set properties on text, comment and attribute nodes
439                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
440                         return undefined;
441                 }
443                 var ret, hooks,
444                         notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
446                 if ( notxml ) {
447                         // Fix name and attach hooks
448                         name = jQuery.propFix[ name ] || name;
449                         hooks = jQuery.propHooks[ name ];
450                 }
452                 if ( value !== undefined ) {
453                         if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
454                                 return ret;
456                         } else {
457                                 return (elem[ name ] = value);
458                         }
460                 } else {
461                         if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
462                                 return ret;
464                         } else {
465                                 return elem[ name ];
466                         }
467                 }
468         },
469         
470         propHooks: {
471                 tabIndex: {
472                         get: function( elem ) {
473                                 // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
474                                 // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
475                                 var attributeNode = elem.getAttributeNode("tabindex");
477                                 return attributeNode && attributeNode.specified ?
478                                         parseInt( attributeNode.value, 10 ) :
479                                         rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
480                                                 0 :
481                                                 undefined;
482                         }
483                 }
484         }
487 // Add the tabindex propHook to attrHooks for back-compat
488 jQuery.attrHooks.tabIndex = jQuery.propHooks.tabIndex;
490 // Hook for boolean attributes
491 boolHook = {
492         get: function( elem, name ) {
493                 // Align boolean attributes with corresponding properties
494                 // Fall back to attribute presence where some booleans are not supported
495                 var attrNode;
496                 return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ?
497                         name.toLowerCase() :
498                         undefined;
499         },
500         set: function( elem, value, name ) {
501                 var propName;
502                 if ( value === false ) {
503                         // Remove boolean attributes when set to false
504                         jQuery.removeAttr( elem, name );
505                 } else {
506                         // value is true since we know at this point it's type boolean and not false
507                         // Set boolean attributes to the same name and set the DOM property
508                         propName = jQuery.propFix[ name ] || name;
509                         if ( propName in elem ) {
510                                 // Only set the IDL specifically if it already exists on the element
511                                 elem[ propName ] = true;
512                         }
514                         elem.setAttribute( name, name.toLowerCase() );
515                 }
516                 return name;
517         }
520 // IE6/7 do not support getting/setting some attributes with get/setAttribute
521 if ( !jQuery.support.getSetAttribute ) {
522         
523         // Use this for any attribute in IE6/7
524         // This fixes almost every IE6/7 issue
525         nodeHook = jQuery.valHooks.button = {
526                 get: function( elem, name ) {
527                         var ret;
528                         ret = elem.getAttributeNode( name );
529                         // Return undefined if nodeValue is empty string
530                         return ret && ret.nodeValue !== "" ?
531                                 ret.nodeValue :
532                                 undefined;
533                 },
534                 set: function( elem, value, name ) {
535                         // Set the existing or create a new attribute node
536                         var ret = elem.getAttributeNode( name );
537                         if ( !ret ) {
538                                 ret = document.createAttribute( name );
539                                 elem.setAttributeNode( ret );
540                         }
541                         return (ret.nodeValue = value + "");
542                 }
543         };
545         // Set width and height to auto instead of 0 on empty string( Bug #8150 )
546         // This is for removals
547         jQuery.each([ "width", "height" ], function( i, name ) {
548                 jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
549                         set: function( elem, value ) {
550                                 if ( value === "" ) {
551                                         elem.setAttribute( name, "auto" );
552                                         return value;
553                                 }
554                         }
555                 });
556         });
560 // Some attributes require a special call on IE
561 if ( !jQuery.support.hrefNormalized ) {
562         jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
563                 jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
564                         get: function( elem ) {
565                                 var ret = elem.getAttribute( name, 2 );
566                                 return ret === null ? undefined : ret;
567                         }
568                 });
569         });
572 if ( !jQuery.support.style ) {
573         jQuery.attrHooks.style = {
574                 get: function( elem ) {
575                         // Return undefined in the case of empty string
576                         // Normalize to lowercase since IE uppercases css property names
577                         return elem.style.cssText.toLowerCase() || undefined;
578                 },
579                 set: function( elem, value ) {
580                         return (elem.style.cssText = "" + value);
581                 }
582         };
585 // Safari mis-reports the default selected property of an option
586 // Accessing the parent's selectedIndex property fixes it
587 if ( !jQuery.support.optSelected ) {
588         jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
589                 get: function( elem ) {
590                         var parent = elem.parentNode;
592                         if ( parent ) {
593                                 parent.selectedIndex;
595                                 // Make sure that it also works with optgroups, see #5701
596                                 if ( parent.parentNode ) {
597                                         parent.parentNode.selectedIndex;
598                                 }
599                         }
600                         return null;
601                 }
602         });
605 // Radios and checkboxes getter/setter
606 if ( !jQuery.support.checkOn ) {
607         jQuery.each([ "radio", "checkbox" ], function() {
608                 jQuery.valHooks[ this ] = {
609                         get: function( elem ) {
610                                 // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
611                                 return elem.getAttribute("value") === null ? "on" : elem.value;
612                         }
613                 };
614         });
616 jQuery.each([ "radio", "checkbox" ], function() {
617         jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
618                 set: function( elem, value ) {
619                         if ( jQuery.isArray( value ) ) {
620                                 return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0);
621                         }
622                 }
623         });
626 })( jQuery );