Merge branch 'master' of https://github.com/azatoth/jquery
[jquery.git] / src / attributes.js
blob5ccbf2cdecd55fc232f5ec89f46ca79e1969ada3
1 (function( jQuery ) {
3 var rclass = /[\n\t\r]/g,
4         rspaces = /\s+/,
5         rreturn = /\r/g,
6         rtype = /^(?:button|input)$/i,
7         rfocusable = /^(?:button|input|object|select|textarea)$/i,
8         rclickable = /^a(?:rea)?$/i,
9         formHook;
11 jQuery.fn.extend({
12         attr: function( name, value ) {
13                 return jQuery.access( this, name, value, true, jQuery.attr );
14         },
16         removeAttr: function( name ) {
17                 return this.each(function() {
18                         jQuery.removeAttr( this, name );
19                 });
20         },
21         
22         prop: function( name, value ) {
23                 return jQuery.access( this, name, value, true, jQuery.prop );
24         },
25         
26         removeProp: function( name ) {
27                 return this.each(function() {
28                         // try/catch handles cases where IE balks (such as removing a property on window)
29                         try {
30                                 this[ name ] = undefined;
31                                 delete this[ name ];
32                         } catch( e ) {}
33                 });
34         },
36         addClass: function( value ) {
37                 if ( jQuery.isFunction( value ) ) {
38                         return this.each(function(i) {
39                                 var self = jQuery(this);
40                                 self.addClass( value.call(this, i, self.attr("class") || "") );
41                         });
42                 }
44                 if ( value && typeof value === "string" ) {
45                         var classNames = (value || "").split( rspaces );
47                         for ( var i = 0, l = this.length; i < l; i++ ) {
48                                 var elem = this[i];
50                                 if ( elem.nodeType === 1 ) {
51                                         if ( !elem.className ) {
52                                                 elem.className = value;
54                                         } else {
55                                                 var className = " " + elem.className + " ",
56                                                         setClass = elem.className;
58                                                 for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
59                                                         if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
60                                                                 setClass += " " + classNames[c];
61                                                         }
62                                                 }
63                                                 elem.className = jQuery.trim( setClass );
64                                         }
65                                 }
66                         }
67                 }
69                 return this;
70         },
72         removeClass: function( value ) {
73                 if ( jQuery.isFunction(value) ) {
74                         return this.each(function(i) {
75                                 var self = jQuery(this);
76                                 self.removeClass( value.call(this, i, self.attr("class")) );
77                         });
78                 }
80                 if ( (value && typeof value === "string") || value === undefined ) {
81                         var classNames = (value || "").split( rspaces );
83                         for ( var i = 0, l = this.length; i < l; i++ ) {
84                                 var elem = this[i];
86                                 if ( elem.nodeType === 1 && elem.className ) {
87                                         if ( value ) {
88                                                 var className = (" " + elem.className + " ").replace(rclass, " ");
89                                                 for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
90                                                         className = className.replace(" " + classNames[c] + " ", " ");
91                                                 }
92                                                 elem.className = jQuery.trim( className );
94                                         } else {
95                                                 elem.className = "";
96                                         }
97                                 }
98                         }
99                 }
101                 return this;
102         },
104         toggleClass: function( value, stateVal ) {
105                 var type = typeof value,
106                         isBool = typeof stateVal === "boolean";
108                 if ( jQuery.isFunction( value ) ) {
109                         return this.each(function(i) {
110                                 var self = jQuery(this);
111                                 self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
112                         });
113                 }
115                 return this.each(function() {
116                         if ( type === "string" ) {
117                                 // toggle individual class names
118                                 var className,
119                                         i = 0,
120                                         self = jQuery( this ),
121                                         state = stateVal,
122                                         classNames = value.split( rspaces );
124                                 while ( (className = classNames[ i++ ]) ) {
125                                         // check each className given, space seperated list
126                                         state = isBool ? state : !self.hasClass( className );
127                                         self[ state ? "addClass" : "removeClass" ]( className );
128                                 }
130                         } else if ( type === "undefined" || type === "boolean" ) {
131                                 if ( this.className ) {
132                                         // store className if set
133                                         jQuery._data( this, "__className__", this.className );
134                                 }
136                                 // toggle whole className
137                                 this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
138                         }
139                 });
140         },
142         hasClass: function( selector ) {
143                 var className = " " + selector + " ";
144                 for ( var i = 0, l = this.length; i < l; i++ ) {
145                         if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
146                                 return true;
147                         }
148                 }
150                 return false;
151         },
153         val: function( value ) {
154                 var hooks, ret,
155                         elem = this[0];
156                 
157                 if ( !arguments.length ) {
158                         if ( elem ) {
159                                 hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
161                                 if ( hooks && "get" in hooks && (ret = hooks.get( elem )) !== undefined ) {
162                                         return ret;
163                                 }
165                                 return (elem.value || "").replace(rreturn, "");
166                         }
168                         return undefined;
169                 }
171                 var isFunction = jQuery.isFunction( value );
173                 return this.each(function( i ) {
174                         var self = jQuery(this), val;
176                         if ( this.nodeType !== 1 ) {
177                                 return;
178                         }
180                         if ( isFunction ) {
181                                 val = value.call( this, i, self.val() );
182                         } else {
183                                 val = value;
184                         }
186                         // Treat null/undefined as ""; convert numbers to string
187                         if ( val == null ) {
188                                 val = "";
189                         } else if ( typeof val === "number" ) {
190                                 val += "";
191                         } else if ( jQuery.isArray( val ) ) {
192                                 val = jQuery.map(val, function ( value ) {
193                                         return value == null ? "" : value + "";
194                                 });
195                         }
197                         hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
199                         // If set returns undefined, fall back to normal setting
200                         if ( !hooks || ("set" in hooks && hooks.set( this, val ) === undefined) ) {
201                                 this.value = val;
202                         }
203                 });
204         }
207 jQuery.extend({
208         valHooks: {
209                 option: {
210                         get: function( elem ) {
211                                 // attributes.value is undefined in Blackberry 4.7 but
212                                 // uses .value. See #6932
213                                 var val = elem.attributes.value;
214                                 return !val || val.specified ? elem.value : elem.text;
215                         }
216                 },
217                 select: {
218                         get: function( elem ) {
219                                 var index = elem.selectedIndex,
220                                         values = [],
221                                         options = elem.options,
222                                         one = elem.type === "select-one";
224                                 // Nothing was selected
225                                 if ( index < 0 ) {
226                                         return null;
227                                 }
229                                 // Loop through all the selected options
230                                 for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
231                                         var option = options[ i ];
233                                         // Don't return options that are disabled or in a disabled optgroup
234                                         if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
235                                                         (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
237                                                 // Get the specific value for the option
238                                                 value = jQuery( option ).val();
240                                                 // We don't need an array for one selects
241                                                 if ( one ) {
242                                                         return value;
243                                                 }
245                                                 // Multi-Selects return an array
246                                                 values.push( value );
247                                         }
248                                 }
250                                 // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
251                                 if ( one && !values.length && options.length ) {
252                                         return jQuery( options[ index ] ).val();
253                                 }
255                                 return values;
256                         },
258                         set: function( elem, value ) {
259                                 var values = jQuery.makeArray( value );
261                                 jQuery(elem).find("option").each(function() {
262                                         this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
263                                 });
265                                 if ( !values.length ) {
266                                         elem.selectedIndex = -1;
267                                 }
268                                 return values;
269                         }
270                 }
271         },
273         attrFn: {
274                 val: true,
275                 css: true,
276                 html: true,
277                 text: true,
278                 data: true,
279                 width: true,
280                 height: true,
281                 offset: true
282         },
283         
284         attrFix: {
285                 // Always normalize to ensure hook usage
286                 tabindex: "tabIndex",
287                 readonly: "readOnly"
288         },
289         
290         attr: function( elem, name, value, pass ) {
291                 var nType = elem.nodeType;
292                 
293                 // don't get/set attributes on text, comment and attribute nodes
294                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
295                         return undefined;
296                 }
298                 if ( pass && name in jQuery.attrFn ) {
299                         return jQuery( elem )[ name ]( value );
300                 }
301                 
302                 var ret, hooks,
303                         notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
304                 
305                 // Normalize the name if needed
306                 name = notxml && jQuery.attrFix[ name ] || name;
308                 // Get the appropriate hook, or the formHook
309                 // if getSetAttribute is not supported and we have form objects in IE6/7
310                 hooks = jQuery.attrHooks[ name ] || ( elem.nodeName === "FORM" && formHook );
312                 if ( value !== undefined ) {
314                         if ( value === null ) {
315                                 jQuery.removeAttr( elem, name );
316                                 return undefined;
318                         } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
319                                 return ret;
321                         } else {
322                                 elem.setAttribute( name, "" + value );
323                                 return value;
324                         }
326                 } else {
328                         if ( hooks && "get" in hooks && notxml ) {
329                                 return hooks.get( elem, name );
331                         } else {
333                                 ret = elem.getAttribute( name );
335                                 // Non-existent attributes return null, we normalize to undefined
336                                 return ret === null ?
337                                         undefined :
338                                         ret;
339                         }
340                 }
341         },
342         
343         removeAttr: function( elem, name ) {
344                 if ( elem.nodeType === 1 ) {
345                         name = jQuery.attrFix[ name ] || name;
346                 
347                         if ( jQuery.support.getSetAttribute ) {
348                                 // Use removeAttribute in browsers that support it
349                                 elem.removeAttribute( name );
350                         } else {
351                                 jQuery.attr( elem, name, "" );
352                                 elem.removeAttributeNode( elem.getAttributeNode( name ) );
353                         }
354                 }
355         },
357         attrHooks: {
358                 type: {
359                         set: function( elem, value ) {
360                                 // We can't allow the type property to be changed (since it causes problems in IE)
361                                 if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
362                                         jQuery.error( "type property can't be changed" );
363                                 }
364                         }
365                 },
366                 tabIndex: {
367                         get: function( elem ) {
368                                 // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
369                                 // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
370                                 var attributeNode = elem.getAttributeNode("tabIndex");
372                                 return attributeNode && attributeNode.specified ?
373                                         parseInt( attributeNode.value, 10 ) :
374                                         rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
375                                                 0 :
376                                                 undefined;
377                         }
378                 }
379         },
380         
381         propFix: {},
382         
383         prop: function( elem, name, value ) {
384                 var nType = elem.nodeType;
385                 
386                 // don't get/set properties on text, comment and attribute nodes
387                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
388                         return undefined;
389                 }
390                 
391                 var ret, hooks,
392                         notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
393                 
394                 // Try to normalize/fix the name
395                 name = notxml && jQuery.propFix[ name ] || name;
396                 
397                 hooks = jQuery.propHooks[ name ];
398                 
399                 if ( value !== undefined ) {
400                         if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
401                                 return ret;
402                         
403                         } else {
404                                 return (elem[ name ] = value);
405                         }
406                 
407                 } else {
408                         if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) {
409                                 return ret;
410                                 
411                         } else {
412                                 return elem[ name ];
413                         }
414                 }
415         },
416         
417         propHooks: {}
420 // IE6/7 do not support getting/setting some attributes with get/setAttribute
421 if ( !jQuery.support.getSetAttribute ) {
422         jQuery.attrFix = jQuery.extend( jQuery.attrFix, {
423                 "for": "htmlFor",
424                 "class": "className",
425                 maxlength: "maxLength",
426                 cellspacing: "cellSpacing",
427                 rowspan: "rowSpan",
428                 colspan: "colSpan",
429                 usemap: "useMap",
430                 frameborder: "frameBorder"
431         });
432         
433         // Use this for any attribute on a form in IE6/7
434         // And the name attribute
435         formHook = jQuery.attrHooks.name = {
436                 get: function( elem, name ) {
437                         var ret = elem.getAttributeNode( name );
438                         // Return undefined if not specified instead of empty string
439                         return ret && ret.specified ?
440                                 ret.nodeValue :
441                                 undefined;
442                 },
443                 set: function( elem, value, name ) {
444                         // Check form objects in IE (multiple bugs related)
445                         // Only use nodeValue if the attribute node exists on the form
446                         var ret = elem.getAttributeNode( name );
447                         if ( ret ) {
448                                 ret.nodeValue = value;
449                                 return value;
450                         }
451                 }
452         };
454         // Set width and height to auto instead of 0 on empty string( Bug #8150 )
455         // This is for removals
456         jQuery.each([ "width", "height" ], function( i, name ) {
457                 jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
458                         set: function( elem, value ) {
459                                 if ( value === "" ) {
460                                         elem.setAttribute( name, "auto" );
461                                         return value;
462                                 }
463                         }
464                 });
465         });
468 // Remove certain attrs if set to false
469 jQuery.each([ "selected", "checked", "readOnly", "disabled" ], function( i, name ) {
470         jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
471                 set: function( elem, value ) {
472                         if ( value === false ) {
473                                 jQuery.removeAttr( elem, name );
474                                 return value;
475                         }
476                 }
477         });
480 // Some attributes require a special call on IE
481 if ( !jQuery.support.hrefNormalized ) {
482         jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
483                 jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
484                         get: function( elem ) {
485                                 var ret = elem.getAttribute( name, 2 );
486                                 return ret === null ? undefined : ret;
487                         }
488                 });
489         });
492 if ( !jQuery.support.style ) {
493         jQuery.attrHooks.style = {
494                 get: function( elem ) {
495                         // Return undefined in the case of empty string
496                         // Normalize to lowercase since IE uppercases css property names
497                         return elem.style.cssText.toLowerCase() || undefined;
498                 },
499                 set: function( elem, value ) {
500                         return (elem.style.cssText = "" + value);
501                 }
502         };
505 // Safari mis-reports the default selected property of an option
506 // Accessing the parent's selectedIndex property fixes it
507 if ( !jQuery.support.optSelected ) {
508         jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
509                 get: function( elem ) {
510                         var parent = elem.parentNode;
512                         if ( parent ) {
513                                 parent.selectedIndex;
515                                 // Make sure that it also works with optgroups, see #5701
516                                 if ( parent.parentNode ) {
517                                         parent.parentNode.selectedIndex;
518                                 }
519                         }
520                 }
521         });
524 // Radios and checkboxes getter/setter
525 if ( !jQuery.support.checkOn ) {
526         jQuery.each([ "radio", "checkbox" ], function() {
527                 jQuery.valHooks[ this ] = {
528                         get: function( elem ) {
529                                 // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
530                                 return elem.getAttribute("value") === null ? "on" : elem.value;
531                         }
532                 };
533         });
535 jQuery.each([ "radio", "checkbox" ], function() {
536         jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
537                 set: function( elem, value ) {
538                         if ( jQuery.isArray( value ) ) {
539                                 return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0);
540                         }
541                 }
542         });
545 })( jQuery );