Jump To …

06-attributes.js

jQuery Annotated Source.

Home | Previous Chapter | Next Chapter

Attributes

var rclass = /[\n\t\r]/g,
  rspace = /\s+/,
  rreturn = /\r/g,
  rtype = /^(?:button|input)$/i,
  rfocusable = /^(?:button|input|object|select|textarea)$/i,
  rclickable = /^a(?:rea)?$/i,
  rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
  rinvalidChar = /\:|^on/,
  formHook, boolHook;

jQuery.fn.extend({
  attr: function( name, value ) {
    return jQuery.access( this, name, value, true, jQuery.attr );
  },

  removeAttr: function( name ) {
    return this.each(function() {
      jQuery.removeAttr( this, name );
    });
  },
  
  prop: function( name, value ) {
    return jQuery.access( this, name, value, true, jQuery.prop );
  },
  
  removeProp: function( name ) {
    name = jQuery.propFix[ name ] || name;
    return this.each(function() {

try/catch handles cases where IE balks (such as removing a property on window)

      try {
        this[ name ] = undefined;
        delete this[ name ];
      } catch( e ) {}
    });
  },

  addClass: function( value ) {
    var classNames, i, l, elem,
      setClass, c, cl;

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( j ) {
        jQuery( this ).addClass( value.call(this, j, this.className) );
      });
    }

    if ( value && typeof value === "string" ) {
      classNames = value.split( rspace );

      for ( i = 0, l = this.length; i < l; i++ ) {
        elem = this[ i ];

        if ( elem.nodeType === 1 ) {
          if ( !elem.className && classNames.length === 1 ) {
            elem.className = value;

          } else {
            setClass = " " + elem.className + " ";

            for ( c = 0, cl = classNames.length; c < cl; c++ ) {
              if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
                setClass += classNames[ c ] + " ";
              }
            }
            elem.className = jQuery.trim( setClass );
          }
        }
      }
    }

    return this;
  },

  removeClass: function( value ) {
    var classNames, i, l, elem, className, c, cl;

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( j ) {
        jQuery( this ).removeClass( value.call(this, j, this.className) );
      });
    }

    if ( (value && typeof value === "string") || value === undefined ) {
      classNames = (value || "").split( rspace );

      for ( i = 0, l = this.length; i < l; i++ ) {
        elem = this[ i ];

        if ( elem.nodeType === 1 && elem.className ) {
          if ( value ) {
            className = (" " + elem.className + " ").replace( rclass, " " );
            for ( c = 0, cl = classNames.length; c < cl; c++ ) {
              className = className.replace(" " + classNames[ c ] + " ", " ");
            }
            elem.className = jQuery.trim( className );

          } else {
            elem.className = "";
          }
        }
      }
    }

    return this;
  },

  toggleClass: function( value, stateVal ) {
    var type = typeof value,
      isBool = typeof stateVal === "boolean";

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( i ) {
        jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
      });
    }

    return this.each(function() {
      if ( type === "string" ) {

toggle individual class names

        var className,
          i = 0,
          self = jQuery( this ),
          state = stateVal,
          classNames = value.split( rspace );

        while ( (className = classNames[ i++ ]) ) {

check each className given, space seperated list

          state = isBool ? state : !self.hasClass( className );
          self[ state ? "addClass" : "removeClass" ]( className );
        }

      } else if ( type === "undefined" || type === "boolean" ) {
        if ( this.className ) {

store className if set

          jQuery._data( this, "__className__", this.className );
        }

toggle whole className

        this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
      }
    });
  },

  hasClass: function( selector ) {
    var className = " " + selector + " ";
    for ( var i = 0, l = this.length; i < l; i++ ) {
      if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
        return true;
      }
    }

    return false;
  },

  val: function( value ) {
    var hooks, ret,
      elem = this[0];
    
    if ( !arguments.length ) {
      if ( elem ) {
        hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];

        if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
          return ret;
        }

        ret = elem.value;

        return typeof ret === "string" ? 

handle most common string cases

          ret.replace(rreturn, "") : 

handle cases where value is null/undef or number

          ret == null ? "" : ret;
      }

      return undefined;
    }

    var isFunction = jQuery.isFunction( value );

    return this.each(function( i ) {
      var self = jQuery(this), val;

      if ( this.nodeType !== 1 ) {
        return;
      }

      if ( isFunction ) {
        val = value.call( this, i, self.val() );
      } else {
        val = value;
      }

Treat null/undefined as ""; convert numbers to string

      if ( val == null ) {
        val = "";
      } else if ( typeof val === "number" ) {
        val += "";
      } else if ( jQuery.isArray( val ) ) {
        val = jQuery.map(val, function ( value ) {
          return value == null ? "" : value + "";
        });
      }

      hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];

If set returns undefined, fall back to normal setting

      if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
        this.value = val;
      }
    });
  }
});

