Skip to content

Commit

Permalink
Separate CSS loading from parsing; move parser into a separate file.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr0grog committed Apr 29, 2013
1 parent 23d0240 commit 706e252
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 109 deletions.
120 changes: 11 additions & 109 deletions lib/element-query.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
var elementQuery = (function() {
// Regexes for parsing
var COMMENT_PATTERN = /(\/\*)[\s\S]*?(\*\/)/g;
var CSS_RULE_PATTERN = /\s*([^{]+)\{([^}]*)\}\s*/g;
var QUERY_PATTERN = /:media\s*\(([^)]*)\)/g;
var QUERY_RULES_PATTERN = /\(?([^\s:]+):\s*(\d+(?:\.\d+)?)(px|em|rem|vw|vh|vmin|vmax)\)?/g;
var MEDIA_PATTERN = /(@media[^{]*)\{((?:\s*([^{]+)\{([^}]*)\}\s*)*)\}/g;

// implementations for testing actual element query properties
var queryMatchers = {
Expand All @@ -21,9 +15,6 @@ var elementQuery = (function() {
},
};

// list of known queries that need matching
var queries = [];

// convert an element query into a css class name we can replace it with
var classNameForRules = function(rules) {
var name = "query";
Expand Down Expand Up @@ -80,17 +71,12 @@ var elementQuery = (function() {
return true;
};


/**
* el-cheapo parser. It at least screws up nested @media rules
* and puts things that were in @media rules out-of-order
*/
var parser = {
var loader = {
// parse an array of CSSStyleSheet objects for element queries
parseStyleSheets: function(sheets, callback) {
loadStyleSheets: function(sheets, callback) {
var completed = 0;
for (var i = 0, len = sheets.length; i < len; i++) {
this.parseStyleSheet(sheets[i], function() {
this.loadStyleSheet(sheets[i], function() {
completed += 1;
if (completed === len) {
callback && callback();
Expand All @@ -100,9 +86,9 @@ var elementQuery = (function() {
},

// parse a single CSSStyleSheet object for element queries
parseStyleSheet: function(sheet, callback) {
loadStyleSheet: function(sheet, callback) {
if (sheet.ownerNode.nodeName === "STYLE") {
var newStyles = parser.parseStyleText(sheet.ownerNode.innerHTML);
var newStyles = elementQuery.parser.parseStyleText(sheet.ownerNode.innerHTML);
sheet.ownerNode.innerHTML += newStyles;
callback && callback();
}
Expand All @@ -112,7 +98,7 @@ var elementQuery = (function() {
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var newStyles = parser.parseStyleText(xhr.responseText);
var newStyles = elementQuery.parser.parseStyleText(xhr.responseText);
var style = document.createElement("style");
style.innerHTML = newStyles;
document.body.appendChild(style);
Expand All @@ -126,91 +112,6 @@ var elementQuery = (function() {
xhr.send(null);
}
},

// parse the raw text of a style sheet for element queries
parseStyleText: function(sheet) {
// new stylesheet content to add (replacing rules with `:media()` in them)
var newRules = "";

// remove comments
sheet = sheet.replace(COMMENT_PATTERN, "");

// manage vanilla media queries
var mediaRules = "";
sheet = sheet.replace(MEDIA_PATTERN, function(mediaString, query, content) {
var newMediaRules = parser.parseStyleText(content);
if (!/^\s*$/.test(newMediaRules)) {
mediaRules += query + "{\n" + newMediaRules + "\n}\n";
}
return "";
});

var ruleMatch;
while (ruleMatch = CSS_RULE_PATTERN.exec(sheet)) {
var results = this.queriesForSelector(ruleMatch[1]);
if (results.queries.length) {
newRules += results.selector + "{" + ruleMatch[2] + "}\n";
queries.push.apply(queries, results.queries);
}
}
return newRules + "\n" + mediaRules;
},

// find all the queries in a selector
queriesForSelector: function(selectorString) {
var selectors = selectorString.split(",");
var selectorResults = [];
var queryResults = [];
for (var i = 0, len = selectors.length; i < len; i++) {
var result = this.queriesForSingleSelector(selectors[i]);
selectorResults.push(result.selector);
queryResults = queryResults.concat(result.queries);
}
return {
selector: selectorResults.join(","),
queries: queryResults
};
},

// find all the queries in a *single* selector (i.e. no commas)
queriesForSingleSelector: function(selectorString) {
var queries = [];
var newSelector = "";
var lastIndex = 0;
var queryMatch;
while (queryMatch = QUERY_PATTERN.exec(selectorString)) {
var querySelector = selectorString.slice(0, queryMatch.index);
var queryRules = this.parseQuery(queryMatch[1]);
var className = classNameForRules(queryRules);
newSelector += selectorString.slice(lastIndex, queryMatch.index);
lastIndex = queryMatch.index + queryMatch[0].length;
queries.push({
selector: newSelector,
className: className,
rules: queryRules
});
newSelector += "." + className;
}
newSelector += selectorString.slice(lastIndex);
return {
selector: newSelector,
queries: queries
};
},

// find the actual queried properties in an element query
parseQuery: function(queryString) {
var rules = [];
var ruleMatch;
while (ruleMatch = QUERY_RULES_PATTERN.exec(queryString)) {
rules.push({
property: ruleMatch[1],
value: parseFloat(ruleMatch[2]),
units: ruleMatch[3]
});
}
return rules;
}
};

// public API
Expand All @@ -219,7 +120,7 @@ var elementQuery = (function() {

init: function() {
var evaluated = false;
parser.parseStyleSheets(document.styleSheets, function() {
this.loader.loadStyleSheets(document.styleSheets, function() {
evaluated = true;
elementQuery.evaluateQueries();
});
Expand All @@ -234,6 +135,7 @@ var elementQuery = (function() {
// update the styling for all the elements that have queries
evaluateQueries: function(context) {
context = context || document;
var queries = this.queries;
for (var i = 0, len = queries.length; i < len; i++) {
var elements = context.querySelectorAll(queries[i].selector);
for (var j = 0; j < elements.length; j++) {
Expand All @@ -249,9 +151,9 @@ var elementQuery = (function() {
},

queryMatchers: queryMatchers,
queries: queries,
parser: parser,

queries: [],
classNameForRules: classNameForRules,
loader: loader
};

// re-run all queries on resize
Expand Down
101 changes: 101 additions & 0 deletions lib/parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
(function(elementQuery) {
// Regexes for parsing
var COMMENT_PATTERN = /(\/\*)[\s\S]*?(\*\/)/g;
var CSS_RULE_PATTERN = /\s*([^{]+)\{([^}]*)\}\s*/g;
var QUERY_PATTERN = /:media\s*\(([^)]*)\)/g;
var QUERY_RULES_PATTERN = /\(?([^\s:]+):\s*(\d+(?:\.\d+)?)(px|em|rem|vw|vh|vmin|vmax)\)?/g;
var MEDIA_PATTERN = /(@media[^{]*)\{((?:\s*([^{]+)\{([^}]*)\}\s*)*)\}/g;

/**
* el-cheapo parser. It at least screws up nested @media rules
* and puts things that were in @media rules out-of-order
*/
elementQuery.parser = {
// parse the raw text of a style sheet for element queries
parseStyleText: function(sheet) {
// new stylesheet content to add (replacing rules with `:media()` in them)
var newRules = "";

// remove comments
sheet = sheet.replace(COMMENT_PATTERN, "");

// manage vanilla media queries
var mediaRules = "";
var parser = this;
sheet = sheet.replace(MEDIA_PATTERN, function(mediaString, query, content) {
var newMediaRules = parser.parseStyleText(content);
if (!/^\s*$/.test(newMediaRules)) {
mediaRules += query + "{\n" + newMediaRules + "\n}\n";
}
return "";
});

var ruleMatch;
while (ruleMatch = CSS_RULE_PATTERN.exec(sheet)) {
var results = this.queriesForSelector(ruleMatch[1]);
if (results.queries.length) {
newRules += results.selector + "{" + ruleMatch[2] + "}\n";
elementQuery.queries.push.apply(elementQuery.queries, results.queries);
}
}
return newRules + "\n" + mediaRules;
},

// find all the queries in a selector
queriesForSelector: function(selectorString) {
var selectors = selectorString.split(",");
var selectorResults = [];
var queryResults = [];
for (var i = 0, len = selectors.length; i < len; i++) {
var result = this.queriesForSingleSelector(selectors[i]);
selectorResults.push(result.selector);
queryResults = queryResults.concat(result.queries);
}
return {
selector: selectorResults.join(","),
queries: queryResults
};
},

// find all the queries in a *single* selector (i.e. no commas)
queriesForSingleSelector: function(selectorString) {
var queries = [];
var newSelector = "";
var lastIndex = 0;
var queryMatch;
while (queryMatch = QUERY_PATTERN.exec(selectorString)) {
var querySelector = selectorString.slice(0, queryMatch.index);
var queryRules = this.parseQuery(queryMatch[1]);
var className = elementQuery.classNameForRules(queryRules);
newSelector += selectorString.slice(lastIndex, queryMatch.index);
lastIndex = queryMatch.index + queryMatch[0].length;
queries.push({
selector: newSelector,
className: className,
rules: queryRules
});
newSelector += "." + className;
}
newSelector += selectorString.slice(lastIndex);
return {
selector: newSelector,
queries: queries
};
},

// find the actual queried properties in an element query
parseQuery: function(queryString) {
var rules = [];
var ruleMatch;
while (ruleMatch = QUERY_RULES_PATTERN.exec(queryString)) {
rules.push({
property: ruleMatch[1],
value: parseFloat(ruleMatch[2]),
units: ruleMatch[3]
});
}
return rules;
}
};

}(elementQuery));
1 change: 1 addition & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
</style> -->

<script src="../lib/element-query.js"></script>
<script src="../lib/parser.js"></script>
</head>
<body>

Expand Down

0 comments on commit 706e252

Please sign in to comment.