Skip to content

Commit

Permalink
Add support for retrieving all styles added via JSS, fix issues in Fi…
Browse files Browse the repository at this point in the history
…refox and update README
  • Loading branch information
editht committed Mar 16, 2014
1 parent 59d0594 commit 5aab9d8
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 72 deletions.
66 changes: 41 additions & 25 deletions README.markdown
@@ -1,45 +1,61 @@
# JSS

A simple JavaScript library for getting/setting CSS stylesheet rules.
A simple JavaScript library for retrieving and setting CSS stylesheet rules.

* Tiny - ~250 lines of code.
* No dependencies.
* MIT Licensed.
* Tiny - ~200 lines of code
* No dependencies
* MIT Licensed

Why generate CSS with JavaScript?

* To set styles that need to be calculated or retrieved
* To set behavioural styles for your widget or plugin so that consumers aren't forced to include a stylesheet for core functionality
* To dynamically apply styles without cluttering your HTML (as is the case with inline styles)
* To set styles on all current and future elements

## Usage

Download and include `jss.js` in your HTML:

<script type="text/javascript" src="jss.js"></script>

Add new rule (or extend existing rule):
**jss.set(selector, properties)** to add a new rule or extend an existing rule:

jss.set('.special', {
color: 'red',
fontSize: '2em',
padding: '10px'
jss.set('.demo', {
'font-size': '15px',
'color': 'red'
});

Retrieve existing rule:

jss.get('.special');
**jss.get([selector])** to retrieve rules added via JSS:

// Returns:
jss.get('.demo');
// returns the following:
{
'color': 'red',
'font-size': '2em',
'padding-bottom': '10px',
'padding-left': '10px',
'padding-right': '10px',
'padding-top': '10px'
'font-size': '15px',
'color': 'red'
}

jss.get();
// returns the following:
{
'.demo': {
'font-size': '15px',
'color': 'red'
}
}


Remove existing rule:
**jss.getAll(selector)** to retrieve all rules that are specified using the selector (not necessarily added via JSS):

jss.remove('.special');
jss.getAll('.demo');
// returns the following:
{
'font-size': '15px',
'color': 'red',
'font-weight': 'bold'
}

## Why generate CSS with JS?
**jss.remove([selector])** to remove rules added via JSS:

* To set styles that must be calculated or retrieved - for example, setting the user's preferred font-size or color from a cookie.
* To set behavioural, rather than aesthetic, styles (especially for UI widget/plugin developers). Tabs, carousels, tooltips, etc. often require some basic CSS simply to function. Users of the code should not be *forced* to include a stylesheet for core functionality. Eye-candy CSS, of course, should still be added through regular stylesheets.
* It's better than inline styles since CSS rules apply to all current and future elements, and don't clutter the HTML when viewing in Firebug / Developer Tools.
jss.remove('.demo'); // removes all JSS styles matching the selector
jss.remove(); // removes all JSS styles
1 change: 1 addition & 0 deletions demo.html
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font: 16px sans-serif;
Expand Down
71 changes: 58 additions & 13 deletions jss.js
Expand Up @@ -8,10 +8,27 @@
var jss = (function(undefined) {
var adjSelAttrRgx = /((?:\.|#)[^\.\s#]+)((?:\.|#)[^\.\s#]+)/g;

function getSelectorsAndRules(sheet) {
var rules = sheet.cssRules || sheet.rules || [];
var results = {};
for (var i = 0; i < rules.length; i++) {
var selectorText = rules[i].selectorText;
if (!results[selectorText]) {
results[selectorText] = [];
}
results[selectorText].push({
sheet: sheet,
index: i,
style: rules[i].style
});
}
return results;
}

function getRules(sheet, selector) {
var rules = sheet.cssRules || sheet.rules || [];
var results = [];
// Browsers report selectors in lowercase - TODO check how true this is cross-browser
// Browsers report selectors in lowercase
selector = selector.toLowerCase();
for (var i = 0; i < rules.length; i++) {
// IE8 will split comma-delimited selectors into multiple rules, breaking our matching
Expand Down Expand Up @@ -72,7 +89,7 @@ var jss = (function(undefined) {
function declaredProperties(style) {
var declared = {};
for (var i = 0; i < style.length; i++) {
declared[style[i]] = style[style[i]];
declared[style[i]] = style[toCamelCase(style[i])];
}
return declared;
}
Expand All @@ -96,27 +113,57 @@ var jss = (function(undefined) {
return swap;
};

// FF will only recognise properties specified in camel-case
function sanitiseProperties(oldProps) {
var newProps = {};
for (oldKey in oldProps) {
var newKey = toCamelCase(oldKey);
newProps[newKey] = oldProps[oldKey]
}
return newProps;
}

function toCamelCase(str) {
return str.replace(/-([a-z])/g, function (match, submatch) {
return submatch.toUpperCase();
});
}

var Jss = function(doc) {
this.doc = doc;
this.head = this.doc.head || this.doc.getElementsByTagName('head')[0];
this.sheets = this.doc.styleSheets || [];
};

Jss.prototype = {
// Returns JSS rules (selector is optional)
get: function(selector) {
return this.defaultSheet ? aggregateStyles(getRules(this.defaultSheet, selector)) : {};
if (!this.defaultSheet) {
return {};
}
if (selector) {
return aggregateStyles(getRules(this.defaultSheet, selector));
}
var rules = getSelectorsAndRules(this.defaultSheet);
for (selector in rules) {
rules[selector] = aggregateStyles(rules[selector]);
}
return rules;
},
// Returns all rules (selector is required)
getAll: function(selector) {
var properties = {};
for (var i = 0; i < this.sheets.length; i++) {
extend(properties, aggregateStyles(getRules(this.sheets[i], selector)));
}
return properties;
},
// Adds JSS rules for the selector based on the given properties
set: function(selector, properties) {
if (!this.defaultSheet) {
this.defaultSheet = this._createSheet();
}
properties = sanitiseProperties(properties);
var rules = getRules(this.defaultSheet, selector);
if (!rules.length) {
rules = [addRule(this.defaultSheet, selector)];
Expand All @@ -125,22 +172,20 @@ var jss = (function(undefined) {
extend(rules[i].style, properties);
}
},
// Removes JSS rules (selector is optional)
remove: function(selector) {
if (!this.defaultSheet)
return;
if (selector) {
// Removes all rules for the selector added via JSS
var rules = getRules(this.defaultSheet, selector);
for (var i = 0; i < rules.length; i++) {
removeRule(rules[i]);
}
return rules.length;
}
else {
// Remove all JSS rules by removing the default sheet
if (!selector) {
this._removeSheet(this.defaultSheet);
delete this.defaultSheet;
return;
}
var rules = getRules(this.defaultSheet, selector);
for (var i = 0; i < rules.length; i++) {
removeRule(rules[i]);
}
return rules.length;
},
_createSheet: function() {
var styleNode = this.doc.createElement('style');
Expand Down
53 changes: 36 additions & 17 deletions test/test-jss.js
Expand Up @@ -5,58 +5,77 @@ module('jss', {
});

test('get retrieves styles from the JSS stylesheet', function() {
var result = jss.get('#qunit-fixture');
var result = jss.get();
equal(Object.keys(result).length, 0);

jss.set('#qunit-fixture', { 'color': 'red', 'font-size': '20px' });
result = jss.get('#qunit-fixture');
jss.set('#jss-test', { 'color': 'red', 'font-size': '20px' });
result = jss.get();
equal(Object.keys(result).length, 1);
equal(Object.keys(result['#jss-test']).length, 2);

jss.set('#jss-test .child-element', { 'color': 'blue' });
result = jss.get();
equal(Object.keys(result).length, 2);
equal(Object.keys(result['#jss-test']).length, 2);
equal(result['#jss-test']['color'], 'red');
equal(result['#jss-test']['font-size'], '20px');
equal(Object.keys(result['#jss-test .child-element']).length, 1);
equal(result['#jss-test .child-element']['color'], 'blue');
});

test('calling get with a selector retrieves styles from the JSS stylesheet for that selector', function() {
var result = jss.get('#jss-test');
equal(Object.keys(result).length, 0);

jss.set('#jss-test', { 'color': 'red', 'font-size': '20px' });
result = jss.get('#jss-test');
equal(Object.keys(result).length, 2);
equal(result['color'], 'red');
equal(result['font-size'], '20px');
});

test('getAll also retrieves styles not part of the JSS stylesheet', function() {
var result = jss.getAll('#qunit-fixture');
var result = jss.getAll('#jss-test');
equal(Object.keys(result).length, 1);
equal(result['display'], 'none');

jss.set('#qunit-fixture', { 'color': 'red', 'font-size': '20px' });
result = jss.getAll('#qunit-fixture');
jss.set('#jss-test', { 'color': 'red', 'font-size': '20px' });
result = jss.getAll('#jss-test');
equal(Object.keys(result).length, 3);
equal(result['color'], 'red');
equal(result['font-size'], '20px');
equal(result['display'], 'none');
});

test('remove only removes styles added to the JSS stylesheet', function() {
jss.set('#qunit-fixture', { 'color': 'red', 'font-size': '20px' });
var result = jss.getAll('#qunit-fixture');
jss.set('#jss-test', { 'color': 'red', 'font-size': '20px' });
var result = jss.getAll('#jss-test');
equal(Object.keys(result).length, 3);

jss.remove();
result = jss.getAll('#qunit-fixture');
result = jss.getAll('#jss-test');
equal(Object.keys(result).length, 1);
equal(result['display'], 'none');
});

test('calling remove with a selector only removes styles added to the JSS stylesheet', function() {
jss.set('#qunit-fixture', { 'color': 'red', 'font-size': '20px' });
var result = jss.getAll('#qunit-fixture');
jss.set('#jss-test', { 'color': 'red', 'font-size': '20px' });
var result = jss.getAll('#jss-test');
equal(Object.keys(result).length, 3);

jss.remove('#qunit-fixture');
result = jss.getAll('#qunit-fixture');
jss.remove('#jss-test');
result = jss.getAll('#jss-test');
equal(Object.keys(result).length, 1);
equal(result['display'], 'none');
});

test('calling remove with a selector only removes styles for that selector', function() {
jss.set('#qunit-fixture', { 'color': 'red' });
jss.set('#jss-test', { 'color': 'red' });
jss.set('#alternative-element', { 'color': 'blue' });
equal(jss.get('#qunit-fixture')['color'], 'red');
equal(jss.get('#jss-test')['color'], 'red');
equal(jss.get('#alternative-element')['color'], 'blue');

jss.remove('#qunit-fixture');
equal(Object.keys(jss.get('#qunit-fixture')).length, 0);
jss.remove('#jss-test');
equal(Object.keys(jss.get('#jss-test')).length, 0);
equal(jss.get('#alternative-element')['color'], 'blue');
});
36 changes: 19 additions & 17 deletions test/test.html
@@ -1,20 +1,22 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="qunit.css" type="text/css"/>
<script src="qunit.js"></script>
<script src="../jss.js"></script>
<script src="test-jss.js"></script>
<style>
#qunit-fixture {
display: none;
}
</style>
</head>
<body>
<h1 id="qunit-header">Test JSS</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture"></div>
</body>
<meta charset="UTF-8">
<link rel="stylesheet" href="qunit.css" type="text/css"/>
<script src="qunit.js"></script>
<script src="../jss.js"></script>
<script src="test-jss.js"></script>
<style>
#jss-test {
display: none;
}
</style>
</head>
<body>
<h1 id="qunit-header">Test JSS</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="jss-test"></div>
</body>
</html>

0 comments on commit 5aab9d8

Please sign in to comment.