No ticket. Use data_priv methods instead of jQuery._removeData and jQuery._data;...
[jquery.git] / src / attributes.js
blobb68f773d85512e09ec5675ead680413728d1b986
1 var nodeHook, boolHook,
2         rclass = /[\t\r\n]/g,
3         rreturn = /\r/g,
4         rfocusable = /^(?:input|select|textarea|button|object)$/i,
5         rclickable = /^(?:a|area)$/i;
7 jQuery.fn.extend({
8         attr: function( name, value ) {
9                 return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
10         },
12         removeAttr: function( name ) {
13                 return this.each(function() {
14                         jQuery.removeAttr( this, name );
15                 });
16         },
18         prop: function( name, value ) {
19                 return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
20         },
22         removeProp: function( name ) {
23                 return this.each(function() {
24                         delete this[ jQuery.propFix[ name ] || name ];
25                 });
26         },
28         addClass: function( value ) {
29                 var classes, elem, cur, clazz, j,
30                         i = 0,
31                         len = this.length,
32                         proceed = typeof value === "string" && value;
34                 if ( jQuery.isFunction( value ) ) {
35                         return this.each(function( j ) {
36                                 jQuery( this ).addClass( value.call( this, j, this.className ) );
37                         });
38                 }
40                 if ( proceed ) {
41                         // The disjunction here is for better compressibility (see removeClass)
42                         classes = ( value || "" ).match( core_rnotwhite ) || [];
44                         for ( ; i < len; i++ ) {
45                                 elem = this[ i ];
46                                 cur = elem.nodeType === 1 && ( elem.className ?
47                                         ( " " + elem.className + " " ).replace( rclass, " " ) :
48                                         " "
49                                 );
51                                 if ( cur ) {
52                                         j = 0;
53                                         while ( (clazz = classes[j++]) ) {
54                                                 if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
55                                                         cur += clazz + " ";
56                                                 }
57                                         }
58                                         elem.className = jQuery.trim( cur );
60                                 }
61                         }
62                 }
64                 return this;
65         },
67         removeClass: function( value ) {
68                 var classes, elem, cur, clazz, j,
69                         i = 0,
70                         len = this.length,
71                         proceed = arguments.length === 0 || typeof value === "string" && value;
73                 if ( jQuery.isFunction( value ) ) {
74                         return this.each(function( j ) {
75                                 jQuery( this ).removeClass( value.call( this, j, this.className ) );
76                         });
77                 }
78                 if ( proceed ) {
79                         classes = ( value || "" ).match( core_rnotwhite ) || [];
81                         for ( ; i < len; i++ ) {
82                                 elem = this[ i ];
83                                 // This expression is here for better compressibility (see addClass)
84                                 cur = elem.nodeType === 1 && ( elem.className ?
85                                         ( " " + elem.className + " " ).replace( rclass, " " ) :
86                                         ""
87                                 );
89                                 if ( cur ) {
90                                         j = 0;
91                                         while ( (clazz = classes[j++]) ) {
92                                                 // Remove *all* instances
93                                                 while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
94                                                         cur = cur.replace( " " + clazz + " ", " " );
95                                                 }
96                                         }
97                                         elem.className = value ? jQuery.trim( cur ) : "";
98                                 }
99                         }
100                 }
102                 return this;
103         },
105         toggleClass: function( value, stateVal ) {
106                 var type = typeof value,
107                         isBool = typeof stateVal === "boolean";
109                 if ( jQuery.isFunction( value ) ) {
110                         return this.each(function( i ) {
111                                 jQuery( this ).toggleClass( value.call(this, i, this.className, 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.match( core_rnotwhite ) || [];
124                                 while ( (className = classNames[ i++ ]) ) {
125                                         // check each className given, space separated list
126                                         state = isBool ? state : !self.hasClass( className );
127                                         self[ state ? "addClass" : "removeClass" ]( className );
128                                 }
130                         // Toggle whole class name
131                         } else if ( type === core_strundefined || type === "boolean" ) {
132                                 if ( this.className ) {
133                                         // store className if set
134                                         data_priv.set( this, "__className__", this.className );
135                                 }
137                                 // If the element has a class name or if we're passed "false",
138                                 // then remove the whole classname (if there was one, the above saved it).
139                                 // Otherwise bring back whatever was previously saved (if anything),
140                                 // falling back to the empty string if nothing was stored.
141                                 this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
142                         }
143                 });
144         },
146         hasClass: function( selector ) {
147                 var className = " " + selector + " ",
148                         i = 0,
149                         l = this.length;
150                 for ( ; i < l; i++ ) {
151                         if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
152                                 return true;
153                         }
154                 }
156                 return false;
157         },
159         val: function( value ) {
160                 var hooks, ret, isFunction,
161                         elem = this[0];
163                 if ( !arguments.length ) {
164                         if ( elem ) {
165                                 hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
167                                 if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
168                                         return ret;
169                                 }
171                                 ret = elem.value;
173                                 return typeof ret === "string" ?
174                                         // handle most common string cases
175                                         ret.replace(rreturn, "") :
176                                         // handle cases where value is null/undef or number
177                                         ret == null ? "" : ret;
178                         }
180                         return;
181                 }
183                 isFunction = jQuery.isFunction( value );
185                 return this.each(function( i ) {
186                         var val,
187                                 self = jQuery(this);
189                         if ( this.nodeType !== 1 ) {
190                                 return;
191                         }
193                         if ( isFunction ) {
194                                 val = value.call( this, i, self.val() );
195                         } else {
196                                 val = value;
197                         }
199                         // Treat null/undefined as ""; convert numbers to string
200                         if ( val == null ) {
201                                 val = "";
202                         } else if ( typeof val === "number" ) {
203                                 val += "";
204                         } else if ( jQuery.isArray( val ) ) {
205                                 val = jQuery.map(val, function ( value ) {
206                                         return value == null ? "" : value + "";
207                                 });
208                         }
210                         hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
212                         // If set returns undefined, fall back to normal setting
213                         if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
214                                 this.value = val;
215                         }
216                 });
217         }
220 jQuery.extend({
221         valHooks: {
222                 option: {
223                         get: function( elem ) {
224                                 // attributes.value is undefined in Blackberry 4.7 but
225                                 // uses .value. See #6932
226                                 var val = elem.attributes.value;
227                                 return !val || val.specified ? elem.value : elem.text;
228                         }
229                 },
230                 select: {
231                         get: function( elem ) {
232                                 var value, option,
233                                         options = elem.options,
234                                         index = elem.selectedIndex,
235                                         one = elem.type === "select-one" || index < 0,
236                                         values = one ? null : [],
237                                         max = one ? index + 1 : options.length,
238                                         i = index < 0 ?
239                                                 max :
240                                                 one ? index : 0;
242                                 // Loop through all the selected options
243                                 for ( ; i < max; i++ ) {
244                                         option = options[ i ];
246                                         // IE6-9 doesn't update selected after form reset (#2551)
247                                         if ( ( option.selected || i === index ) &&
248                                                         // Don't return options that are disabled or in a disabled optgroup
249                                                         ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
250                                                         ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
252                                                 // Get the specific value for the option
253                                                 value = jQuery( option ).val();
255                                                 // We don't need an array for one selects
256                                                 if ( one ) {
257                                                         return value;
258                                                 }
260                                                 // Multi-Selects return an array
261                                                 values.push( value );
262                                         }
263                                 }
265                                 return values;
266                         },
268                         set: function( elem, value ) {
269                                 var optionSet, option,
270                                         options = elem.options,
271                                         values = jQuery.makeArray( value ),
272                                         i = options.length;
274                                 while ( i-- ) {
275                                         option = options[ i ];
276                                         if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
277                                                 optionSet = true;
278                                         }
279                                 }
281                                 // force browsers to behave consistently when non-matching value is set
282                                 if ( !optionSet ) {
283                                         elem.selectedIndex = -1;
284                                 }
285                                 return values;
286                         }
287                 }
288         },
290         attr: function( elem, name, value ) {
291                 var hooks, ret,
292                         nType = elem.nodeType;
294                 // don't get/set attributes on text, comment and attribute nodes
295                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
296                         return;
297                 }
299                 // Fallback to prop when attributes are not supported
300                 if ( typeof elem.getAttribute === core_strundefined ) {
301                         return jQuery.prop( elem, name, value );
302                 }
304                 // All attributes are lowercase
305                 // Grab necessary hook if one is defined
306                 if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
307                         name = name.toLowerCase();
308                         hooks = jQuery.attrHooks[ name ] ||
309                                 ( jQuery.expr.match.boolean.test( name ) ? boolHook : nodeHook );
310                 }
312                 if ( value !== undefined ) {
314                         if ( value === null ) {
315                                 jQuery.removeAttr( elem, name );
317                         } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
318                                 return ret;
320                         } else {
321                                 elem.setAttribute( name, value + "" );
322                                 return value;
323                         }
325                 } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
326                         return ret;
328                 } else {
329                         ret = jQuery.find.attr( elem, name );
331                         // Non-existent attributes return null, we normalize to undefined
332                         return ret == null ?
333                                 undefined :
334                                 ret;
335                 }
336         },
338         removeAttr: function( elem, value ) {
339                 var name, propName,
340                         i = 0,
341                         attrNames = value && value.match( core_rnotwhite );
343                 if ( attrNames && elem.nodeType === 1 ) {
344                         while ( (name = attrNames[i++]) ) {
345                                 propName = jQuery.propFix[ name ] || name;
347                                 // Boolean attributes get special treatment (#10870)
348                                 if ( jQuery.expr.match.boolean.test( name ) ) {
349                                         // Set corresponding property to false
350                                         elem[ propName ] = false;
351                                 }
353                                 elem.removeAttribute( name );
354                         }
355                 }
356         },
358         attrHooks: {
359                 type: {
360                         set: function( elem, value ) {
361                                 if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
362                                         // Setting the type on a radio button after the value resets the value in IE6-9
363                                         // Reset value to default in case type is set after value during creation
364                                         var val = elem.value;
365                                         elem.setAttribute( "type", value );
366                                         if ( val ) {
367                                                 elem.value = val;
368                                         }
369                                         return value;
370                                 }
371                         }
372                 }
373         },
375         propFix: {
376                 "for": "htmlFor",
377                 "class": "className"
378         },
380         prop: function( elem, name, value ) {
381                 var ret, hooks, notxml,
382                         nType = elem.nodeType;
384                 // don't get/set properties on text, comment and attribute nodes
385                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
386                         return;
387                 }
389                 notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
391                 if ( notxml ) {
392                         // Fix name and attach hooks
393                         name = jQuery.propFix[ name ] || name;
394                         hooks = jQuery.propHooks[ name ];
395                 }
397                 if ( value !== undefined ) {
398                         if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
399                                 return ret;
401                         } else {
402                                 return ( elem[ name ] = value );
403                         }
405                 } else {
406                         if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
407                                 return ret;
409                         } else {
410                                 return elem[ name ];
411                         }
412                 }
413         },
415         propHooks: {
416                 tabIndex: {
417                         get: function( elem ) {
418                                 // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
419                                 // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
420                                 var attributeNode = elem.getAttributeNode("tabindex");
422                                 return attributeNode && attributeNode.specified ?
423                                         parseInt( attributeNode.value, 10 ) :
424                                         rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
425                                                 0 :
426                                                 undefined;
427                         }
428                 }
429         }
432 // Hooks for boolean attributes
433 boolHook = {
434         set: function( elem, value, name ) {
435                 if ( value === false ) {
436                         // Remove boolean attributes when set to false
437                         jQuery.removeAttr( elem, name );
438                 } else {
439                         elem.setAttribute( name, name );
440                 }
441                 return name;
442         }
444 jQuery.each( jQuery.expr.match.boolean.source.match( /\w+/g ), function( i, name ) {
445         var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
447         jQuery.expr.attrHandle[ name ] = function( elem, name, isXML ) {
448                 var fn = jQuery.expr.attrHandle[ name ],
449                         ret = isXML ?
450                                 undefined :
451                                 /* jshint eqeqeq: false */
452                                 // Temporarily disable this handler to check existence
453                                 (jQuery.expr.attrHandle[ name ] = undefined) !=
454                                         getter( elem, name, isXML ) ?
456                                         name.toLowerCase() :
457                                         null;
459                 // Restore handler
460                 jQuery.expr.attrHandle[ name ] = fn;
462                 return ret;
463         };
466 // Support: IE9+
467 // Selectedness for an option in an optgroup can be inaccurate
468 if ( !jQuery.support.optSelected ) {
469         jQuery.propHooks.selected = {
470                 get: function( elem ) {
471                         var parent = elem.parentNode;
472                         if ( parent && parent.parentNode ) {
473                                 parent.parentNode.selectedIndex;
474                         }
475                         return null;
476                 }
477         };
480 jQuery.each([
481         "tabIndex",
482         "readOnly",
483         "maxLength",
484         "cellSpacing",
485         "cellPadding",
486         "rowSpan",
487         "colSpan",
488         "useMap",
489         "frameBorder",
490         "contentEditable"
491 ], function() {
492         jQuery.propFix[ this.toLowerCase() ] = this;
495 // Radios and checkboxes getter/setter
496 jQuery.each([ "radio", "checkbox" ], function() {
497         jQuery.valHooks[ this ] = {
498                 set: function( elem, value ) {
499                         if ( jQuery.isArray( value ) ) {
500                                 return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
501                         }
502                 }
503         };
504         if ( !jQuery.support.checkOn ) {
505                 jQuery.valHooks[ this ].get = function( elem ) {
506                         // Support: Webkit
507                         // "" is returned instead of "on" if a value isn't specified
508                         return elem.getAttribute("value") === null ? "on" : elem.value;
509                 };
510         }