DailyJS

Let's Make a Framework: CSS Manipulation

Introduction

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials frameworks css lmaf dom documentation

Let's Make a Framework: CSS Manipulation

Posted by Alex R. Young on .
Featured

tutorials frameworks css lmaf dom documentation

Let's Make a Framework: CSS Manipulation

Posted by Alex R. Young on .

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

If you haven't been following along, these articles are tagged with
lmaf. The project we're creating is called Turing. Documentation is
available at turingjs.com.

Last week I took a look at how jQuery manipulates the DOM using
\$.html(). This week I'm going to discuss how jQuery lets
us get and set CSS properties with .css.

jQuery's .css() Method

The .css() method can both get and set CSS values on
attributes. The reason why this is useful is -- you've guessed it --
browser incompatibilities. Standards-compliant browsers provide the
getComputedStyle() method, while Internet Explorer uses currentStyle and
runtimeStyle.

jQuery also returns the correct value for both CSS and DOM naming
schemes: .css('background-color') will return the same
value as .css('backgroundColor').

Even though accessing an element's style property, jQuery's
API provides a more consistent and less error-prone way of programming.

Internals

jQuery groups this functionality into
css.js. Getting values uses jQuery.css(elem, name), while setting
uses jQuery.style(elem, name, value).

jQuery.css

The first thing to note is the camelCase method, which is
used quite early on:

css: function( elem, name, extra ) {
  // Make sure that we're working with the right name
  var ret, origName = jQuery.camelCase( name ),
    hooks = jQuery.cssHooks[ origName ];

This is a very simple method that uses a regular expression to transform
dashed strings to DOM properties:

// rdashAlpha = /-([a-z])/ig
// fcamelCase = function( all, letter ) {
//   return letter.toUpperCase();
// };

camelCase: function( string ) {
  return string.replace( rdashAlpha, fcamelCase );
}

Which works like this:

'background-color'.replace(/-([a-z])/ig, function(all, letter) { return letter.toUpperCase(); })

>> 'backgroundColor'

This value is kept and referred to as originName. This is then used with
cssProps which helps deal with special cases. The "hooks"
refer to CSS hooks, which is jQuery's way of making CSS getting and
setting more extensible.

// cssProps: {
//  "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
// },

// Back in the CSS function:
name = jQuery.cssProps[ origName ] || origName;

// If a hook was provided get the computed value from there
if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
  return ret;

// Otherwise, if a way to get the computed value exists, use that
} else if ( curCSS ) {
  return curCSS( elem, name, origName );
}

If none of the hooks match, curCSS is called. This is
actually a reference to the getComputedStyle or
currentStyle method used to extract CSS values. jQuery
wraps these methods with its own functions to make them behave more
consistently. For example, the currentStyle function
converts sizes to pixels based on a hack written by Dean Edwards in
2007
.

Both of the calculated names are passed to getComputedStyle
and used to extracted the value from a query against
document.defaultView.getComputedStyle (or IE's equivalent).

jQuery.style

The .style method sets style properties, and works with
various signatures:

$('selector').css({ 'background-color': 'blue' });
$('selector').css('background-color', 'blue');

The first thing it does is checks the element is valid for this type of
operation:

// Don't set styles on text and comment nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
  return;
}

We've seen nodeType used
like this frequently in this series. The next part looks a lot like
.css():

// Make sure that we're working with the right name
var ret, origName = jQuery.camelCase( name ),
  style = elem.style, hooks = jQuery.cssHooks[ origName ];

name = jQuery.cssProps[ origName ] || origName;

When a value to set is present, some interesting checks are performed:

// Make sure that NaN and null values aren't set. See: #7116
if ( typeof value === "number" && isNaN( value ) || value == null ) {
  return;
}

// If a number was passed in, add 'px' to the (except for certain CSS properties)
if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) {
  value += "px";
}

// If a hook was provided, use that value, otherwise just set the specified value
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
  // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
  // Fixes bug #5509
  try {
    style[ name ] = value;
  } catch(e) {}
}

Other than the weird bugs worked around with the if
statements, writing styles simply boils down to mapping to a camel case
name then writing the element's property.

Conclusion

jQuery, and frameworks like it, give us a seamless way of getting and
setting style properties. It would be a fairly simple task if it wasn't
for those pesky browser bugs and incompatibilities.