Let's Make a Framework: Element Properties

2011-05-26 00:00:00 +0100 by Alex R. Young

Welcome to part 64 of Let's Make a Framework, the ongoing series about
building a JavaScript framework.

These articles are tagged with
lmaf. The project we're creating is called Turing. Documentation is
available at turingjs.com.

For the past three weeks I've been looking at accessing element

When we talk about attributes we're referring to the actual HTML
attributes. These values can differ from the DOM element's properties.
The distinction is somewhat confusing, and many frameworks attempt to
iron out the differences behind the scenes. jQuery ended up providing
access to both attributes and properties.

Properties vs. Attributes

The most obvious case that demonstrates the difference between
properties and values is checkboxes. Accessing a checkbox's
checked attribute should return 'checked',
whereas the property will return true.

Perhaps that isn't completely obvious, but if you play around with this
you'll see what I mean:


To test if these checkboxes really behaved this way, I wrote these tests
for the prop method:

'test getting properties': function() {
  var checkbox = turing.dom.get('#checkbox')[0];

  assert.equal(turing.dom.prop(checkbox, 'checked'), true);
  assert.equal(turing('#checkbox').prop('checked'), true);

'test setting properties': function() {
  var checkbox = turing.dom.get('#checkbox')[0];

  turing.dom.prop(checkbox, 'checked', false);
  assert.equal(turing.dom.prop(checkbox, 'checked'), false);

'test removing properties': function() {
  var checkbox = turing.dom.get('#checkbox')[0];

  turing.dom.removeProp(checkbox, 'checked');
  assert.eqyal(turing.dom.prop(checkbox, 'checked'), undefined);


This is a basic implementation that just access an element's properties,
applying propertyFix from the previous tutorials when

 * Get or set properties.
 * @param {Object} element A DOM element
 * @param {String} attribute The property name
 * @param {String|Number|Boolean} value The property value
dom.prop = function(element, property, value) {
  if (propertyFix[property])
    property = propertyFix[property];
  if (typeof value === 'undefined') {
    return element[property];
  } else {
    if (value === null) {
      return dom.removeProperty(element, property);
    } else {
      return element[property] = value;

And this is the corresponding DOM chain method:

 * Get or set a property.
 * @param {String} property The property name
 * @param {String} value The property value
 * @returns {String} The property value
prop: function(property, value) {
  if (this.elements.length > 0) {
    return dom.prop(this[0], property, value);

It accesses the first element, if one has been found.

Attribute Implementation

This will actually fail in IE:

assert.equal(turing.dom.attr(checkbox, 'checked'), 'checked');

To get around this, we need to test to see if an attribute is a boolean
. A boolean attribute should return its own name when it's
set, which means IE's getAttribute code should test for
this and return the expected value:

booleanAttributes = {
  'selected': true,
  'readonly': true,
  'checked':  true

function getAttribute(element, name) {
  if (propertyFix[name]) {
    name = propertyFix[name];

  if (getAttributeParamFix[name]) {
    return element.getAttribute(name, 2);

  if (name === 'value' && element.nodeName === 'BUTTON') {
    return element.getAttributeNode(name).nodeValue;
  } else if (booleanAttributes[name]) {
    return element[name] ? name : undefined;

  return element.getAttribute(name);

I'll have to add more booleanAttributes later on.

Removing Properties

When I tried to remove the checked attribute in IE it
raised an exception which said Object doesn't support this action. I
decided to give up and catch the exception:

dom.removeProp = function(element, property) {
  if (propertyFix[property])
    property = propertyFix[property];
  try {
    element[property] = undefined;
    delete element[property];
  } catch (e) {


Property manipulation built on what we'd already achieved with
attributes, but has a few browser quirks of its own to deal with. A
naive implementation can be built easily, but supporting a wide range of
browsers is where the real effort comes in.

This week's code is commit