jQuery.extend({
  valHooks: {
    option: {
      get: function( elem ) {

attributes.value is undefined in Blackberry 4.7 but uses .value. See #6932

        var val = elem.attributes.value;
        return !val || val.specified ? elem.value : elem.text;
      }
    },
    select: {
      get: function( elem ) {
        var value,
          index = elem.selectedIndex,
          values = [],
          options = elem.options,
          one = elem.type === "select-one";

Nothing was selected

        if ( index < 0 ) {
          return null;
        }

Loop through all the selected options

        for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
          var option = options[ i ];

Don't return options that are disabled or in a disabled optgroup

          if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
              (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {

Get the specific value for the option

            value = jQuery( option ).val();

We don't need an array for one selects

            if ( one ) {
              return value;
            }

Multi-Selects return an array

            values.push( value );
          }
        }

Fixes Bug #2551 -- select.val() broken in IE after form.reset()

        if ( one && !values.length && options.length ) {
          return jQuery( options[ index ] ).val();
        }

        return values;
      },

      set: function( elem, value ) {
        var values = jQuery.makeArray( value );

        jQuery(elem).find("option").each(function() {
          this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
        });

        if ( !values.length ) {
          elem.selectedIndex = -1;
        }
        return values;
      }
    }
  },

  attrFn: {
    val: true,
    css: true,
    html: true,
    text: true,
    data: true,
    width: true,
    height: true,
    offset: true
  },
  
  attrFix: {

Always normalize to ensure hook usage

    tabindex: "tabIndex"
  },
  
  attr: function( elem, name, value, pass ) {
    var nType = elem.nodeType;
    

don't get/set attributes on text, comment and attribute nodes

    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
      return undefined;
    }

    if ( pass && name in jQuery.attrFn ) {
      return jQuery( elem )[ name ]( value );
    }

Fallback to prop when attributes are not supported

    if ( !("getAttribute" in elem) ) {
      return jQuery.prop( elem, name, value );
    }

    var ret, hooks,
      notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

Normalize the name if needed

    if ( notxml ) {
      name = jQuery.attrFix[ name ] || name;

      hooks = jQuery.attrHooks[ name ];

      if ( !hooks ) {

Use boolHook for boolean attributes

        if ( rboolean.test( name ) ) {

          hooks = boolHook;

Use formHook for forms and if the name contains certain characters

        } else if ( formHook && name !== "className" &&
          (jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) {

          hooks = formHook;
        }
      }
    }

    if ( value !== undefined ) {

      if ( value === null ) {
        jQuery.removeAttr( elem, name );
        return undefined;

      } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
        return ret;

      } else {
        elem.setAttribute( name, "" + value );
        return value;
      }

    } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
      return ret;

    } else {

      ret = elem.getAttribute( name );

Non-existent attributes return null, we normalize to undefined

      return ret === null ?
        undefined :
        ret;
    }
  },

  removeAttr: function( elem, name ) {
    var propName;
    if ( elem.nodeType === 1 ) {
      name = jQuery.attrFix[ name ] || name;
    
      if ( jQuery.support.getSetAttribute ) {

Use removeAttribute in browsers that support it

        elem.removeAttribute( name );
      } else {
        jQuery.attr( elem, name, "" );
        elem.removeAttributeNode( elem.getAttributeNode( name ) );
      }

Set corresponding property to false for boolean attributes

      if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) {
        elem[ propName ] = false;
      }
    }
  },

  attrHooks: {
    type: {
      set: function( elem, value ) {

We can't allow the type property to be changed (since it causes problems in IE)

        if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
          jQuery.error( "type property can't be changed" );
        } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {

Setting the type on a radio button after the value resets the value in IE6-9 Reset value to it's default in case type is set after value This is for element creation

          var val = elem.value;
          elem.setAttribute( "type", value );
          if ( val ) {
            elem.value = val;
          }
          return value;
        }
      }
    },
    tabIndex: {
      get: function( elem ) {

elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/

        var attributeNode = elem.getAttributeNode("tabIndex");

        return attributeNode && attributeNode.specified ?
          parseInt( attributeNode.value, 10 ) :
          rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
            0 :
            undefined;
      }
    },

