Let's Make a Framework: Writing CSS Properties

2011-04-21 00:00:00 +0100 by Alex R. Young

Welcome to part 59 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.

I had some feedback on last week's commit from John-David
which can be viewed here: commit
The changes he suggested help keep behaviour relative to the element's
document rather than the global one. I tested both suggestions in

Writing CSS

Last week we were doing this sort of thing:


Now CSS properties can be read, how about writing them? I discussed how
jQuery implements writing styles back in part
. The algorithm works
like this:

  1. Check the element has the right nodeType
  2. Get the CSS property name, correcting case
  3. Make sure that NaN and null values aren't
  4. If a number was passed in, add px to the value (except
    for certain CSS properties)
  5. Write the value using the element's style property

This process is actually fairly simple. Even the cases where
px shouldn't be added are properties that aren't usually

// Exclude the following css properties to add px
cssNumber: {
  "zIndex": true,
  "fontWeight": true,
  "opacity": true,
  "zoom": true,
  "lineHeight": true

It's easier to pass numbers rather than remembering to use '10px'. The
reason this is an object with properties set to true is to
make it easy to use: jQuery.cssNumber[property] is nice
and succinct.


These are the tests I want to pass:

'test writing style properties': function() {
  var element = turing.dom.get('#dom-test')[0],
      expected = element.currentStyle ? '#f5f5f5' : 'rgb(245, 245, 245)';

  turing.dom.css(element, { 'background-color': expected, 'width': 1000 });

  assert.equal(turing.dom.css(element, 'background-color'), expected);
  assert.equal(turing.dom.css(element, 'backgroundColor'), expected);
  assert.equal(turing.dom.css(element, 'width'), '1000px');

'test chained writing style properties': function() {
  var element = turing.dom.get('#dom-test')[0],
      expected = element.currentStyle ? '#f1f1f1' : 'rgb(241, 241, 241)';

  turing('#dom-test').css({ 'background-color': expected });

  assert.equal(turing('#dom-test').css('background-color'), expected);
  assert.equal(turing('#dom-test').css('backgroundColor'), expected);

As usual I'm testing both the "modular" API and chained API. The first
test includes a numerical value that should automatically get
px set. I haven't tested all the edge cases because I don't
think it would help you learn anything about DOM programming.


The original dom.css method I defined already detected
cases where styles should be written, so I've added a loop to iterate
over an object with a list of styles:

 * Gets or sets style values.
 * @param {Object} element A DOM element
 * @returns {Object} The style value
dom.css = function(element, options) {
  if (typeof options === 'string') {
    return getStyle(element, options);
  } else {
    for (var property in options) {
      if (options.hasOwnProperty(property)) {
        setStyle(element, property, options[property]);

Next, setStyle's implementation is dependent on browser:

if (document.documentElement.currentStyle) {
  getStyle = function(element, property) {
    return element.currentStyle[camelCase(property)];

  setStyle = function(element, property, value) {
    return setStyleProperty(element, camelCase(property), value);
} else if (document.defaultView.getComputedStyle) {
  getStyle = function(element, property) {
    return element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue(uncamel(property));

  setStyle = function(element, property, value) {
    return setStyleProperty(element, uncamel(property), value);

The currentStyle detection lets us determine if we should
always camelCase properties or not. Next, setStyleProperty
does the real work:

function setStyleProperty(element, property, value) {
  if (invalidCSSNode(element)) {

  if (typeof value === 'number' && !cssNumericalProperty[property]) {
    value += 'px';

  element.style[property] = value;

This uses cssNumericalProperty which is the same as
jQuery's cssNumber object. I also created
invalidCSSNode to detect if the element's style can be
written to:

function invalidCSSNode(element) {
  return !element || element.nodeType === nodeTypes.TEXT_NODE || element.nodeType === nodeTypes.COMMENT_NODE || !element.style;

I've used the nodeTypes object again to make this more


The tests pass in IE, Firefox, and Chrome/Safari, so I'm happy. It would
be possible to take reading and writing CSS properties a lot further
than I have here -- colour values could be unified across browsers, and
maybe even the document's stylesheets could be manipulated.

This week's code is commit