Let's Make a Framework: Element Properties
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 attributes:
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:
<input id="check" type="checkbox" checked="checked" />
Tests
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);
}
Implementation
This is a basic implementation that just access an element’s properties, applying propertyFix from the previous tutorials when required:
/**
* 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 attribute. 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) {
}
};
Conclusion
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 f385e09.
