Design & Download
Your JS Library
in Seconds!!!

Snippet: css() - Embedding Stylesheets in JS

Written by Christopher West (cwest) on November 07, 2015.
Creates a CSS stylesheet from an JSON representation of a stylesheet.
var css;
(function(
  document,
  RGX_UPPER,
  RGX_AMP,
  RGX_NO_COMMAS_OR_NOTHING,
  RGX_NO_AMP,
  RGX_IND_SEL,
  RGX_CLS,
  RGX_TRIM_SELS,
  undefined
) {
  css = function(obj, selAncestors) {
    if (typeof selAncestors != 'string') {
      if (selAncestors) {
        var className = ('_' + Math.random()).replace(RGX_CLS, +new Date);
        selAncestors = typeOf(selAncestors, 'Array')
          ? selAncestors
          : [selAncestors];
        for (var i = selAncestors.length; i--;) {
          selAncestors[i].className += ' ' + className;
        }
      }
      selAncestors = className ? '.' + className : '';
    }

    var code = getCssCode(obj, selAncestors);
    var style = document.createElement('style');
    style.type = 'text/css';
    if (style.styleSheet && !style.sheet) {
      style.styleSheet.cssText = code;
    }
    else {
      style.appendChild(document.createTextNode(code));
    }
    (document.getElementsByTagName('head')[0] || document.body).appendChild(style);
    return style;
  }

  function getCssCode(obj, selAncestors) {
    var rules = [];
    var rule = [];
    for (var key in obj) {
      if (has(obj, key)) {
        var value = obj[key];
        var typeName = typeOf(value);
        if (!key.indexOf('@media ')) {
          rules.push(key + '{' + getCssCode(value, selAncestors) + '}');
        }
        else if (typeName == 'Object') {
          // Trim selectors
          key = key.replace(RGX_TRIM_SELS, '$1');
          // Return all selectors
          key = key.replace(RGX_IND_SEL, function(sel) {
            sel = selAncestors ? sel.replace(RGX_NO_AMP, '& $&') : sel;
            return selAncestors.replace(RGX_NO_COMMAS_OR_NOTHING, function(selAncestor) {
              return sel.replace(RGX_AMP, selAncestor);
            });
          });
          rules.push(getCssCode(value, key));
        }
        else {
          value = typeName != 'Array'
            ? value != undefined
              ? value && typeof value == 'number'
                ? value + 'px'
                : ((value + '').slice(-1) == '!' ? value + 'important' : value)
              : 'none'
            : value.join(',');
          key = key.replace(RGX_UPPER, '-$&').toLowerCase();
          rule.push(key + ':' + value + ';');
        }
      }
    }
    if (rule[0]) {
        rules.unshift(selAncestors + '{' + rule.join('') + '}');
    }
    return rules.join('');
  }
})(
  document,
  /[A-Z]/g,
  /&/g,
  /[^,]+|^$/g,
  /^[^&]+$/,
  /[^\s\xa0,][^,]*/g,
  /0(.|$)/,
  /^[\s\xa0]+|[\s\xa0]*(,)[\s\xa0]*|[\s\xa0]+$/g
);

One of the cool things about JS is that you can do almost anything with it! The only thing is that you need to have the tools to get that “anything” job done. Recently, I had the desire to embed HTML, CSS, and JS all in one file that would be loaded when a specific browser event fired. The common solution is to use a library such as jQuery to load an HTML snippet but I wanted a different solution. I was thinking it would be great to be able to do something like this:

css({
  'div.special-1, span.special-2': {
    fontFamily: 'Trebuchet MS',
    a: {
      textDecoration: null,
      '&:link, &:visited': { color: '#08F' },
      '&:hover, &:active': { color: 'red' }
    }
  }
});

Having this resulting in a stylesheet added to the HEAD with the following CSS:

div.special-1, span.special-2 {
  font-family: Trebuchet MS;
}
div.special-1 a, span.special-2 a {
  text-decoration: none;
}

div.special-1 a:link, span.special-2 a:link,
div.special-1 a:visited, span.special-2 a:visited {
  color: #08F;
}

div.special-1 a:hover, span.special-2 a:hover,
div.special-1 a:active, span.special-2 a:active {
  color: red;
}

After a bit of work to make a few Sass-like features work, this YourJS snippet was born.

Brief css(objStyles, opt_ancestors) Documentation

This function takes an object representing CSS rules and adds a stylesheet with those rules to the document.

Parameters

  1. objStyles
    An object representing the CSS rules to be inserted into the document.
    • Property names will be used as media queries if they start with "@media ".
    • Property names will be used as rule selectors if the value is an object.
    • If a property name is to be used as a selector, if any selectors don't contain &, "& " will be prepended to it.
    • For all selectors, & will be replaced recursively with the selectors found in the parent.
    • CSS property names will be uncamelcased by inserting dashes before each uppercased character and lower casing all characters.
    • If a value is null or undefined, it will be turned into "none".
    • If a value is a number other than 0, "px" will be appended to it.
    • If a value is an array all of the items will be concatenated together, using "," to delimit the values.
    • If a value ends with ! it will be replaced with "!important".
  2. opt_ancestors Optional. This can be an element or an array of elements which will get another class added to target all rules to it and its children. This can alternatively be a CSS path (selector) specifying the root on which all CSS rules created should be based.

Returns

The stylesheet that is created and appended to the document is returned.

Future Development

One thing I would like to incorporate is the ability to have variables. Another nice-to-have thing would be the ability to supply more options such as where the stylesheet should be inserted (if it will be inserted at all), pretty-print, and helper functions (eg. darken). Feel free to leave a comment saying what other things may be nice to have.