Attributes and custom properties

  1. Properties
  2. Attributes
  3. Properties and attribytes synchronization
    1. id
    2. href
    3. value
    4. class/className
    5. The fun with old IEs
    6. Attributes as DOM nodes
  4. Summary

A DOM node may have attributes and properties. Sometimes people mess them up, because they are related, but they are really two different things.

Properties

DOM node is an object. So it can store custom properties and methods just like any JavaScript object.

The following example works by assigning an object to myData property of document.body:

document.body.myData = { 
  name: 'John'
}
alert(document.body.myData.name) // John

document.body.sayHi = function() { 
  alert(this.nodeName)
}
 
document.body.sayHi()  // BODY

Custom properties and methods are visible only in JavaScript and don’t affect HTML.

Also, custom properties show up in for..in mixed with native properties:

document.body.custom = 5

var list = []
for(var key in document.body) {
  list.push(key)
}

alert(list.join('\n'))

Custom DOM properties:

  • May have any value.Property names case-sensitive
  • Don’t affect HTML

Attributes

DOM nodes provide access to HTML attributes using the following standard methods:

  • elem.hasAttribute(name) - checks if the attribute exists
  • elem.getAttribute(name) - gets an attribute value
  • elem.setAttribute(name, value) - sets an attribute
  • elem.removeAttribute(name) - removes an attribute

The attributes are broken in IE<8 and in IE8 compatible rendering mode:

  • Only getAttribute and setAttribute methods exist.
  • They actually modify DOM properties, not attributes.
  • Attributes and properties in IE<8 are merged. Sometimes that leads to weird results, but the ways to manage attributes which we discuss here work fine with that.

In contrast with properties, attributes:

  • May be only strings.
  • Names not case-sensitive, because HTML attributes are not case-sensitive
  • They show up in innerHTML (unless it’s older IE)
  • You can list all attributes using an array-like attributes property of the element.

For example, let’s see the following HTML:

<body>
  <div about="Elephant" class="smiling"></div>
</body>

The example below sets attributes.

<body>
  <div about="Elephant" class="smiling"></div>

  <script>
    var div = document.body.children[0]
    alert( div.getAttribute('ABOUT') ) // (1)
    
    div.setAttribute('Test', 123)   // (2)
    alert( document.body.innerHTML )   // (3)
  </script>
</body>

When you run the code above, note the following:

  1. An getAttribute('ABOUT') uses the name in upper case, but that doesn’t matter.
  2. You can try assign a string or other primitive value, which will become a string. Object should be autoconverted, but IE has problems here, so stick to primitives.
  3. The innerHTML contains the new "test" attribute.

Properties and attribytes synchronization

Every type of DOM nodes has standard properties.

For example, see the 'A' tag: Interface HTMLAnchorElement.

It has "href" and "accessKey" and other specific attributes. Besides, it inherits "id" and other attributes from HTMLElement.

Standard DOM properties are synchronized with attributes.

id

For example, the browser synchronizes "id" attribute with id property:

<script>
  document.body.setAttribute('id','la-la-la')
  alert(document.body.id) // la-la-la
</script>

href

The synchronization does not guarantee the same value. Let’s set the "href" attribute for example:

<a href="#"></a>
<script>
  var a  = document.body.children[0]

  a.href = '/'
  alert( 'attribute:' + a.getAttribute('href') ) // '/'
  alert( 'property:' + a.href )  // IE: '/', others: *!*full URL*/!*

</script>

That’s because href, according to W3C specification must be a well-formed link.

There are other attributes, which are synced, but not copied. For example input.checked:

<input type="checkbox" checked>

<script>
  var input  = document.body.children[0]

  alert( input.checked ) // true
  alert( input.getAttribute('checked') ) // empty string
</script>

The value of input.checked property is either true or false, but the attribute has whatever you put into it.

value

There are also built-in properties which are synced one-way only.

For example, the input.value is synchronized from the attribute:

<body>
  <input type="text" value="markup">
  <script>
    var input = document.body.children[0]

    input.setAttribute('value', 'new')

    alert( input.value ) // 'new', input.value changed
  </script>
</body>

But the attribute is not synchronized from the property:

<body>
  <input type="text" value="markup">
  <script>
    var input = document.body.children[0]

    input.value = 'new'

    alert(input.getAttribute('value')) // 'markup', not changed!
  </script>
