make sure toggleClass does not delete classNames when forcefully removing classes...
[jquery.git] / src / attributes.js
1 jQuery.fn.extend({
2         attr: function( name, value ) {
3                 var options = name, isFunction = jQuery.isFunction( value );
4
5                 if ( typeof name === "string" ) {
6                         // Are we setting the attribute?
7                         if ( value === undefined ) {
8                                 return this.length ?
9                                         jQuery.attr( this[0], name ) :
10                                         null;
11
12                         // Convert name, value params to options hash format
13                         } else {
14                                 options = {};
15                                 options[ name ] = value;
16                         }
17                 }
18
19                 // For each element...
20                 for ( var i = 0, l = this.length; i < l; i++ ) {
21                         var elem = this[i];
22
23                         // Set all the attributes
24                         for ( var prop in options ) {
25                                 value = options[prop];
26
27                                 if ( isFunction ) {
28                                         value = value.call( elem, i );
29                                 }
30
31                                 jQuery.attr( elem, prop, value );
32                         }
33                 }
34
35                 return this;
36         },
37
38         hasClass: function( selector ) {
39                 return !!selector && this.is( "." + selector );
40         },
41
42         val: function( value ) {
43                 if ( value === undefined ) {
44                         var elem = this[0];
45
46                         if ( elem ) {
47                                 if( jQuery.nodeName( elem, 'option' ) )
48                                         return (elem.attributes.value || {}).specified ? elem.value : elem.text;
49
50                                 // We need to handle select boxes special
51                                 if ( jQuery.nodeName( elem, "select" ) ) {
52                                         var index = elem.selectedIndex,
53                                                 values = [],
54                                                 options = elem.options,
55                                                 one = elem.type == "select-one";
56
57                                         // Nothing was selected
58                                         if ( index < 0 )
59                                                 return null;
60
61                                         // Loop through all the selected options
62                                         for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
63                                                 var option = options[ i ];
64
65                                                 if ( option.selected ) {
66                                                         // Get the specifc value for the option
67                                                         value = jQuery(option).val();
68
69                                                         // We don't need an array for one selects
70                                                         if ( one )
71                                                                 return value;
72
73                                                         // Multi-Selects return an array
74                                                         values.push( value );
75                                                 }
76                                         }
77
78                                         return values;
79                                 }
80
81                                 // Everything else, we just grab the value
82                                 return (elem.value || "").replace(/\r/g, "");
83
84                         }
85
86                         return undefined;
87                 }
88
89                 if ( typeof value === "number" )
90                         value += '';
91
92                 return this.each(function(){
93                         if ( this.nodeType != 1 )
94                                 return;
95
96                         if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
97                                 this.checked = (jQuery.inArray(this.value, value) >= 0 ||
98                                         jQuery.inArray(this.name, value) >= 0);
99
100                         else if ( jQuery.nodeName( this, "select" ) ) {
101                                 var values = jQuery.makeArray(value);
102
103                                 jQuery( "option", this ).each(function(){
104                                         this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
105                                                 jQuery.inArray( this.text, values ) >= 0);
106                                 });
107
108                                 if ( !values.length )
109                                         this.selectedIndex = -1;
110
111                         } else
112                                 this.value = value;
113                 });
114         }
115 });
116
117 jQuery.each({
118         removeAttr: function( name ) {
119                 jQuery.attr( this, name, "" );
120                 if (this.nodeType == 1)
121                         this.removeAttribute( name );
122         },
123
124         addClass: function( classNames ) {
125                 jQuery.className.add( this, classNames );
126         },
127
128         removeClass: function( classNames ) {
129                 jQuery.className.remove( this, classNames );
130         },
131
132         toggleClass: function( classNames, state ) {
133                 var type = typeof classNames;
134                 if ( type === "string" ) {
135                         // toggle individual class names
136                         var isBool = typeof state === "boolean", className, i = 0,
137                                 classNames = classNames.split( /\s+/ );
138                         while ( (className = classNames[ i++ ]) ) {
139                                 // check each className given, space seperated list
140                                 state = isBool ? state : !jQuery.className.has( this, className );
141                                 jQuery.className[ state ? "add" : "remove" ]( this, className );
142                         }
143                 } else if ( type === "undefined" || type === "boolean" ) {
144                         if ( this.className ) {
145                                 // store className if set
146                                 jQuery.data( this, "__className__", this.className );
147                         }
148                         // toggle whole className
149                         this.className = this.className || classNames === false ? "" : jQuery.data( this, "__className__" ) || "";
150                 }
151         }
152 }, function(name, fn){
153         jQuery.fn[ name ] = function(){
154                 return this.each( fn, arguments );
155         };
156 });
157
158 jQuery.extend({
159         className: {
160                 // internal only, use addClass("class")
161                 add: function( elem, classNames ) {
162                         jQuery.each((classNames || "").split(/\s+/), function(i, className){
163                                 if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
164                                         elem.className += (elem.className ? " " : "") + className;
165                         });
166                 },
167
168                 // internal only, use removeClass("class")
169                 remove: function( elem, classNames ) {
170                         if (elem.nodeType == 1)
171                                 elem.className = classNames !== undefined ?
172                                         jQuery.grep(elem.className.split(/\s+/), function(className){
173                                                 return !jQuery.className.has( classNames, className );
174                                         }).join(" ") :
175                                         "";
176                 },
177
178                 // internal only, use hasClass("class")
179                 has: function( elem, className ) {
180                         return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
181                 }
182         },
183
184         attr: function( elem, name, value ) {
185                 // don't set attributes on text and comment nodes
186                 if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
187                         return undefined;
188
189                 var notxml = !elem.tagName || !jQuery.isXMLDoc( elem ),
190                         // Whether we are setting (or getting)
191                         set = value !== undefined;
192
193                 // Try to normalize/fix the name
194                 name = notxml && jQuery.props[ name ] || name;
195
196                 // Only do all the following if this is a node (faster for style)
197                 if ( elem.tagName ) {
198
199                         // These attributes require special treatment
200                         var special = /href|src|style/.test( name );
201
202                         // Safari mis-reports the default selected property of a hidden option
203                         // Accessing the parent's selectedIndex property fixes it
204                         if ( name == "selected" && elem.parentNode )
205                                 elem.parentNode.selectedIndex;
206
207                         // If applicable, access the attribute via the DOM 0 way
208                         if ( name in elem && notxml && !special ) {
209                                 if ( set ){
210                                         // We can't allow the type property to be changed (since it causes problems in IE)
211                                         if ( name == "type" && elem.nodeName.match(/(button|input)/i) && elem.parentNode )
212                                                 throw "type property can't be changed";
213
214                                         elem[ name ] = value;
215                                 }
216
217                                 // browsers index elements by id/name on forms, give priority to attributes.
218                                 if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
219                                         return elem.getAttributeNode( name ).nodeValue;
220
221                                 // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
222                                 // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
223                                 if ( name == "tabIndex" ) {
224                                         var attributeNode = elem.getAttributeNode( "tabIndex" );
225                                         return attributeNode && attributeNode.specified
226                                                 ? attributeNode.value
227                                                 : elem.nodeName.match(/(button|input|object|select|textarea)/i)
228                                                         ? 0
229                                                         : elem.nodeName.match(/^(a|area)$/i) && elem.href
230                                                                 ? 0
231                                                                 : undefined;
232                                 }
233
234                                 return elem[ name ];
235                         }
236
237                         if ( !jQuery.support.style && notxml && name == "style" ) {
238                                 if ( set )
239                                         elem.style.cssText = "" + value;
240
241                                 return elem.style.cssText;
242                         }
243
244                         if ( set )
245                                 // convert the value to a string (all browsers do this but IE) see #1070
246                                 elem.setAttribute( name, "" + value );
247
248                         var attr = !jQuery.support.hrefNormalized && notxml && special
249                                         // Some attributes require a special call on IE
250                                         ? elem.getAttribute( name, 2 )
251                                         : elem.getAttribute( name );
252
253                         // Non-existent attributes return null, we normalize to undefined
254                         return attr === null ? undefined : attr;
255                 }
256
257                 // elem is actually elem.style ... set the style
258                 // Using attr for specific style information is now deprecated. Use style insead.
259                 return jQuery.style(elem, name, value);
260         }
261 });