DailyJS

Let's Make a Framework: Element Attributes Part 3

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials frameworks lmaf dom documentation

Let's Make a Framework: Element Attributes Part 3

Posted by Alex R. Young on .
Featured

tutorials frameworks lmaf dom documentation

Let's Make a Framework: Element Attributes Part 3

Posted by Alex R. Young on .

Welcome to part 63 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 two weeks I've been looking at accessing element
attributes. This week I'll demonstrate how to write element attributes.

API Design

I like the idea of using the same method for both getting and setting
element attributes:

// Get
turing('selector').attr('name');

// Set
turing('selector').attr('name', 'value');

It's fairly easy to remember this and easy to implement.

setAttribute

Remember element.getAttribute? Well, there's also
setAttribute. It was introduced in DOM Level
1
,
so browser support isn't terrible.

Most of the cross-browser issues relating to getAttribute
should apply to writing attributes because they were almost all related
to correcting IE's interpretation of the attribute's capitalisation.
That means we can use setAttribute in a very similar way.

Null Values

According to MDC:

Even though getAttribute() returns null for missing attributes, you should use removeAttribute() instead of elt.setAttribute(attr, null) to remove the attribute.

This is present in jQuery's
attributes.js implementation:

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

Note that we must distinguish between null and
undefined -- because we're using attr to read
and write values, undefined is an absence of a value which
implies reading the attribute.

That means we need to use element.removeAttribute when
null is passed. However, this method has poor browser
support. When I tried jQuery's implementation in IE6 I seemed to get an
empty string instead of undefined, so I think a true
cross-browser remove attribute method might be out of scope here.

To my knowledge, this is the closest I can get in IE:

function removeAttribute(element, name) {
  if (element.nodeType !== nodeTypes.ELEMENT_NODE) return;
  if (propertyFix[name]) name = propertyFix[name];
  setAttribute(element, name, '');
  element.removeAttributeNode(element.getAttributeNode(name));
}

Tests

I wrote this test to help me implement the core functionality for
writing attributes:

'test setting attributes': function() {
  var element = turing.dom.get('#attr-write')[0],
      link = turing.dom.get('#attr-write a')[0],
      input = turing.dom.get('#attr-write form input')[0],
      button = turing.dom.get('#attr-write form button')[0];

  turing.dom.attr(element, 'id', 'attr-test2');
  assert.equal(turing.dom.attr(element, 'id'), 'attr-test2');

  turing.dom.attr(element, 'class', 'example2');
  assert.equal(turing.dom.attr(element, 'class'), 'example2');

  turing.dom.attr(element, 'tabindex', 1);
  assert.equal(turing.dom.attr(element, 'tabindex'), 1);

  turing.dom.attr(link, 'href', '/somewhere');
  assert.equal(turing.dom.attr(link, 'href'), '/somewhere');

  // Forms
  turing.dom.attr(input, 'value', 'changed-value');
  assert.equal(turing.dom.attr(input, 'value'), 'changed-value');

  turing.dom.attr(input, 'name', 'changed-name');
  assert.equal(turing.dom.attr(input, 'name'), 'changed-name');

  turing.dom.attr(button, 'name', 'changed-button-name');
  assert.equal(turing.dom.attr(button, 'name'), 'changed-button-name');

  turing.dom.attr(button, 'value', 'changed-button-value');
  assert.equal(turing.dom.attr(button, 'value'), 'changed-button-value');
}

Implementation

Building on last week's code, I added a check to see if the attribute
value is null or undefined:

/**
 * Get or set attributes.
 *
 * @param {Object} element A DOM element
 * @param {String} attribute The attribute name
 * @param {String} value The attribute value
 */
dom.attr = function(element, attribute, value) {
  if (typeof value === 'undefined') {
    return turing.detect('getAttribute') ?
      element.getAttribute(attribute) : getAttribute(element, attribute);
  } else {
    if (value === null) {
      return dom.removeAttr(element, attribute);
    } else {
      return turing.detect('getAttribute') ?
        element.setAttribute(attribute, value) : setAttribute(element, attribute, value);
    }
  }
};

I reused the getAttribute capability test for writing
attributes. I started off with a simple attribute setter for IE:

function setAttribute(element, name, value) {
  if (propertyFix[name]) {
    name = propertyFix[name];
  }

  return element.setAttribute(name, value);
}

But the button test failed so I had to add similar code from
getAttribute:

if (name === 'value' && element.nodeName === 'BUTTON') {
  return element.getAttributeNode(name).nodeValue = value;
}

Conclusion

Getting and setting attributes is extremely similar, and it just goes to
show how frustratingly similar browser implementations have been. The
strange case of IE's removeAttribute behaviour still
baffles me, but if I find a good solution I'll write an update.

This week's code is in commit
c5625f8
.