</body>

The "value" attribute keeps the original value after the property was updated, for example when a visitor typed in something. The original value can be used to check if the input is changed, or to reset it.

class/className

The naming exception: "class" attribute corresponds to className property.

Because "class" is a reserved word in JavaScript, the name of the corresponding property for the "class" attribute is className.

<body>
  <script>
    document.body.setAttribute('class', 'big red bloom')

    alert( document.body.className )  // ^^^
  </script>
</body>

Note, the example above doesn’t work in IE<9, because of the weird way attributes and properties are mixed up.

We can live fine with it, just always use className property to manage classes, not the attribute.

  1. Assign the div to a variable.
  2. Get the value of "data-widgetName" attribute.

The document:

<body>

  <!-- hello world! don't remove me.-->

  <div data-widgetName="menu">Select the genre</div>  

  <script>/* ... */</script>
</body>

The source is at tutorial/browser/dom/custom_attribute.html.

Open solution
Solution
  1. First, we need to get the div. Unfortunately, children contain comment nodes in IE, so direct document.body.children[0] is not cross-browser.

    But we can check the nodeType and take next child if needed:

    var div = document.body.children[0]
    if (div.nodeType != 1) {
      // can't use nextSibling, because it can be whitespace
      div = document.body.children[1]  
    }
    

  2. The only way to access a custom attribute lies through the attribute API.

    For example, getAttribute() method. (We use a simpler document as an example:

    <div data-widgetName="menu">Select the genre</div>
    
    <script>
    var div = document.body.children[0]
    
    alert( div.getAttribute('data-widgetName') )  // "menu"
    </script>
    

The fun with old IEs

First, IE<9 synchronizes all properties and attributes.:

document.body.setAttribute('try-in-ie', 123)

alert( document.body['try-in-ie'] === 123 )  // true in IE<9

Note that the type is also same. The attribute did not become a string, as it should.

Second, in IE<8 (or IE8 in IE7-compat. mode) properties and attributes are same. That leads to funny consequences.

For example, properties names are case-sensitive, and attribute names are not. And if the browser thinks that they are same, then what should be the result of the following code?

document.body.abba = 1 // assign property (now can read it by getAttribute)
document.body.ABBA = 5 // assign property with another case

// must get a property named 'ABba' in *!*case-insensitive*/!* way.
alert( document.body.getAttribute('ABba') ) // ??

The browser escapes the trap by picking the first value by default. It also provides an optional second IE-only parameter for getAttribute, which makes it case-sensitive. See MSDN getAttribute for details.

The "class" attribute changes class in all browsers except IE<9. Don’t use the attribute. Use className property all the time.

To live well with any IE, use attributes correctly.

Or, in other words, try using properties all the time, until you really need an attribute.

And the only times you really need an attribute are:

  1. To get a custom HTML attribute, because it is not synced to DOM property.
  2. To get an “original value” of the standard HTML attribute, like <INPUT value="...">.

Attributes as DOM nodes

Attributes are also accessible via elem.attributes collection.

In attributes collection, every attribute is represented by a special kind of DOM node. It has name, value and other properties.

For example:

<span style="color:blue" id="my">text</span>

<script>
  var span = document.body.children[0]
  alert( span.attributes['style'].value )  // "color:blue;"
  alert( span.attributes['id'].value )  // "my"
</script>

By the way, IE<8 and IE8 in compatibility mode go crazy about the "style" attribute. Guess why.

Attribute DOM nodes are not the part of the document tree, they are accessible from their elements only.

Summary

Both attributes and properties are core features in the DOM model.

The table of differences and relations:

Properties Attributes
Any value String
Names are case-sensitive not case-sensitive
Don’t show up in innerHTML Visible in innerHTML
Standard DOM properties and attributes are synchronized, custom are not.
Attributes are mixed with properties and screwed up in IE<8, IE8 compat. mode.

If you want to have custom attributes in HTML, remember that data-* attributes are valid in HTML5. See Custom data attributes section of HTML5 standard.

In real life, in 98% of cases DOM properties are used.

You should use attributes in only two cases:

  1. A custom HTML attribute, because it is not synced to DOM property.
  2. To access a built-in HTML attribute, which is not synced from the property, and you are sure you need the attribute.
    For example, value in INPUT.

Tutorial

Donate

Donate to this project