From 706e2526d59321b9ed11847212f991f11273a420 Mon Sep 17 00:00:00 2001 From: Rob Brackett Date: Sun, 28 Apr 2013 17:23:56 -0700 Subject: [PATCH] Separate CSS loading from parsing; move parser into a separate file. --- lib/element-query.js | 120 ++++--------------------------------------- lib/parser.js | 101 ++++++++++++++++++++++++++++++++++++ test/index.html | 1 + 3 files changed, 113 insertions(+), 109 deletions(-) create mode 100644 lib/parser.js diff --git a/lib/element-query.js b/lib/element-query.js index 4cafab0..f046e8e 100644 --- a/lib/element-query.js +++ b/lib/element-query.js @@ -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 = { @@ -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"; @@ -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(); @@ -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(); } @@ -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); @@ -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 @@ -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(); }); @@ -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++) { @@ -249,9 +151,9 @@ var elementQuery = (function() { }, queryMatchers: queryMatchers, - queries: queries, - parser: parser, - + queries: [], + classNameForRules: classNameForRules, + loader: loader }; // re-run all queries on resize diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..87037bf --- /dev/null +++ b/lib/parser.js @@ -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)); \ No newline at end of file diff --git a/test/index.html b/test/index.html index 15aea39..8c976c0 100644 --- a/test/index.html +++ b/test/index.html @@ -47,6 +47,7 @@ --> +