├── test ├── ss │ └── scrollbar.css └── testShims.html ├── src └── cssx │ ├── shim │ ├── ie6Bundle.js │ ├── default.js │ ├── opacity.js │ ├── ieOpacity.js │ ├── inlineBlock.js │ ├── scrollbar.js │ ├── auto.js │ ├── _tests.js │ ├── _bundles.js │ └── boxOffsets.js │ ├── common.js │ ├── stylesheet.js │ ├── sniff.js │ ├── CssDomParser.js │ ├── cssx.js │ ├── css.js │ └── CssTextParser.js ├── package.json └── README /test/ss/scrollbar.css: -------------------------------------------------------------------------------- 1 | .scrolltest { 2 | border-top: -cssx-scrollbar-height solid red; 3 | } 4 | -------------------------------------------------------------------------------- /src/cssx/shim/ie6Bundle.js: -------------------------------------------------------------------------------- 1 | define( 2 | [ 3 | './inlineBlock', 4 | './boxOffsets', 5 | './ieOpacity' 6 | ], 7 | function (inlineBlock, boxOffsets, ieOpacity) { 8 | 9 | return { 10 | inlineBlock: inlineBlock, 11 | boxOffsets: boxOffsets, 12 | ieOpacity: ieOpacity 13 | }; 14 | 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /src/cssx/shim/default.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/default 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | */ 10 | define(['./scrollbar'], function (scrollbar) { 11 | return { 12 | scrollbar: scrollbar 13 | }; 14 | }); 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cssx", 3 | "version": "0.1", 4 | "description": "Extensible CSS Loader", 5 | "licenses": [ 6 | { 7 | "type": "AFLv2.1", 8 | "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L43" 9 | }, 10 | { 11 | "type": "BSD", 12 | "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L13" 13 | } 14 | ], 15 | "directories": { 16 | "lib": "src" 17 | }, 18 | "main": "./src/cssx" 19 | } 20 | -------------------------------------------------------------------------------- /test/testShims.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Run shim Unit Tests 6 | 7 | 14 | 15 | 16 | 17 | 18 | 27 | 28 | 29 | 30 | 31 |
this box has a border on the top
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/cssx/shim/opacity.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/opacity 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | This cssx plugin fixes lack of box offset positioning in IE6. 10 | 11 | */ 12 | define( 13 | ['cssx/sniff'], 14 | function (sniff) { 15 | 16 | return { 17 | 18 | onProperty: function (processor, parseArgs, ctx) { //(/* String */ propName, /* String */ value, /* String|Array */ selectors, /* String */ ss) { 19 | if (parseArgs.propName == 'opacity') { 20 | var rule = { selectors: parseArgs.selectors }; 21 | rule[ctx.propName] = parseArgs.propValue; 22 | processor.addRule(rule); 23 | } 24 | } 25 | }; 26 | 27 | } 28 | ); 29 | 30 | -------------------------------------------------------------------------------- /src/cssx/shim/ieOpacity.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/opacity 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | This cssx plugin fixes lack of box offset positioning in IE6. 10 | 11 | */ 12 | define( 13 | function () { 14 | 15 | return { 16 | 17 | onProperty: function (processor, parseArgs, ctx) { //(/* String */ propName, /* String */ value, /* String|Array */ selectors, /* String */ ss) { 18 | if (parseArgs.propName === 'opacity') { 19 | var decl = 'progid:DXImageTransform.Microsoft.Alpha(Opacity=' + (value * 100) + ')', 20 | rule = { 21 | selectors: parseArgs.selectors, 22 | propValue: decl 23 | }; 24 | rule.propName = ctx.filterName; 25 | processor.appendRule(rule); 26 | } 27 | } 28 | }; 29 | 30 | } 31 | ); 32 | 33 | -------------------------------------------------------------------------------- /src/cssx/shim/inlineBlock.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/inlineBlock 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | This cssx plugin fixes lack of inline-block support in IE6 and IE7 10 | 11 | */ 12 | define( 13 | function () { 14 | 15 | return { 16 | 17 | onProperty: function (processor, parseArgs) { 18 | // processor: the cssx processor in context 19 | // parseArgs: 20 | // propName: String 21 | // value: String 22 | // selectors: String|Array 23 | // sheet: String 24 | if ('inline-block' === parseArgs.propValue && 'display' === parseArgs.propName) { 25 | processor.appendRule([ 26 | {selectors: parseArgs.selectors, propName: 'display', propValue: 'inline'}, 27 | {selectors: parseArgs.selectors, propName: 'zoom', propValue: '1'} 28 | ]); 29 | } 30 | } 31 | 32 | }; 33 | 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /src/cssx/shim/scrollbar.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/scrollbar 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | */ 9 | define( 10 | [ 11 | 'cssx/sniff' 12 | ], 13 | function (sniff) { 14 | 15 | function getSbSize () { 16 | var sbSize = sniff.getScrollbarSize(); 17 | sbSize = { w: sbSize.w + 'px', h: sbSize.h + 'px' }; 18 | getSbSize = function () { return sbSize; }; 19 | return sbSize; 20 | } 21 | 22 | return { 23 | 24 | onProperty: function (processor, parseArgs) { 25 | // processor: the cssx processor in context 26 | // parseArgs: 27 | // propName: String 28 | // value: String 29 | // selectors: String|Array 30 | // sheet: String 31 | if (/-cssx-scrollbar/.test(parseArgs.propValue)) { 32 | processor.appendRule({ 33 | selectors: parseArgs.selectors, 34 | propName: parseArgs.propName, 35 | propValue: parseArgs.propValue === '-cssx-scrollbar-width' ? getSbSize().w : getSbSize().h 36 | }); 37 | } 38 | } 39 | 40 | }; 41 | 42 | } 43 | ); 44 | -------------------------------------------------------------------------------- /src/cssx/shim/auto.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/auto 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | This cssx plugin scans all css properties for referenced plugins and 10 | ensures they are loaded. Plugins are found by checking for the following 11 | pattern: -cssx- 12 | 13 | The following are examples 14 | border-left-width: -cssx-scrollbar-width; <-- loads the scrollbar plugin 15 | -cssx-transition: top 1s ease; <-- loads the transition plugin 16 | 17 | TODO: remove this file 18 | 19 | */ 20 | define( 21 | function () { 22 | 23 | function cssxFinder (str) { 24 | var m = /\s*-cssx-(\w*)/.match(str); 25 | return m && m[1]; 26 | } 27 | 28 | return { 29 | 30 | // isActive can check for processor state (buildtime, domparsing, textparsing, etc) 31 | isActive: function (processor) { return true; }, 32 | 33 | /* event handlers */ 34 | 35 | onProperty: function (processor, parseArgs) { 36 | // processor: the cssx processor in context 37 | // parseArgs: 38 | // propName: String 39 | // value: String 40 | // selectors: String|Array 41 | // sheet: String 42 | var cssxName = cssxFinder(parseArgs.propName) || cssxFinder(parseArgs.propValue); 43 | if (!processor.hasPlugin(cssxName)) { 44 | // getPlugins initializes the plugins, if necessary 45 | processor.getPlugins([cssxName], function (plugins) { 46 | // if plugin is active 47 | if (plugins[0].isActive()) { 48 | // plugin will resolve or reject 49 | plugins[0].onProperty(processor, parseArgs); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | }; 56 | 57 | } 58 | ); 59 | -------------------------------------------------------------------------------- /src/cssx/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/common 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | */ 9 | define(function () { 10 | 11 | var slice = [].slice; 12 | 13 | return { 14 | 15 | isArray: function (a) { 16 | return Object.prototype.toString.call(a) === '[object Array]'; 17 | }, 18 | 19 | every: function (a, cb) { 20 | var e = true, i = 0, len = a.length; 21 | while (i++ < len && e) { 22 | e = cb(a[i], i, a); 23 | } 24 | return e; 25 | }, 26 | 27 | map: function (a, cb) { 28 | var i = 0, len = a.length, m = new Array(len); 29 | while (i++ < len && e) { 30 | m[i] = cb(a[i], i, a); 31 | } 32 | return m; 33 | }, 34 | 35 | camelize: function (str) { 36 | return str.replace(/-./g, function (c) { return c.substr(1).toUpperCase(); }); 37 | }, 38 | 39 | beget: (function () { 40 | function F () {} 41 | return function (proto, props) { 42 | F.prototype = proto; 43 | var o = new F(); 44 | if (props) { 45 | for (var p in props) { 46 | o[p] = props[p]; 47 | } 48 | } 49 | return o; 50 | } 51 | })(), 52 | 53 | partial: function (func) { 54 | // pre-applies arguments to a function 55 | var args = slice.call(arguments, 1); 56 | return function () { 57 | return func.apply(this, args.concat(arguments)); 58 | } 59 | }, 60 | 61 | forin: function (obj, cb) { 62 | // this is a safe for (var p in obj) 63 | for (var p in obj) if (!(p in Object.prototype)) cb(obj[p], p, obj); 64 | }, 65 | 66 | capitalize: function (s) { 67 | // summary: returns the given string, s, with the first char capitalized. 68 | return (s || '').replace(/./, function (c) { return c.toUpperCase(); }) 69 | } 70 | 71 | 72 | }; 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /src/cssx/shim/_tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | cssx/shim/_tests 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | */ 10 | 11 | define({ 12 | 13 | // these are the feature tests needed to determine if cssx shims should be loaded 14 | // each one has a test() function and a plugin (string) property 15 | // the test function is passed three arguments: 16 | // env: Object - has a property, isBuild, which is true during a build 17 | // sniff: Object - the cssx/sniff module, with many sniffing methods 18 | // ctx: Object - a place to store stuff that the shim might need (e.g. vendor prefix) 19 | 20 | inlineBlock: { 21 | test: function (env, sniff) { return sniff.cssValue('display', 'inline-block'); }, 22 | name: './shim/inlineBlock' 23 | }, 24 | 25 | boxOffsets: { 26 | test: function (env, sniff) { 27 | // Note: this is an inference test. A true test would require 28 | // setting top and bottom of an absolutely positioned node and 29 | // verifying the height. 30 | // FIXME: do a true test? 31 | return sniff.cssValue('position', 'fixed'); 32 | }, 33 | name: './shim/boxOffsets' 34 | }, 35 | 36 | opacity: { 37 | // non-ie opacity 38 | test: function (env, sniff, ctx) { 39 | // store the property name (may have vendor prefix) 40 | ctx.propName = sniff.cssProp('opacity', true); 41 | return ctx.propName !== 'opacity'; 42 | }, 43 | name: './shim/opacity' 44 | }, 45 | 46 | ieOpacity: { 47 | test: function (env, sniff, ctx) { 48 | // store the property names (may have vendor prefix) 49 | ctx.opacityName = sniff.cssProp('opacity', true); 50 | ctx.filterName = sniff.cssProp('filter', true); 51 | return ctx.opacityName || !ctx.filterName; 52 | }, 53 | name: './shim/ieOpacity' 54 | }, 55 | 56 | scrollbar: { 57 | test: function () { return false; }, 58 | name: './shim/scrollbar' 59 | } 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /src/cssx/shim/_bundles.js: -------------------------------------------------------------------------------- 1 | /** 2 | cssx/shim/_bundles 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | */ 10 | 11 | /* 12 | 13 | Loading scenario A: normal case 14 | 1) coder specifies the cssx/cssx plugin in a dependency 15 | - cssx.js plugin is loaded 16 | 2) cssx.js plugin figures out the correct bundle (which could be the default bundle) 17 | - default bundle is loaded 18 | 3) each cssx plugin specified in the bundle is loaded and registers with cssx: 19 | - require(['cssx/cssx']).then(function (cssx) { cssx.register(thisPlugin); }); 20 | 21 | Loading scenario B: coder-defined cssx plugin 22 | 1) coder requires her cssx plugin in a module somewhere 23 | - plugin is loaded and calls require(['cssx/cssx']).then() to register 24 | 2) cssx.js plugin is loaded, then detects and loads a bundle, etc. 25 | 26 | */ 27 | 28 | // TODO: is the auto.js plugin needed any more? 29 | 30 | define({ 31 | 32 | ie60: { 33 | test: function (env, sniff) { return /^Mozilla\/4\.0 \(compatible; MSIE 6\.0; Windows NT \d\.\d(.*)\)$/.test(env.userAgent); }, 34 | name: './shim/ie6Bundle' 35 | }, 36 | 37 | ie70: { 38 | test: function (env, sniff) { return /^Mozilla\/4\.0 \(compatible; MSIE 7\.0; Windows NT \d\.\d(.*)\)$/.test(env.userAgent); }, 39 | name: './shim/ie7Bundle' 40 | }, 41 | 42 | ff36: { 43 | test: function (env, sniff) { return /^Mozilla\/5\.0 \(Windows; U;(.*)rv\:1\.9\.2.(\d{1,2})\)( Gecko\/(\d{8}))? Firefox\/3\.6(\.\d{1,2})?( \(.+\))?$/.test(env.userAgent); }, 44 | name: './shim/ff36Bundle' 45 | }, 46 | 47 | cr80: { 48 | test: function (env, sniff) { return /^Mozilla\/5\.0 \((Windows|Macintosh|X11); U;.+\) AppleWebKit\/534\.10 \(KHTML\, like Gecko\) (.+)Chrome\/8\.0\.(\d{3})\.(\d{1,3}) Safari\/534\.10$/.test(env.userAgent); }, 49 | name: './shim/cr8Bundle' 50 | }, 51 | 52 | 'default': { 53 | test: true, 54 | name: './shim/default' // all non-detectable shims 55 | } 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /src/cssx/shim/boxOffsets.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/boxOffsets 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | 9 | This cssx plugin fixes lack of box offset positioning in IE6. 10 | 11 | TODO: the logic in here could be improved a bit 12 | 13 | */ 14 | define( 15 | function () { 16 | 17 | return { 18 | 19 | onProperty: function (processor, parseArgs) { 20 | // processor: the cssx processor in context 21 | // parseArgs: 22 | // propName: String 23 | // value: String 24 | // selectors: String|Array 25 | // sheet: String 26 | 27 | var prop = parseArgs.propName, 28 | value = parseArgs.propValue, 29 | result; 30 | 31 | if (prop === 'bottom' && value !== 'auto') { 32 | // optimize common case in which bottom is in pixels already or is 0 (IE always uses '0px' for '0') 33 | if (value.match(/px$/)) { 34 | result = { 35 | selectors: parseArgs.selectors, 36 | propName: 'height', 37 | propValue: 'expression(cssx_boxOffsets_checkBoxHeight(this, ' + parseInt(value) + '))' 38 | }; 39 | } 40 | else { 41 | result = [ 42 | { 43 | selectors: parseArgs.selectors, 44 | propName: 'height', 45 | propValue: 'expression(cssx_boxOffsets_checkBoxHeight(this))' 46 | }, 47 | { 48 | selectors: parseArgs.selectors, 49 | propName: 'bottom', 50 | propValue:'expression("' + value + '")' 51 | } 52 | ]; 53 | } 54 | } 55 | else if (prop === 'right' && value !== 'auto') { 56 | if (value.match(/px$/)) { 57 | result = { 58 | selectors: parseArgs.selectors, 59 | propName: 'width', 60 | propValue: 'expression(cssx_boxOffsets_checkBoxWidth(this, ' + parseInt(value) + '))' 61 | }; 62 | } 63 | else { 64 | result = [ 65 | { 66 | selectors: parseArgs.selectors, 67 | propName: 'width', 68 | propValue: 'expression(cssx_boxOffsets_checkBoxWidth(this))' 69 | }, 70 | { 71 | selectors: parseArgs.selectors, 72 | propName: 'right', 73 | propValue:'expression("' + value + '")' 74 | } 75 | ]; 76 | } 77 | } 78 | 79 | if (result) { 80 | processor.appendRule(result); 81 | } 82 | 83 | } 84 | 85 | }; 86 | 87 | } 88 | ); 89 | 90 | // it's easiest if these functions are global 91 | 92 | function cssx_boxOffsets_checkBoxHeight (node, bVal) { 93 | var style = node.currentStyle, 94 | parent = node.offsetParent, 95 | doc = node.ownerDocument; 96 | // are we using box offset positioning? (Note: assumes position:fixed is fixed for IE6) 97 | if (parent && style.top != 'auto' && style.position == 'absolute' || style.position == 'fixed') { 98 | var height = parent == doc.body ? doc.body.clientHeight : parent.offsetHeight 99 | - (node.offsetHeight - node.clientHeight) /* border height */ 100 | - parseInt(style.paddingTop)- parseInt(style.paddingBottom) /* padding height if px */; 101 | return height - node.offsetTop - (bVal != null ? bVal : node.style.pixelBottom) + 'px'; 102 | } 103 | else 104 | return ''; 105 | } 106 | 107 | function cssx_boxOffsets_checkBoxWidth (node, rVal) { 108 | var style = node.currentStyle, 109 | parent = node.offsetParent, 110 | doc = node.ownerDocument; 111 | // are we using box offset positioning? (Note: assumes position:fixed is fixed for IE6) 112 | if (parent && style.left != 'auto' && style.position == 'absolute' || style.position == 'fixed') { 113 | var width = (parent == doc.body ? doc.body.clientWidth : parent.offsetWidth) 114 | - (node.offsetWidth - node.clientWidth) /* border width */ 115 | - parseInt(style.paddingLeft)- parseInt(style.paddingRight) /* padding width if px */; 116 | return width - node.offsetLeft - (rVal != null ? rVal : node.style.pixelRight) + 'px'; 117 | } 118 | else 119 | return ''; 120 | } 121 | -------------------------------------------------------------------------------- /src/cssx/stylesheet.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/stylesheet 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | */ 9 | define(function () { 10 | 11 | function findDoc () { 12 | return window['document']; 13 | } 14 | 15 | function findHead (doc) { 16 | // Finds the HEAD element (or the BODY element if the head wasn't 17 | // found). 18 | // doc: DOMDocument (optional) Searches the supplied document, 19 | // or the currently-scoped window.document if omitted. 20 | var node = (doc || findDoc()).documentElement.firstChild; 21 | while (node && (node.nodeType != 1 || !/head|body/i.test(node.tagName))) { 22 | node = node.nextSibling; 23 | } 24 | return node; 25 | } 26 | 27 | function _ss () { 28 | var sheet = createStylesheet(); 29 | return (_ss = function () { return sheet; })(); 30 | } 31 | 32 | function createStylesheet (cssText, position) { 33 | // summary: Creates a new stylesheet so rules may be added. 34 | // cssText: String The initial text content of the stylesheet (i.e. rules in text form) 35 | // description: Do not supply cssText if you plan to add rules via the appendRule method immediately. 36 | // Firefox 3+ temporarily removes the cssRules collection when text content is 37 | // inserted. A setTimeout is required before the cssRules are available again. 38 | 39 | var doc = findDoc(), 40 | head = findHead(); 41 | 42 | return (createStylesheet = 43 | doc.createStyleSheet ? 44 | // IE (hack city) 45 | function (cssText) { 46 | try { 47 | var node = doc.createElement('style'); 48 | node.type = 'text/css'; 49 | head.appendChild(node); 50 | var ss = node.styleSheet; 51 | } 52 | catch (ex) { 53 | // we must have hit 31 stylesheet limit so try the other way: 54 | ss = doc.createStyleSheet(); 55 | } 56 | // IE6 needs to have cssText or the stylesheet won't get created (verify again?) 57 | cssText = cssText || '#cssx_ignore_ {}'; 58 | ss.cssText = cssText; 59 | return ss; 60 | } : 61 | // w3c 62 | function (cssText) { 63 | var node = doc.createElement('style'); 64 | node.type = 'text/css'; 65 | head.appendChild(node); 66 | if (cssText) node.appendChild(doc.createTextNode(cssText)); 67 | return node.sheet; 68 | } 69 | )(); 70 | } 71 | 72 | function appendRule (/* String */ selectorText, /* String */ cssText, /* CSSStylesheet? */ ss) { 73 | // summary: appends a new rule to the end of a stylesheet 74 | // selectorText: String the selector of the new rule 75 | // cssText: String the css property declarations of the new rule 76 | // ss: StyleSheet? if omitted, a default stylesheet is used 77 | return insertRule(selectorText, cssText, -1, ss); 78 | } 79 | 80 | function insertRule (/* String */ selectorText, /* String */ declText, /* Number? */ pos, /* CSSStylesheet? */ ss) { 81 | // summary: inserts a new rule into a stylesheet 82 | // selectorText: String the selector of the new rule 83 | // cssText: String the css property declarations of the new rule 84 | // pos: Number? the position to insert at (or the end if omitted) 85 | // ss: StyleSheet? if omitted, a default stylesheet is used 86 | // special thanks to PPK at http://www.quirksmode.org for his work on stylesheets 87 | ss = ss || _ss(); 88 | var rules = ss.cssRules || ss.rules; 89 | if (ss.insertRule) {// w3c 90 | if (!(pos >= 0)) pos = rules.length; 91 | ss.insertRule(selectorText + '{' + declText + '}', pos); 92 | } 93 | // IE. what a stinkin pile! 94 | else { 95 | if (!declText) declText = 'zoom:1'; /* IE6 throws "Invalid argument." when there's no cssText */ 96 | // addRule fails in IE6 if the selectors are comma-separated 97 | // TODO: FIXME? could there be a comma in a css3 attr selector? 98 | var selectors = selectorText.split(','); 99 | for (var i = 0; i < selectors.length; i++) { 100 | ss.addRule(selectors[i], declText, pos++ || -1); 101 | } 102 | if (!(pos >= 0)) pos = rules.length - 1; 103 | } 104 | return rules[pos]; 105 | } 106 | 107 | return { 108 | createStylesheet: createStylesheet, 109 | insertRule: insertRule, 110 | appendRule: appendRule, 111 | common: function common () { return _ss(); } 112 | 113 | }; 114 | 115 | }); 116 | -------------------------------------------------------------------------------- /src/cssx/sniff.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/sniff 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | */ 9 | define( 10 | [ 11 | './stylesheet', 12 | './common' 13 | ], 14 | function (stylesheet, common) { 15 | 16 | var _vendor, 17 | _testRule, 18 | prefixes = { 19 | 'Moz': '-moz-', // mozilla 20 | 'Webkit': '-webkit-', // webkit 21 | 'O': '-o-', // opera 22 | 'Khtml': '-khtml-', // konqueror 23 | 'Ms': '' // IE is so b0rked (even IE 8) 24 | }, 25 | sbSize, 26 | capitalize = common.capitalize; 27 | 28 | function _supported (propName, node) { 29 | return typeof (node || document.documentElement).style[propName] === 'string'; 30 | } 31 | 32 | // function getPropPrefix (/* String */ propName, /* DOMNode? */ node) { 33 | // // summary: obtains and returns the vendor prefix used for a particular property. 34 | // var prefix; 35 | // return _supported(propName) ? '' : getVendorPrefix(propName, node); 36 | // } 37 | 38 | function getVendorPrefix (/* String */ propName, /* DOMNode? */ node) { 39 | // summary: tries to obtain the vendor prefix if it is used for the given property. 40 | if (_supported(propName)) { 41 | return ''; 42 | } 43 | else { 44 | common.forin(prefixes, function (camel, dash) { 45 | if (_supported(dash + capitalize(propName), node)) { 46 | getVendorPrefix = function () { return dash; }; 47 | return dash; 48 | } 49 | }); 50 | return null; 51 | } 52 | } 53 | 54 | 55 | return { 56 | 57 | prefixes: prefixes, 58 | 59 | cssProp: function (/* String */ propName, /* Boolean? */ checkVendorPrefixes, /* DOMNode? */ node) { 60 | // summary: Checks if a css property is supported by the current browser 61 | // propName: String - the camelCased property name to check 62 | // checkVendorPrefixes: Boolean? - if true, checks for vendor-specific variations 63 | // node: DOMNode? - a dom node to test (checks the body if omitted) 64 | // returns: String - If checkVendorPrefixes is true, returns the actual property 65 | // name, if any. Otherwise, returns true if the property is supported. 66 | // 67 | // example: hasRadius = sniff.cssProp('borderRadius', true); 68 | // inspired by kangax: http://thinkweb2.com/projects/prototype/feature-testing-css-properties/ 69 | // Also see: http://yura.thinkweb2.com/cft/ (common feature tests) 70 | var supported = _supported(propName, node) && propName; 71 | if (!supported && checkVendorPrefixes) { 72 | var pre = getVendorPrefix(propName, node), 73 | prop = pre && (pre + capitalize(propName)); 74 | return (pre && _supported(prop)) ? prop : void 0; 75 | } 76 | else 77 | return supported; 78 | 79 | }, 80 | 81 | cssValue: function (/* String */ propName, /* String */ testValue, /* Boolean? */ checkVendorPrefixes, /* DOMNode? */ node) { 82 | // summary: Checks if a css value is supported by the current browser. 83 | // propName: String - the camelCased property name to check 84 | // testValue: String - the property value to test 85 | // checkVendorPrefixes: Boolean? - if true, checks for vendor-specific variations 86 | // node: DOMNode? - a dom node to test (checks the body if omitted) 87 | // returns: String - If checkVendorPrefixes is true, returns the actual property 88 | // name, if any. Otherwise, returns true if the property is supported. 89 | // Also see: http://ryanmorr.com/archives/detecting-browser-css-style-support 90 | // TODO: check vendor prefixes! 91 | var success = false; 92 | if (!_testRule) 93 | _testRule = stylesheet.appendRule('#cssx_test_rule', ''); 94 | try { 95 | _testRule.style[propName] = testValue; 96 | success = _testRule.style[propName] !== ''; 97 | _testRule.style[propName] = ''; // clean up 98 | } 99 | catch (ex) { /* squelch IE */ } 100 | return success; 101 | }, 102 | 103 | gcsValue: function (/* String */ propName, /* String */ testValue, /* Boolean? */ checkVendorPrefixes, /* DOMNode? */ node) { 104 | // summary: returns true if the browser supports the css property in the getComputedStyle / 105 | // currentStyle collections. be sure to supply a testValue that is not falsy already! (TODO: fix this?) 106 | // TODO: check vendor prefixes 107 | if (!node) { 108 | node = document.body; 109 | } 110 | var result = false, 111 | oldVal = node.style[propName]; 112 | node.style[propName] = testValue; 113 | try { 114 | result = !!(window.getComputedStyle ? window.getComputedStyle(node, null)[propName] : node.currentStyle[propName]); 115 | } 116 | finally { 117 | node.style[propName] = oldVal; 118 | } 119 | return result; 120 | }, 121 | 122 | getScrollbarSize: function () { 123 | // summary: figures out the height and width of the scrollbars on this system. 124 | // something like this exists in dojox, but we don't want to rely on dojox 125 | // Returns an object with w and h properties (width and height, Number) in pixels 126 | if (!sbSize) { 127 | sbSize = {w: 15, h: 15}; // default 128 | var testEl = document.createElement('div'); 129 | testEl.style.cssText = 'width:100px;height:100px;overflow:scroll;bottom:100%;right:100%;position:absolute;visibility:hidden;'; 130 | document.body.appendChild(testEl); 131 | try { 132 | sbSize = { 133 | w: testEl.offsetWidth - Math.max(testEl.clientWidth, testEl.scrollWidth), 134 | h: testEl.offsetHeight - Math.max(testEl.clientHeight, testEl.scrollHeight) 135 | }; 136 | document.body.removeChild(testEl); 137 | } 138 | catch (ex) { 139 | // squelch 140 | } 141 | } 142 | return sbSize; 143 | }, 144 | 145 | getVendorPrefix: function (propName, node) { return getVendorPrefix(propName, node) } 146 | 147 | }; 148 | 149 | } 150 | ); 151 | -------------------------------------------------------------------------------- /src/cssx/CssDomParser.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/CssDomParser 3 | (c) copyright 2010, unscriptable.com 4 | author: john 5 | 6 | LICENSE: see the LICENSE.txt file. If file is missing, this file is subject to the AFL 3.0 7 | license at the following url: http://www.opensource.org/licenses/afl-3.0.php. 8 | */ 9 | define(['./common'], function (common) { 10 | 11 | var every = common.every; 12 | 13 | return function (/* Object */ cb) { 14 | // summary: A fast, flexible CSS event-based DOM parser in 1kB! (minified) 15 | // See also the cssx.cssTextParser! 16 | // cb: Object 17 | // The cb parameter is a configuration object supplying callbacks that are called whenever 18 | // a new object is encountered in the CSS object hierarchy. Return an explicit false (not 19 | // a falsy value) from the callback to terminate processing that branch in the hierarchy. 20 | // For instance, to abort the parsing of the current rule, return false from the onRule 21 | // callback. To abort parsing of the current sheet, return false from the onSheet callback. 22 | // Call the stop() method to stop parsing altogether. The signatures of the callbacks 23 | // are as follows: 24 | // onSheet: function (/* CSSStyleSheet */ ss) {} 25 | // onRule: function (/* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 26 | // onImport: function (/* CSSStyleSheet */ importedSheet, /* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 27 | // onSelector: function (/* String */ selectorText, /* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 28 | // onProperty: function (/* String */ propName, /* String */ value, /* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 29 | // This css parser will only dig into the stylesheet as deeply as you require. If 30 | // you don't supply an onSelector or onProperty callback, it will not process that 31 | // deeply. 32 | // Other properties of cb: 33 | // dontSplit: Boolean. Prevents the parser from spliting compound selectors into 34 | // multiple single selectors in all browsers except IE. See Notes below. 35 | // skipImports: Boolean. If true, the parser will not process any imported stylesheets. 36 | // context: Object. If supplied, runs the callbacks in the context of this object. 37 | // If missing, runs the callbacks in the context of the CssParser instance. 38 | // Notes: 39 | // 1. The selectors are split at the comma in compound selectors, e.g. the selector, 40 | // ".dijitFoo, .dijitBar", results in two onSelector callbacks: one for ".dijitFoo" 41 | // and one for ".dijitBar". If you don't want the selectorText split, supply a truthy 42 | // dontSplit property on the cb parameter. 43 | // 2. IE breaks compound selectors into separate rules. If your stylesheet has a rule with 44 | // the selector, ".dijitFoo, .dijitBar", IE will break it into two rules having only 45 | // one of the pair of selectors, i.e. one with ".dijitFoo" and one with ".dijitBar". 46 | // Both rules will, of course, have the exact same style properties. 47 | // Thanks to PPK for clarification on IE: http://www.quirksmode.org/dom/w3c_css.html 48 | // 3. To obtain the style properties as text from a rule, use the rule's style.cssText 49 | // property. All browsers support this method. 50 | // Example 1: 51 | // var myCallbacks = { 52 | // dontSplit: true, 53 | // onSelector: function (st, r, ss) { console.log(st, r, ss); } 54 | // }; 55 | // (new CssParser(myCallbacks)).parse(); 56 | // Example 2: 57 | // function checkRuleForOpacity (rule, sheet) { 58 | // /* stop all processing if we hit a style property for opacity */ 59 | // if (rule.style.cssText.match(/opacity|rgba/)) 60 | // this.stop(); 61 | // } 62 | // var canceled = !(new CssParser({onRule: checkRuleForOpacity})).parse(); 63 | // 64 | // TODO: onSheet only gets called if a document (or null) is passed into parse() 65 | 66 | var 67 | // context in which to execute callbacks 68 | ctx = cb.context || this, 69 | // flag to detect if user has stopped (c is short for "continue") 70 | c = true, 71 | // preventative measure 72 | undefined; 73 | 74 | this.parse = function (/* DOMDocument|CSSStyleSheet|Array? */ w) { 75 | // summary: Call parse to start parsing the document. 76 | // w: DOMDocument|CSSStyleSheet|Array? - The item(s) to parse. 77 | // Defaults to currently-scoped document (dojo.doc). May be any number of 78 | // documents or stylesheet objects. 79 | // returns Boolean. true == parse was not stopped; false == it was stopped. 80 | c = true; 81 | if (!common.isArray(w)) w = [w || document]; 82 | every(w, function (obj) { 83 | return obj.nodeType == 9 ? doc(obj) : sheet(obj); // 9 == DOMDocument 84 | }); 85 | return c; 86 | }; 87 | 88 | this.stop = function () { 89 | // summary: Call stop() from within a callback to stop parsing. 90 | c = false; 91 | }; 92 | 93 | function doc (/* DOMDocument */ doc) { 94 | every(doc.styleSheets, function (s) { 95 | // Note: c should be checked AFTER the callback. 96 | sheet(s); 97 | return c; 98 | }); 99 | } 100 | 101 | function sheet (/* CSSStyleSheet */ s) { 102 | if (cb.onRule || cb.onImport || cb.onSelector || cb.onProperty || (cb.onSheet && cb.onSheet.call(ctx, s) !== false) && c) 103 | every(s.cssRules || s.rules /* <-- friggin IE! */, function (r) { 104 | // parse if there are callbacks AND the current callback (if any) didn't cancel and 105 | // caller didn't cancel (c == false). Note: c should be checked AFTER the callback. 106 | if (cb.onSelector || cb.onProperty || cb.onImport || (cb.onRule && cb.onRule.call(ctx, r, s) !== false) && c) 107 | rule(r, s); 108 | return c; 109 | }); 110 | } 111 | 112 | function rule (/* CSSStyleRule */ r, /* CSSStyleSheet */ s) { 113 | // if this is an @import 114 | if (r.styleSheet) { 115 | if (!cb.skipImports) { 116 | if (cb.onImport && cb.onImport(r.stylesheet, r, s) !== false && c) 117 | sheet(r.stylesheet); 118 | } 119 | } 120 | // otherwise 121 | else { 122 | var t; 123 | // if there is an onSelector callback 124 | if (cb.onSelector && (/* performance gain and less bytes: */ t = r.selectorText)) 125 | every(cb.dontSplit ? [t] : t.split(/\s*,\s*/g), function (p) { 126 | return cb.onSelector.call(ctx, p, r, s) !== false && c; 127 | }); 128 | // if there is an onProperty callback 129 | if (cb.onProperty) { 130 | // grab the style object and iterate over its properties 131 | t = r.style; 132 | // normal (fast) way 133 | if (t.length !== undefined) 134 | every(t, function (p) { 135 | return cb.onProperty.call(ctx, p, t[p], r, s) !== false && c; 136 | }); 137 | // IE (slow) way 138 | else { 139 | // Note: this regex is overly simple, but won't hurt because we'll catch invalid property names in the next loop 140 | var props = common.map(t.cssText.match(/([\w-]+):/g) || [], function (p) { 141 | return common.camelize(p.substr(0, p.length - 1).toLowerCase()); 142 | }); 143 | every(props, function (p) { 144 | var v = t[p]; // property value 145 | return (v == undefined || cb.onProperty.call(ctx, p, /* convert to string: */ '' + v, r, s) !== false) && c; 146 | }); 147 | } 148 | } 149 | } 150 | } 151 | 152 | } 153 | 154 | }); 155 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | cssx (Cujo Style Sheet eXtender) 2 | 3 | current version: 0.1 4 | Note: all but the css.js plugin is in flux right now. You may use 5 | the css.js file safely. It now works with Chrome 10. 6 | 7 | ---------------------------------------- 8 | 9 | What is cssx? 10 | 11 | Cssx is an AMD-compliant plugin that loads and augments css files. It's 12 | an important part of the cujo web app framework, but can be used 13 | independently of any other cujo micro-library. Cssx only requires an 14 | AMD-compliant module loader, such as RequireJS, Backdraft's loader, or 15 | curl.js (another awesome cujo micro-library). 16 | 17 | If you want to just use the css-loading capabilities of cssx, you can 18 | simply copy the css.js file into your project. It does not rely on any other 19 | files in this repo. css.js requires the use of an AMD-compliant module 20 | loader just like cssx does. More notes about using css.js are in the section 21 | "How do I just use css.js in my RequireJS or curl.js project?" (below). 22 | 23 | ---------------------------------------- 24 | 25 | Why would you want to augment css files? 26 | 27 | Mainly, to provide fixes for browsers that don't support CSS 3 or CSS 2.1. 28 | Cssx has it's own plugins. These plugins modify css in various ways, such as: 29 | 30 | 1) convert opacity and rgba to something that works in IE, e.g.: 31 | opacity:0.1; to filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=10); 32 | 2) implement "box offset positioning" in IE6 33 | (http://www.w3.org/TR/CSS2/visuren.html#position-props) 34 | 3) implement advanced selectors in IE 6&7, e.g.: 35 | input[type=checkbox], .class1.class2, .parent > .child 36 | 4) automatically convert css3 properties to vendor-specific properties, e.g.: 37 | transition:opacity 1s ease; to -webkit-transition:opacity 1s ease; 38 | 5) automatically convert machine-specific measurements, e.g.: 39 | margin-right: -cssx-scrollbar-width; to margin-right: 15px; 40 | 41 | All of the above (and several others) are already implemented and work 42 | dynamically (not statically like when using Selectivizr)! Because cssx is 43 | plugin-based, you can create and add your own cssx plugins. 44 | 45 | ---------------------------------------- 46 | 47 | Doesn't run-time augmentation of css take up valuable load time? 48 | 49 | Yes. It does. But probably not as much as you think. It takes a few 50 | milliseconds to process a reasonably-sized css file. cssx provides several 51 | configuration options to help streamline the parsing process. 52 | 53 | Current work on AMD is focused on building optimized bundles of javascript 54 | for each browser. This is called User Agent Profiling. UA Profiling also 55 | extends to AMD plugins like cssx. UA Profiling will allow css augmentation 56 | to run on the server, rather than in the browser, eliminating the expensive 57 | text parsing. 58 | 59 | cujo.js's build tool, cram (Cujo Resource AsseMbler), is only in the proof- 60 | of-concept stages. We expect it to be ready by mid 2011. 61 | 62 | ---------------------------------------- 63 | 64 | How do I start using cssx? 65 | 66 | To start using cssx, just copy (or clone) the src/cssx folder into your project 67 | and map one of your AMD loader paths to it. There are several ways to map your 68 | loader paths. Go RTFM if you want to get the intimate details. In summary, you 69 | should map your loader's baseUrl to a common folder to all your javascript 70 | modules (including plugins such as cssx), if a common folder exists. Then, 71 | create path configuration options for any module roots that aren't peers within 72 | the baseUrl folder. Here's a simple example: 73 | 74 | // this is just one way to set the configuration for RequireJS or curl.js: 75 | require = { 76 | baseUrl: 'js/', // maps to the js folder that is a peer to this page 77 | paths: { 78 | myApp: 'myCompany/myApp', // maps to js/myCompany/myApp/ 79 | cssx: '../libs/cssx' // maps to libs/cssx 80 | // Note: libs and js are peer folders 81 | } 82 | }; 83 | 84 | Once you've got the paths configured correctly, you can start referencing the 85 | cssx plugin in your code. Typically, you'll do this in define() calls. By 86 | convention, you should invoke the plugin by using a prefix with a complete path 87 | to the plugin. Prefixes are delineated by a ! symbol in the module name. (CSS 88 | files are considered resources by AMD loaders, but have the same syntax as 89 | modules.) 90 | 91 | define( 92 | [ 93 | 'myApp/moduleA', // a javascript module dependency 94 | 'text!templates/myTemplate.html', // example usage of the text plugin 95 | 'cssx/cssx!styles/moduleA.css' // our css file! 96 | ], 97 | function (moduleA, template, cssxDef) { 98 | // code some awesomeness here 99 | } 100 | ); 101 | 102 | In this example, the stylesheet, moduleA.css is loaded, parsed, and augmented 103 | by cssx before the callback function is executed. When the callback function 104 | executes, it is handed an object, cssxDef, which -- except for tinkerers and 105 | the curious -- is not of much use. It contains a reference to the original 106 | element inserted as well as a possible