Remove .hyphen property from tests (left behind in refactoring)
[jquery.git] / src / attributes.js
blob1bd3fac242eac428314fd8d86eefb71131e263fe
1 var nodeHook, boolHook, fixSpecified,
2         rclass = /[\t\r\n]/g,
3         rreturn = /\r/g,
4         rtype = /^(?:button|input)$/i,
5         rfocusable = /^(?:button|input|object|select|textarea)$/i,
6         rclickable = /^a(?:rea|)$/i,
7         rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
8         getSetAttribute = jQuery.support.getSetAttribute;
10 jQuery.fn.extend({
11         attr: function( name, value ) {
12                 return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
13         },
15         removeAttr: function( name ) {
16                 return this.each(function() {
17                         jQuery.removeAttr( this, name );
18                 });
19         },
21         prop: function( name, value ) {
22                 return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
23         },
25         removeProp: function( name ) {
26                 name = jQuery.propFix[ name ] || 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                 var classNames, i, l, elem,
38                         setClass, c, cl;
40                 if ( jQuery.isFunction( value ) ) {
41                         return this.each(function( j ) {
42                                 jQuery( this ).addClass( value.call(this, j, this.className) );
43                         });
44                 }
46                 if ( value && typeof value === "string" ) {
47                         classNames = value.split( core_rspace );
49                         for ( i = 0, l = this.length; i < l; i++ ) {
50                                 elem = this[ i ];
52                                 if ( elem.nodeType === 1 ) {
53                                         if ( !elem.className && classNames.length === 1 ) {
54                                                 elem.className = value;
56                                         } else {
57                                                 setClass = " " + elem.className + " ";
59                                                 for ( c = 0, cl = classNames.length; c < cl; c++ ) {
60                                                         if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
61                                                                 setClass += classNames[ c ] + " ";
62                                                         }
63                                                 }
64                                                 elem.className = jQuery.trim( setClass );
65                                         }
66                                 }
67                         }
68                 }
70                 return this;
71         },
73         removeClass: function( value ) {
74                 var removes, className, elem, c, cl, i, l;
76                 if ( jQuery.isFunction( value ) ) {
77                         return this.each(function( j ) {
78                                 jQuery( this ).removeClass( value.call(this, j, this.className) );
79                         });
80                 }
81                 if ( (value && typeof value === "string") || !arguments.length ) {
82                         removes = ( value || "" ).split( core_rspace );
84                         for ( i = 0, l = this.length; i < l; i++ ) {
85                                 elem = this[ i ];
86                                 if ( elem.nodeType === 1 && elem.className ) {
88                                         className = (" " + elem.className + " ").replace( rclass, " " );
90                                         // loop over each item in the removal list
91                                         for ( c = 0, cl = removes.length; c < cl; c++ ) {
92                                                 // Remove until there is nothing to remove,
93                                                 while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
94                                                         className = className.replace( " " + removes[ c ] + " " , " " );
95                                                 }
96                                         }
97                                         elem.className = value ? jQuery.trim( className ) : "";
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.split( core_rspace );
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                         } 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                         i = 0,
145                         l = this.length;
146                 for ( ; i < l; i++ ) {
147                         if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
148                                 return true;
149                         }
150                 }
152                 return false;
153         },
155         val: function( value ) {
156                 var hooks, ret, isFunction,
157                         elem = this[0];
159                 if ( !arguments.length ) {
160                         if ( elem ) {
161                                 hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
163                                 if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
164                                         return ret;
165                                 }
167                                 ret = elem.value;
169                                 return typeof ret === "string" ?
170                                         // handle most common string cases
171                                         ret.replace(rreturn, "") :
172                                         // handle cases where value is null/undef or number
173                                         ret == null ? "" : ret;
174                         }
176                         return;
177                 }
179                 isFunction = jQuery.isFunction( value );
181                 return this.each(function( i ) {
182                         var val,
183                                 self = jQuery(this);
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.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
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, option,
229                                         options = elem.options,
230                                         index = elem.selectedIndex,
231                                         one = elem.type === "select-one" || index < 0,
232                                         values = one ? null : [],
233                                         max = one ? index + 1 : options.length,
234                                         i = index < 0 ?
235                                                 max :
236                                                 one ? index : 0;
238                                 // Loop through all the selected options
239                                 for ( ; i < max; i++ ) {
240                                         option = options[ i ];
242                                         // oldIE doesn't update selected after form reset (#2551)
243                                         if ( ( option.selected || i === index ) &&
244                                                         // Don't return options that are disabled or in a disabled optgroup
245                                                         ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
246                                                         ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
248                                                 // Get the specific value for the option
249                                                 value = jQuery( option ).val();
251                                                 // We don't need an array for one selects
252                                                 if ( one ) {
253                                                         return value;
254                                                 }
256                                                 // Multi-Selects return an array
257                                                 values.push( value );
258                                         }
259                                 }
261                                 return values;
262                         },
264                         set: function( elem, value ) {
265                                 var values = jQuery.makeArray( value );
267                                 jQuery(elem).find("option").each(function() {
268                                         this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
269                                 });
271                                 if ( !values.length ) {
272                                         elem.selectedIndex = -1;
273                                 }
274                                 return values;
275                         }
276                 }
277         },
279         attr: function( elem, name, value, pass ) {
280                 var ret, hooks, notxml,
281                         nType = elem.nodeType;
283                 // don't get/set attributes on text, comment and attribute nodes
284                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
285                         return;
286                 }
288                 if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
289                         return jQuery( elem )[ name ]( value );
290                 }
292                 // Fallback to prop when attributes are not supported
293                 if ( typeof elem.getAttribute === "undefined" ) {
294                         return jQuery.prop( elem, name, value );
295                 }
297                 notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
299                 // All attributes are lowercase
300                 // Grab necessary hook if one is defined
301                 if ( notxml ) {
302                         name = name.toLowerCase();
303                         hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
304                 }
306                 if ( value !== undefined ) {
308                         if ( value === null ) {
309                                 jQuery.removeAttr( elem, name );
310                                 return;
312                         } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
313                                 return ret;
315                         } else {
316                                 elem.setAttribute( name, value + "" );
317                                 return value;
318                         }
320                 } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
321                         return ret;
323                 } else {
325                         ret = elem.getAttribute( name );
327                         // Non-existent attributes return null, we normalize to undefined
328                         return ret === null ?
329                                 undefined :
330                                 ret;
331                 }
332         },
334         removeAttr: function( elem, value ) {
335                 var propName, attrNames, name, isBool,
336                         i = 0;
338                 if ( value && elem.nodeType === 1 ) {
340                         attrNames = value.split( core_rspace );
342                         for ( ; i < attrNames.length; i++ ) {
343                                 name = attrNames[ i ];
345                                 if ( name ) {
346                                         propName = jQuery.propFix[ name ] || name;
347                                         isBool = rboolean.test( name );
349                                         // See #9699 for explanation of this approach (setting first, then removal)
350                                         // Do not do this for boolean attributes (see #10870)
351                                         if ( !isBool ) {
352                                                 jQuery.attr( elem, name, "" );
353                                         }
354                                         elem.removeAttribute( getSetAttribute ? name : propName );
356                                         // Set corresponding property to false for boolean attributes
357                                         if ( isBool && propName in elem ) {
358                                                 elem[ propName ] = false;
359                                         }
360                                 }
361                         }
362                 }
363         },
365         attrHooks: {
366                 type: {
367                         set: function( elem, value ) {
368                                 // We can't allow the type property to be changed (since it causes problems in IE)
369                                 if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
370                                         jQuery.error( "type property can't be changed" );
371                                 } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
372                                         // Setting the type on a radio button after the value resets the value in IE6-9
373                                         // Reset value to it's default in case type is set after value
374                                         // This is for element creation
375                                         var val = elem.value;
376                                         elem.setAttribute( "type", value );
377                                         if ( val ) {
378                                                 elem.value = val;
379                                         }
380                                         return value;
381                                 }
382                         }
383                 },
384                 // Use the value property for back compat
385                 // Use the nodeHook for button elements in IE6/7 (#1954)
386                 value: {
387                         get: function( elem, name ) {
388                                 if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
389                                         return nodeHook.get( elem, name );
390                                 }
391                                 return name in elem ?
392                                         elem.value :
393                                         null;
394                         },
395                         set: function( elem, value, name ) {
396                                 if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
397                                         return nodeHook.set( elem, value, name );
398                                 }
399                                 // Does not return so that setAttribute is also used
400                                 elem.value = value;
401                         }
402                 }
403         },
405         propFix: {
406                 tabindex: "tabIndex",
407                 readonly: "readOnly",
408                 "for": "htmlFor",
409                 "class": "className",
410                 maxlength: "maxLength",
411                 cellspacing: "cellSpacing",
412                 cellpadding: "cellPadding",
413                 rowspan: "rowSpan",
414                 colspan: "colSpan",
415                 usemap: "useMap",
416                 frameborder: "frameBorder",
417                 contenteditable: "contentEditable"
418         },
420         prop: function( elem, name, value ) {
421                 var ret, hooks, notxml,
422                         nType = elem.nodeType;
424                 // don't get/set properties on text, comment and attribute nodes
425                 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
426                         return;
427                 }
429                 notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
431                 if ( notxml ) {
432                         // Fix name and attach hooks
433                         name = jQuery.propFix[ name ] || name;
434                         hooks = jQuery.propHooks[ name ];
435                 }
437                 if ( value !== undefined ) {
438                         if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
439                                 return ret;
441                         } else {
442                                 return ( elem[ name ] = value );
443                         }
445                 } else {
446                         if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
447                                 return ret;
449                         } else {
450                                 return elem[ name ];
451                         }
452                 }
453         },
455         propHooks: {
456                 tabIndex: {
457                         get: function( elem ) {
458                                 // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
459                                 // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
460                                 var attributeNode = elem.getAttributeNode("tabindex");
462                                 return attributeNode && attributeNode.specified ?
463                                         parseInt( attributeNode.value, 10 ) :
464                                         rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
465                                                 0 :
466                                                 undefined;
467                         }
468                 }
469         }
472 // Hook for boolean attributes
473 boolHook = {
474         get: function( elem, name ) {
475                 // Align boolean attributes with corresponding properties
476                 // Fall back to attribute presence where some booleans are not supported
477                 var attrNode,
478                         property = jQuery.prop( elem, name );
479                 return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
480                         name.toLowerCase() :
481                         undefined;
482         },
483         set: function( elem, value, name ) {
484                 var propName;
485                 if ( value === false ) {
486                         // Remove boolean attributes when set to false
487                         jQuery.removeAttr( elem, name );
488                 } else {
489                         // value is true since we know at this point it's type boolean and not false
490                         // Set boolean attributes to the same name and set the DOM property
491                         propName = jQuery.propFix[ name ] || name;
492                         if ( propName in elem ) {
493                                 // Only set the IDL specifically if it already exists on the element
494                                 elem[ propName ] = true;
495                         }
497                         elem.setAttribute( name, name.toLowerCase() );
498                 }
499                 return name;
500         }
503 // IE6/7 do not support getting/setting some attributes with get/setAttribute
504 if ( !getSetAttribute ) {
506         fixSpecified = {
507                 name: true,
508                 id: true,
509                 coords: true
510         };
512         // Use this for any attribute in IE6/7
513         // This fixes almost every IE6/7 issue
514         nodeHook = jQuery.valHooks.button = {
515                 get: function( elem, name ) {
516                         var ret;
517                         ret = elem.getAttributeNode( name );
518                         return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
519                                 ret.value :
520                                 undefined;
521                 },
522                 set: function( elem, value, name ) {
523                         // Set the existing or create a new attribute node
524                         var ret = elem.getAttributeNode( name );
525                         if ( !ret ) {
526                                 ret = elem.ownerDocument.createAttribute( name );
527                                 elem.setAttributeNode( ret );
528                         }
529                         return ( ret.value = value + "" );
530                 }
531         };
533         // Set width and height to auto instead of 0 on empty string( Bug #8150 )
534         // This is for removals
535         jQuery.each([ "width", "height" ], function( i, name ) {
536                 jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
537                         set: function( elem, value ) {
538                                 if ( value === "" ) {
539                                         elem.setAttribute( name, "auto" );
540                                         return value;
541                                 }
542                         }
543                 });
544         });
546         // Set contenteditable to false on removals(#10429)
547         // Setting to empty string throws an error as an invalid value
548         jQuery.attrHooks.contenteditable = {
549                 get: nodeHook.get,
550                 set: function( elem, value, name ) {
551                         if ( value === "" ) {
552                                 value = "false";
553                         }
554                         nodeHook.set( elem, value, name );
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 // IE6/7 call enctype encoding
606 if ( !jQuery.support.enctype ) {
607         jQuery.propFix.enctype = "encoding";
610 // Radios and checkboxes getter/setter
611 if ( !jQuery.support.checkOn ) {
612         jQuery.each([ "radio", "checkbox" ], function() {
613                 jQuery.valHooks[ this ] = {
614                         get: function( elem ) {
615                                 // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
616                                 return elem.getAttribute("value") === null ? "on" : elem.value;
617                         }
618                 };
619         });
621 jQuery.each([ "radio", "checkbox" ], function() {
622         jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
623                 set: function( elem, value ) {
624                         if ( jQuery.isArray( value ) ) {
625                                 return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
626                         }
627                 }
628         });