Use the value property for back compat Use the formHook for button elements in IE6/7 (#1954)

    value: {
      get: function( elem, name ) {
        if ( formHook && jQuery.nodeName( elem, "button" ) ) {
          return formHook.get( elem, name );
        }
        return name in elem ?
          elem.value :
          null;
      },
      set: function( elem, value, name ) {
        if ( formHook && jQuery.nodeName( elem, "button" ) ) {
          return formHook.set( elem, value, name );
        }

Does not return so that setAttribute is also used

        elem.value = value;
      }
    }
  },

  propFix: {
    tabindex: "tabIndex",
    readonly: "readOnly",
    "for": "htmlFor",
    "class": "className",
    maxlength: "maxLength",
    cellspacing: "cellSpacing",
    cellpadding: "cellPadding",
    rowspan: "rowSpan",
    colspan: "colSpan",
    usemap: "useMap",
    frameborder: "frameBorder",
    contenteditable: "contentEditable"
  },
  
  prop: function( elem, name, value ) {
    var nType = elem.nodeType;

don't get/set properties on text, comment and attribute nodes

    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
      return undefined;
    }

    var ret, hooks,
      notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    if ( notxml ) {

Fix name and attach hooks

      name = jQuery.propFix[ name ] || name;
      hooks = jQuery.propHooks[ name ];
    }

    if ( value !== undefined ) {
      if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
        return ret;

      } else {
        return (elem[ name ] = value);
      }

    } else {
      if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) {
        return ret;

      } else {
        return elem[ name ];
      }
    }
  },
  
  propHooks: {}
});

Hook for boolean attributes

boolHook = {
  get: function( elem, name ) {

Align boolean attributes with corresponding properties

    return jQuery.prop( elem, name ) ?
      name.toLowerCase() :
      undefined;
  },
  set: function( elem, value, name ) {
    var propName;
    if ( value === false ) {

Remove boolean attributes when set to false

      jQuery.removeAttr( elem, name );
    } else {

value is true since we know at this point it's type boolean and not false Set boolean attributes to the same name and set the DOM property

      propName = jQuery.propFix[ name ] || name;
      if ( propName in elem ) {

Only set the IDL specifically if it already exists on the element

        elem[ propName ] = true;
      }

      elem.setAttribute( name, name.toLowerCase() );
    }
    return name;
  }
};

IE6/7 do not support getting/setting some attributes with get/setAttribute

if ( !jQuery.support.getSetAttribute ) {

propFix is more comprehensive and contains all fixes

  jQuery.attrFix = jQuery.propFix;
  

Use this for any attribute on a form in IE6/7

  formHook = jQuery.attrHooks.name = jQuery.attrHooks.title = jQuery.valHooks.button = {
    get: function( elem, name ) {
      var ret;
      ret = elem.getAttributeNode( name );

Return undefined if nodeValue is empty string

      return ret && ret.nodeValue !== "" ?
        ret.nodeValue :
        undefined;
    },
    set: function( elem, value, name ) {

Check form objects in IE (multiple bugs related) Only use nodeValue if the attribute node exists on the form

      var ret = elem.getAttributeNode( name );
      if ( ret ) {
        ret.nodeValue = value;
        return value;
      }
    }
  };

Set width and height to auto instead of 0 on empty string( Bug #8150 ) This is for removals

  jQuery.each([ "width", "height" ], function( i, name ) {
    jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
      set: function( elem, value ) {
        if ( value === "" ) {
          elem.setAttribute( name, "auto" );
          return value;
        }
      }
    });
  });
}

Some attributes require a special call on IE

if ( !jQuery.support.hrefNormalized ) {
  jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
    jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
      get: function( elem ) {
        var ret = elem.getAttribute( name, 2 );
        return ret === null ? undefined : ret;
      }
    });
  });
}

if ( !jQuery.support.style ) {
  jQuery.attrHooks.style = {
    get: function( elem ) {

Return undefined in the case of empty string Normalize to lowercase since IE uppercases css property names

      return elem.style.cssText.toLowerCase() || undefined;
    },
    set: function( elem, value ) {
      return (elem.style.cssText = "" + value);
    }
  };
}

Safari mis-reports the default selected property of an option Accessing the parent's selectedIndex property fixes it

if ( !jQuery.support.optSelected ) {
  jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
    get: function( elem ) {
      var parent = elem.parentNode;

      if ( parent ) {
        parent.selectedIndex;

Make sure that it also works with optgroups, see #5701

        if ( parent.parentNode ) {
          parent.parentNode.selectedIndex;
        }
      }
    }
  });
}

Radios and checkboxes getter/setter

if ( !jQuery.support.checkOn ) {
  jQuery.each([ "radio", "checkbox" ], function() {
    jQuery.valHooks[ this ] = {
      get: function( elem ) {

Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified

        return elem.getAttribute("value") === null ? "on" : elem.value;
      }
    };
  });
}
jQuery.each([ "radio", "checkbox" ], function() {
  jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
    set: function( elem, value ) {
      if ( jQuery.isArray( value ) ) {
        return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0);
      }
    }
  });
});