├── test ├── img │ └── prevq-x.gif ├── ss │ ├── hover.css │ ├── image.css │ ├── selectors.css │ ├── scrollbar.css │ └── blocks.css ├── testComboSelector.html ├── testBoxes.html └── testShims.html ├── src └── cssx │ ├── shim │ ├── default.js │ ├── ieOpacity.js │ ├── opacity.js │ ├── ie6Bundle.js │ ├── inlineBlock.js │ ├── scrollbar.js │ ├── _bundles.js │ ├── minmax.js │ ├── hover.js │ ├── attrSelector.js │ ├── boxOffsets.js │ ├── childSelector.js │ ├── _tests.js │ └── comboSelector.js │ ├── shims.js │ ├── stylesheet.js │ ├── sniff.js │ ├── CssDomParser.js │ ├── css.js │ ├── CssTextParser.js │ └── cssx.js ├── package.json ├── LICENSE.txt └── README /test/img/prevq-x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unscriptable/cssx/HEAD/test/img/prevq-x.gif -------------------------------------------------------------------------------- /test/ss/hover.css: -------------------------------------------------------------------------------- 1 | .special:hover { 2 | font-style: italic; 3 | } 4 | 5 | P:hover .inner { 6 | font-style: italic; 7 | } 8 | /* comment test */ 9 | -------------------------------------------------------------------------------- /test/ss/image.css: -------------------------------------------------------------------------------- 1 | .image-left { 2 | padding-left: 24px; 3 | min-height: 24px; 4 | background: transparent url(../img/prevq-x.gif) no-repeat center left; 5 | } 6 | -------------------------------------------------------------------------------- /test/ss/selectors.css: -------------------------------------------------------------------------------- 1 | .parent > .child { 2 | color: green; 3 | } 4 | 5 | .parent > .parent > .child { 6 | color: red; 7 | } 8 | 9 | .combo.selector { 10 | background-color: purple; 11 | color: yellow; 12 | } 13 | -------------------------------------------------------------------------------- /test/ss/scrollbar.css: -------------------------------------------------------------------------------- 1 | .scrolltest { 2 | opacity: 0.5; 3 | border-style: outset; 4 | border-width: -cssx-scrollbar-height -cssx-scrollbar-width -cssx-scrollbar-height -cssx-scrollbar-width; 5 | border-top: -cssx-scrollbar-height solid red; 6 | } 7 | -------------------------------------------------------------------------------- /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.2", 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/cssx" 17 | }, 18 | "main": "./src/cssx/cssx" 19 | } 20 | -------------------------------------------------------------------------------- /src/cssx/shim/ieOpacity.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/ieOpacity 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 | 14 | opacity: function (prop, value, selectors) { 15 | return 'filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=' + (value * 100) + ');zoom:1;'; 16 | } 17 | 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /test/ss/blocks.css: -------------------------------------------------------------------------------- 1 | .box { 2 | border: 1px solid #ccc; 3 | background-color: #eee; 4 | padding: 5px; 5 | } 6 | 7 | .inline-block-test { 8 | display: inline-block; 9 | width: 100px; 10 | } 11 | 12 | .box-offset-container { 13 | position: relative; 14 | height: 100px; 15 | margin-top: 5px; 16 | } 17 | 18 | .box-offset-test { 19 | background: #ffc; 20 | position: absolute; 21 | top: 5px; 22 | bottom: 5px; 23 | left: 5px; 24 | right: 5px; 25 | } 26 | 27 | .max-height-test { 28 | max-height: 20px; 29 | overflow: hidden; 30 | width: 100px; 31 | } 32 | 33 | .min-height-test { 34 | min-height: 100px; 35 | width: 100px; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /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 | opacity: function (prop, value, selectors) { 19 | return sniff.cssProp(prop, true) + ':' + value + ';'; 20 | } 21 | 22 | }; 23 | 24 | } 25 | ); 26 | 27 | -------------------------------------------------------------------------------- /src/cssx/shim/ie6Bundle.js: -------------------------------------------------------------------------------- 1 | define( 2 | [ 3 | './inlineBlock', 4 | './boxOffsets', 5 | './ieOpacity', 6 | './minmax', 7 | './hover', 8 | './childSelector', 9 | './comboSelector'/*, 10 | './attrSelector'*/ 11 | ], 12 | function (inlineBlock, boxOffsets, ieOpacity, minmax, hover, childSelector, comboSelector/*, attrSelector*/) { 13 | 14 | return { 15 | inlineBlock: inlineBlock, 16 | boxOffsets: boxOffsets, 17 | ieOpacity: ieOpacity, 18 | minmax: minmax, 19 | hoverPseudo: hoverPseudo, 20 | // attrSelector: attrSelector, 21 | childSelector: childSelector, 22 | comboSelector: comboSelector 23 | }; 24 | 25 | } 26 | ); 27 | -------------------------------------------------------------------------------- /test/testComboSelector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |if .combo.selector works, this is purple!
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(function () { 13 | 14 | var inlineBlockRx = /inline-block/; 15 | 16 | return { 17 | 18 | display: function (prop, value, selectors) { 19 | if (inlineBlockRx.test(value)) { 20 | return 'display:inline;zoom:1;'; 21 | } 22 | } 23 | 24 | }; 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Open Source Initiative OSI - The MIT License 2 | 3 | http://www.opensource.org/licenses/mit-license.php 4 | 5 | Copyright (c) 2011 Brian Cavalier 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /test/testBoxes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |park your mouse over me and i should get slanty
67 | 68 |if .combo.selector works, this is yellow on purple! (hover and I'll get slanty, too)
69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /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 | function () { 11 | 12 | var scrollbarPropRx = /-cssx-scrollbar-(width|height)/g; 13 | 14 | // TODO: combine these two functions into one 15 | 16 | function getScrollbarSize () { 17 | // summary: figures out the height and width of the scrollbars on this system. 18 | // something like this exists in dojox, but we don't want to rely on dojox 19 | // Returns an object with w and h properties (width and height, Number) in pixels 20 | var sbSize = {w: 15, h: 15}; // default 21 | var testEl = document.createElement('div'); 22 | testEl.style.cssText = 'width:100px;height:100px;overflow:scroll;bottom:100%;right:100%;position:absolute;visibility:hidden;'; 23 | document.body.appendChild(testEl); 24 | try { 25 | sbSize = { 26 | w: testEl.offsetWidth - Math.max(testEl.clientWidth, testEl.scrollWidth), 27 | h: testEl.offsetHeight - Math.max(testEl.clientHeight, testEl.scrollHeight) 28 | }; 29 | document.body.removeChild(testEl); 30 | } 31 | catch (ex) { 32 | // squelch 33 | } 34 | return sbSize; 35 | } 36 | 37 | function getSbSize () { 38 | var sbSize = getScrollbarSize(); 39 | sbSize = { w: sbSize.w + 'px', h: sbSize.h + 'px' }; 40 | getSbSize = function () { return sbSize; }; 41 | return sbSize; 42 | } 43 | 44 | function replaceScrollbarDim (full, which) { 45 | return which == 'width' ? getSbSize().w : getSbSize().h; 46 | } 47 | 48 | return { 49 | 50 | onValue: function (prop, value, selectors) { 51 | return value.replace(scrollbarPropRx, replaceScrollbarDim); 52 | } 53 | 54 | }; 55 | 56 | } 57 | ); 58 | -------------------------------------------------------------------------------- /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/minmax.js: -------------------------------------------------------------------------------- 1 | /* 2 | cssx/shim/minmax 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 min/max height/width in IE6 and limited support in IE7. 10 | 11 | We're using cssx-min-height (et al) because IE will block min-height (et al) from 12 | currentStyle in many cases. 13 | 14 | */ 15 | (function (global, doc) { 16 | 17 | define({ 18 | 19 | 'max-height': function (prop, value, selectors) { 20 | if (parseFloat(value) > 0) { 21 | return 'height:expression(cssx_minmax_boxHeight(this));\ncssx-max-height:' + value + ';\n'; 22 | } 23 | }, 24 | 25 | 'min-height': function (prop, value, selectors) { 26 | if (parseFloat(value) > 0) { 27 | return 'height:expression(cssx_minmax_boxHeight(this));\ncssx-min-height:' + value + ';\n'; 28 | } 29 | }, 30 | 31 | 'max-width': function (prop, value, selectors) { 32 | if (parseFloat(value) > 0) { 33 | return 'width:expression(cssx_minmax_boxWidth(this));\ncssx-max-width:' + value + ';\n'; 34 | } 35 | }, 36 | 37 | 'min-width': function (prop, value, selectors) { 38 | if (parseFloat(value) > 0) { 39 | return 'width:expression(cssx_minmax_boxWidth(this));\ncssx-min-width:' + value + ';\n'; 40 | } 41 | } 42 | 43 | }); 44 | 45 | var testNode; 46 | 47 | function getPx (refNode, cssValue) { 48 | testNode = doc.createElement('div'); 49 | global.attachEvent && global.attachEvent('unload', function () { testNode = null; }); 50 | getPx = function (refNode, cssValue) { 51 | var result, number = parseFloat(cssValue); 52 | if (isNaN(number)) { 53 | result = 0; 54 | } 55 | else if (/px$/.test(cssValue)) { 56 | result = number; 57 | } 58 | else { 59 | // set font for text-dependent units 60 | testNode.style.fontFamily = refNode.currentStyle.fontFamily; 61 | testNode.style.fontSize = refNode.currentStyle.fontSize; 62 | testNode.style.height = cssValue; 63 | result = testNode.currentStyle ? parseInt(testNode.currentStyle.height) : ''; 64 | } 65 | return result; 66 | }; 67 | return getPx(refNode, cssValue); 68 | } 69 | 70 | // unfortunately, these functions must be global 71 | 72 | global['cssx_minmax_boxHeight'] = function (node) { 73 | var max = node.currentStyle['cssx-max-height'], 74 | min = node.currentStyle['cssx-min-height']; 75 | return Math.max(minPx = min ? getPx(node, min) : 0, Math.min(max ? getPx(node, max) : Infinity, node.scrollHeight)) + 'px'; 76 | }; 77 | 78 | global['cssx_minmax_boxWidth'] = function (node, maxVal, minVal) { 79 | var max = node.currentStyle['cssx-max-width'], 80 | min = node.currentStyle['cssx-min-width']; 81 | return Math.max(minPx = min ? getPx(node, min) : 0, Math.min(max ? getPx(node, max) : Infinity, node.scrollHeight)) + 'px'; 82 | }; 83 | 84 | }(this, document)); 85 | -------------------------------------------------------------------------------- /src/cssx/shim/hover.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 unscriptable.com 3 | * 4 | * TODO: documentation 5 | * TODO: allow for nested rules (LESS, SASS, XStyle) 6 | * 7 | */ 8 | 9 | (function (global, doc) { 10 | 11 | var replacements, 12 | id = 0, 13 | parseHoverRx = /([^\s])+:hover(?=$|\s)/, 14 | // Note: this is safe if we assume nobody will create their own 15 | // classes that include cssx-hover-pseudo within them 16 | removeReplacementRx = /\s?cssx-hover-pseudo-\d+/g; 17 | 18 | function createKey () { 19 | return 'cssx-hover-pseudo-' + id++; 20 | } 21 | 22 | function install () { 23 | doc.attachEvent('onmouseover', enter); 24 | doc.attachEvent('onmouseout', leave); 25 | } 26 | 27 | function remove () { 28 | doc.detachEvent('onmouseover', enter); 29 | doc.detachEvent('onmouseout', leave); 30 | global.detachEvent('unload', remove); 31 | } 32 | 33 | function findHoverKeyAndNode (node) { 34 | var hoverKey; 35 | do { 36 | hoverKey = node.currentStyle['cssx_hover_pseudo']; 37 | } 38 | while (!hoverKey && (node = node.parentNode) && node.nodeType == 1); 39 | return {node: node, hoverKey: hoverKey}; 40 | } 41 | 42 | function enter () { 43 | var e = window.event, 44 | search = findHoverKeyAndNode(e.toElement); 45 | if (search.hoverKey) { 46 | search.node.className += ' ' + search.hoverKey; 47 | } 48 | } 49 | 50 | function leave () { 51 | var e = window.event, 52 | search = findHoverKeyAndNode(e.fromElement); 53 | if (search.hoverKey) { 54 | search.node.className = search.node.className.replace(removeReplacementRx, ''); 55 | } 56 | } 57 | 58 | define({ 59 | 60 | onSelector: function (selector) { 61 | 62 | if (selector.indexOf(':hover')) { 63 | 64 | replacements = replacements || []; 65 | 66 | // parse selector 67 | selector = selector.replace(parseHoverRx, function (match) { 68 | // create unique key for this :hover replacement 69 | // and substitute it as a class instead 70 | var key = createKey(), 71 | partial = match.replace(':hover', ''), 72 | newPart = partial + '.' + key; 73 | // save replacement 74 | replacements.push({ 75 | partial: partial, 76 | key: key 77 | }); 78 | 79 | return newPart; 80 | 81 | }); 82 | return selector; 83 | 84 | } 85 | }, 86 | 87 | onEndRule: function () { 88 | var i, output = '', replacement; 89 | 90 | if (replacements) { 91 | 92 | // for each captured selector 93 | for (i = 0; i < replacements.length; i++) { 94 | 95 | replacement = replacements[i]; 96 | 97 | // add a new rule that inserts a custom property 98 | output += replacement.partial + '{cssx_hover_pseudo:' + replacement.key + ';}'; 99 | 100 | } 101 | 102 | replacements = null; 103 | } 104 | 105 | return output; 106 | } 107 | 108 | }); 109 | 110 | install(); 111 | global.attachEvent('unload', remove); 112 | 113 | }(this, document)); 114 | -------------------------------------------------------------------------------- /src/cssx/shim/attrSelector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 unscriptable.com 3 | * 4 | * TODO: documentation 5 | * TODO: allow for nested rules (LESS, SASS, XStyle) 6 | * TODO: reuse toggleClass and templatize 7 | * 8 | */ 9 | 10 | (function (global, doc) { 11 | 12 | var 13 | replacements, 14 | id = 0, 15 | attrDetectorRx = /\[/g, 16 | attrFinderRx = /(.*?)\[([^\]]+)\]/g, 17 | attrSplitterRx = /(\w+)=["']?([^"']+)["']/g, 18 | ruleTemplate = '${0}{${1}:expression(cssx_attr_selector_check(this, "${1}","${2}","${3}"));}\n'; 19 | 20 | function createKey () { 21 | return 'cssx-attr-selector-' + id++; 22 | } 23 | 24 | var replaceRx = /\$\{(\d)\}/g; 25 | function templatize (string, values) { 26 | return string.replace(replaceRx, function (match, pos) { 27 | return values[pos]; 28 | }); 29 | } 30 | 31 | define({ 32 | 33 | onSelector: function (selector) { 34 | 35 | if (attrDetectorRx.test(selector)) { 36 | 37 | replacements = replacements || []; 38 | 39 | selector = selector.replace(attrFinderRx, function (match, base, attrDef) { 40 | var key = createKey(), 41 | newPart = base + '.' + key ; 42 | 43 | replacements.push({ 44 | base: base, 45 | attrDef: attrDef, 46 | key: key 47 | }); 48 | return newPart; 49 | }); 50 | //alert('new selector: ' + selector); 51 | return selector; 52 | } 53 | 54 | }, 55 | 56 | onEndRule: function () { 57 | var i, part, baseRule = '', rules = ''; 58 | 59 | if (replacements) { 60 | 61 | for (i = 0; i < replacements.length; i++) { 62 | 63 | part = replacements[i]; 64 | part.attrDef.replace(attrSplitterRx, function (match, attr, value) { 65 | //alert('attr: ' + attr + ' value: ' + value); 66 | rules += templatize(ruleTemplate, [part.base, part.key, attr, value]); 67 | return ''; // minimizes memory allocation work 68 | }); 69 | 70 | } 71 | 72 | baseRule += '\n' + rules; 73 | } 74 | 75 | // clean up replacements 76 | replacements = null; 77 | //if (baseRule) alert(baseRule); 78 | return baseRule; 79 | 80 | } 81 | 82 | }); 83 | 84 | // TODO: remove this and replace it with something simpler 85 | function toggleClass (node, className, add) { 86 | var replaced, newClassName, replaceRx = new RegExp('\\s?' + className + '\\s?'); 87 | newClassName = node.className.replace(replaceRx, function (match) { 88 | replaced = add; 89 | return add ? match : ''; 90 | }); 91 | newClassName += add && !replaced ? ' ' + className : ''; 92 | // IE6 isn't smart enough to check if className actually changed 93 | if (node.className != newClassName) { 94 | node.className = newClassName; 95 | } 96 | } 97 | 98 | global['cssx_attr_selector_check'] = function (node, proxyClass, attr, value) { 99 | var valid = attr && node.getAttribute(attr) == value; 100 | //alert(valid + ' ' + attr + ' '+ value); 101 | toggleClass(node, proxyClass, valid); 102 | return ''; 103 | }; 104 | 105 | }(this, document)); 106 | -------------------------------------------------------------------------------- /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 the global functions may be improved a bit 12 | 13 | */ 14 | (function (global) { 15 | 16 | define({ 17 | 18 | bottom: function (prop, value, selectors) { 19 | if (value != 'auto') { 20 | // optimize common case in which bottom is in pixels already or is 0 (IE always uses '0px' for '0') 21 | if (value.match(/px$/)) { 22 | return 'height:expression(cssx_boxOffsets_checkBoxHeight(this,' + parseInt(value) + '));\nbottom:' + value + ';\n'; 23 | } 24 | else { 25 | return 'height:expression(cssx_boxOffsets_checkBoxHeight(this));\nbottom:expression("' + value + '");\n'; 26 | } 27 | } 28 | }, 29 | 30 | right: function (prop, value, selectors) { 31 | if (value != 'auto') { 32 | // optimize common case in which right is in pixels already or is 0 (IE always uses '0px' for '0') 33 | if (value.match(/px$/)) { 34 | return 'width:expression(cssx_boxOffsets_checkBoxWidth(this,' + parseInt(value) + '));\nright:' + value + ';\n'; 35 | } 36 | else { 37 | return 'width:expression(cssx_boxOffsets_checkBoxWidth(this));\nright:expression("' + value + '");\n'; 38 | } 39 | } 40 | } 41 | 42 | }); 43 | 44 | // unfortunately, these functions must be global 45 | 46 | global['cssx_boxOffsets_checkBoxHeight'] = function (node, bVal) { 47 | var style = node.currentStyle, 48 | parent = node.offsetParent, 49 | doc = node.ownerDocument; 50 | // are we using box offset positioning? (Note: assumes position:fixed is fixed for IE6) 51 | if (parent && style.top != 'auto' && style.position == 'absolute' || style.position == 'fixed') { 52 | var height = parent == doc.body ? doc.body.clientHeight : parent.offsetHeight 53 | - (node.offsetHeight - node.clientHeight) /* border height */ 54 | - parseInt(style.paddingTop)- parseInt(style.paddingBottom) /* padding height if px */; 55 | return height - node.offsetTop - (bVal != null ? bVal : node.style.pixelBottom) + 'px'; 56 | } 57 | else 58 | return ''; 59 | }; 60 | 61 | global['cssx_boxOffsets_checkBoxWidth'] = function (node, rVal) { 62 | var style = node.currentStyle, 63 | parent = node.offsetParent, 64 | doc = node.ownerDocument; 65 | // are we using box offset positioning? (Note: assumes position:fixed is fixed for IE6) 66 | if (parent && style.left != 'auto' && style.position == 'absolute' || style.position == 'fixed') { 67 | var width = (parent == doc.body ? doc.body.clientWidth : parent.offsetWidth) 68 | - (node.offsetWidth - node.clientWidth) /* border width */ 69 | - parseInt(style.paddingLeft)- parseInt(style.paddingRight) /* padding width if px */; 70 | return width - node.offsetLeft - (rVal != null ? rVal : node.style.pixelRight) + 'px'; 71 | } 72 | else 73 | return ''; 74 | } 75 | 76 | }(this)); 77 | -------------------------------------------------------------------------------- /src/cssx/shim/childSelector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 unscriptable.com 3 | * 4 | * TODO: documentation 5 | * TODO: process selector replacements in onSelector so they can be cascaded to other selector processors 6 | * TODO: allow for nested rules (LESS, SASS, XStyle) 7 | * 8 | */ 9 | 10 | (function (global, doc) { 11 | 12 | var ancestries, id = 0, 13 | ancestrySplitterRx = /\s*>\s*/g, 14 | rightTemplate = '${0}{${1}:expression(cssx_child_selector_right(this,"${1}","${2}"));}\n', 15 | levelTemplate = '${0}{${1}:expression(cssx_child_selector(this,"${1}","${2}"));}\n', 16 | leftTemplate = '${0}{${1}:${1}}'; 17 | 18 | function createKey () { 19 | return 'cssx-child-selector-' + id++; 20 | } 21 | 22 | var replaceRx = /\$\{(\d)\}/g; 23 | function replace (string, values) { 24 | return string.replace(replaceRx, function (match, pos) { 25 | return values[pos]; 26 | }); 27 | } 28 | 29 | define({ 30 | 31 | onSelector: function (selector) { 32 | 33 | if (selector.indexOf('>') >= 0) { 34 | 35 | // create unique key for this ancestry 36 | var key = createKey(); 37 | 38 | // save ancestry 39 | ancestries = ancestries || []; 40 | ancestries.push({ selector: selector, key: key }); 41 | 42 | return '.' + key; 43 | } 44 | 45 | }, 46 | 47 | onEndRule: function (selectors) { 48 | var i, j, ancestry, level, parentKey, childKey, output = ''; 49 | 50 | if (ancestries && ancestries.length > 0) { 51 | 52 | for (i = 0; i < ancestries.length; i++) { 53 | 54 | // TODO: bail if any blanks were found in ancestry 55 | ancestry = ancestries[i].selector.split(ancestrySplitterRx); 56 | childKey = ancestries[i].key; 57 | 58 | for (j = ancestry.length - 1; j > 0; j--) { 59 | 60 | level = ancestry[j]; 61 | 62 | parentKey = createKey(); 63 | 64 | if (j == ancestry.length - 1) { 65 | // create right-most rule 66 | output += replace(rightTemplate, [level, childKey, parentKey]); 67 | } 68 | else { 69 | output += replace(levelTemplate, [level, childKey, parentKey]); 70 | } 71 | 72 | childKey = parentKey; 73 | 74 | } 75 | 76 | // create left-most rule 77 | output += replace(leftTemplate, [ancestry[0], parentKey]); 78 | 79 | } 80 | } 81 | 82 | // clean up ancestries 83 | ancestries = null; 84 | 85 | return output; 86 | } 87 | 88 | }); 89 | 90 | // TODO: remove this and replace it with something simpler 91 | function toggleClass (node, className, add) { 92 | var replaced, newClassName, replaceRx = new RegExp('\s?' + className + '\s?'); 93 | newClassName = node.className.replace(replaceRx, function (match) { 94 | replaced = add; 95 | return add ? match : ' '; 96 | }); 97 | newClassName += add && !replaced ? ' ' + className : ''; 98 | // IE6 isn't smart enough to check if className actually changed 99 | if (node.className != newClassName) { 100 | node.className = newClassName; 101 | } 102 | } 103 | 104 | global['cssx_child_selector'] = function (node, childKey, parentKey) { 105 | var parent = node.parentNode; 106 | return parent && parent.currentStyle[parentKey] == parentKey ? childKey : ''; 107 | }; 108 | 109 | global['cssx_child_selector_right'] = function (node, origKey, parentKey) { 110 | var parent = node.parentNode; 111 | toggleClass(node, origKey, parent && parent.currentStyle[parentKey] == parentKey); 112 | }; 113 | 114 | }(this, document)); 115 | -------------------------------------------------------------------------------- /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 | /***** properties and vaues *****/ 21 | 22 | minmax: { 23 | test: function (env, sniff) { 24 | return sniff.cssProp('maxWidth'); 25 | }, 26 | name: './shim/minmax' 27 | }, 28 | 29 | opacity: { 30 | // non-ie opacity 31 | test: function (env, sniff, ctx) { 32 | // store the property name (may have vendor prefix) 33 | ctx.opacityName = sniff.cssProp('opacity', true); 34 | ctx.filterName = sniff.cssProp('filter', true); 35 | return ctx.opacityName == 'opacity' || ctx.filterName; 36 | }, 37 | name: './shim/opacity' 38 | }, 39 | 40 | ieOpacity: { 41 | test: function (env, sniff, ctx) { 42 | // store the property names (may have vendor prefix) 43 | ctx.opacityName = sniff.cssProp('opacity', true); 44 | ctx.filterName = sniff.cssProp('filter', true); 45 | return ctx.opacityName || !ctx.filterName; 46 | }, 47 | name: './shim/ieOpacity' 48 | }, 49 | 50 | inlineBlock: { 51 | test: function (env, sniff) { 52 | // Note: this is an inference test. A true test would require 53 | // setting height of an inline-block node and 54 | // verifying the height which would need to wait for domready. 55 | // FIXME: do a true test? 56 | return sniff.cssProp('maxWidth'); 57 | }, 58 | name: './shim/inlineBlock' 59 | }, 60 | 61 | boxOffsets: { 62 | test: function (env, sniff) { 63 | // Note: this is an inference test. A true test would require 64 | // setting top and bottom of an absolutely positioned node and 65 | // verifying the height which would need to wait for domready. 66 | // FIXME: do a true test? 67 | return sniff.cssProp('maxWidth'); 68 | }, 69 | name: './shim/boxOffsets' 70 | }, 71 | 72 | scrollbar: { 73 | test: function () { return false; }, 74 | name: './shim/scrollbar' 75 | }, 76 | 77 | /***** selectors *****/ 78 | 79 | hoverPseudo: { 80 | test: function (env, sniff) { 81 | // Note: this is an inference test. 82 | // FIXME: do a true test? 83 | return sniff.cssProp('maxWidth'); 84 | }, 85 | name: './shim/hover' 86 | }, 87 | 88 | // TODO: get attrSelector working 89 | //attrSelector: { 90 | // test: function (env, sniff) { 91 | // // Note: this is an inference test. 92 | // // FIXME: do a true test? 93 | // return sniff.cssProp('maxWidth'); 94 | // }, 95 | // name: './shim/attrSelector' 96 | //}, 97 | 98 | comboSelector: { 99 | test: function (env, sniff) { 100 | // Note: this is an inference test. 101 | // FIXME: do a true test? 102 | return sniff.cssProp('maxWidth'); 103 | }, 104 | name: './shim/comboSelector' 105 | }, 106 | 107 | // Note: child selector shim needs to run after other shims because it 108 | // removes the selectors entirely and replaces them with a new one. 109 | // Fix this somehow? 110 | childSelector: { 111 | test: function (env, sniff) { 112 | // Note: this is an inference test. 113 | // FIXME: do a true test? 114 | return sniff.cssProp('maxWidth'); 115 | }, 116 | name: './shim/childSelector' 117 | } 118 | 119 | }); 120 | -------------------------------------------------------------------------------- /src/cssx/shim/comboSelector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 unscriptable.com 3 | * 4 | * TODO: documentation 5 | * TODO: allow for nested rules (LESS, SASS, XStyle) 6 | * TODO: reuse toggleClass 7 | * 8 | */ 9 | 10 | (function (global, doc) { 11 | 12 | var 13 | comboCheckers = [], 14 | replacements, 15 | id = 0, 16 | comboDetectorRx = /\.[\w\-]+\./g, 17 | comboFinderRx = /(.*?)((?:\.[\w\-]+){2,})/g, 18 | comboSplitterRx = /\b(\w|-)+\b/g, 19 | ruleTemplate = '.${0}{${1}:expression(cssx_combo_selector_check(this,"${2}",${3}));}\n'; 20 | 21 | function createKey () { 22 | return 'cssx-combo-selector-' + id++; 23 | } 24 | 25 | var replaceRx = /\$\{(\d)\}/g; 26 | function templatize (string, values) { 27 | return string.replace(replaceRx, function (match, pos) { 28 | return values[pos]; 29 | }); 30 | } 31 | 32 | function createComboChecker (combo) { 33 | var classes = {}, classList = [], checker, index = 0; 34 | 35 | combo.replace(comboSplitterRx, function (className) { 36 | if (!classes[className]) { 37 | classes[className] = Math.pow(2, index++); 38 | classList.push(className); 39 | } 40 | return ''; // minimizes memory allocation work 41 | }); 42 | 43 | // IE is such a cluster ____. Can't seem to get any single-pass 44 | // regex to work without capturing an starting space so we have 45 | // to post-process the matched strings. performance fail! 46 | 47 | checker = { 48 | classes: classes, 49 | full: Math.pow(2, index) - 1, 50 | rx: new RegExp('(^|\\s)(' + classList.join('|') + ')(?=$|\\s)', 'g'), 51 | check: function (classes) { 52 | var accum = 0, map = this.classes; 53 | classes.replace(this.rx, function (className) { 54 | // here's the post-processing needed: 55 | className = className.replace(/^\s/, ''); 56 | accum |= (map[className] || 0); 57 | return className; // minimizes memory allocation work 58 | }); 59 | return this.full == accum; 60 | } 61 | }; 62 | return comboCheckers.push(checker) - 1; 63 | } 64 | 65 | function checkComboChecker (classes, checkerId) { 66 | var checker = comboCheckers[checkerId]; 67 | return checker && checker.check(classes); 68 | } 69 | 70 | define({ 71 | 72 | onSelector: function (selector) { 73 | 74 | if (comboDetectorRx.test(selector)) { 75 | 76 | replacements = replacements || []; 77 | 78 | selector = selector.replace(comboFinderRx, function (match, other, combo) { 79 | var key = createKey(), 80 | newPart = other + '.' + key ; 81 | replacements.push({ 82 | other: other, 83 | combo: combo, 84 | key: key 85 | }); 86 | return newPart; 87 | }); 88 | 89 | return selector; 90 | } 91 | 92 | }, 93 | 94 | onEndRule: function () { 95 | var i, part, checkerId, baseKey, baseRule = '', rules = ''; 96 | 97 | if (replacements) { 98 | 99 | for (i = 0; i < replacements.length; i++) { 100 | 101 | // TODO: bail if any blanks were found in classes 102 | //parts = parseCombos(replacements[i].selector); 103 | part = replacements[i]; 104 | baseKey = part.key; 105 | baseRule += part.other; 106 | checkerId = createComboChecker(part.combo); 107 | part.combo.replace(comboSplitterRx, function (className) { 108 | rules += templatize(ruleTemplate, [className, part.key, baseKey, checkerId]); 109 | return ''; // minimizes memory allocation work 110 | }); 111 | baseRule += '.' + part.key + ','; 112 | 113 | } 114 | 115 | baseRule += '\n' + rules; 116 | } 117 | 118 | // clean up replacements 119 | replacements = null; 120 | 121 | return baseRule; 122 | 123 | } 124 | 125 | }); 126 | 127 | // TODO: remove this and replace it with something simpler 128 | function toggleClass (node, className, add) { 129 | var replaced, newClassName, replaceRx = new RegExp('\\s?' + className + '\\s?'); 130 | newClassName = node.className.replace(replaceRx, function (match) { 131 | replaced = add; 132 | return add ? match : ''; 133 | }); 134 | newClassName += add && !replaced ? ' ' + className : ''; 135 | // IE6 isn't smart enough to check if className actually changed 136 | if (node.className != newClassName) { 137 | node.className = newClassName; 138 | } 139 | } 140 | 141 | global['cssx_combo_selector_check'] = function (node, origClass, checkerId) { 142 | var allSatisfied = checkComboChecker(node.className, checkerId); 143 | toggleClass(node, origClass, allSatisfied); 144 | }; 145 | 146 | }(this, document)); 147 | -------------------------------------------------------------------------------- /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 | TODO: start using has() 10 | 11 | */ 12 | define( 13 | [ 14 | './stylesheet' 15 | ], 16 | function (stylesheet) { 17 | 18 | var _vendor, 19 | _testRule, 20 | prefixes = { 21 | 'Moz': '-moz-', // mozilla 22 | 'Webkit': '-webkit-', // webkit 23 | 'O': '-o-', // opera 24 | 'Khtml': '-khtml-', // konqueror 25 | 'Ms': '' // IE is so b0rked (even IE 8) 26 | }, 27 | sbSize; 28 | 29 | function capitalize (s) { 30 | // summary: returns the given string, s, with the first char capitalized. 31 | return (s || '').replace(/./, function (c) { return c.toUpperCase(); }) 32 | } 33 | 34 | function _propSupported (propName, node) { 35 | return typeof (node || document.documentElement).style[propName] == 'string'; 36 | } 37 | 38 | // function getPropPrefix (/* String */ propName, /* DOMNode? */ node) { 39 | // // summary: obtains and returns the vendor prefix used for a particular property. 40 | // var prefix; 41 | // return _supported(propName) ? '' : getVendorPrefix(propName, node); 42 | // } 43 | 44 | function getVendorPrefix (/* String */ propName, /* DOMNode? */ node) { 45 | // summary: tries to obtain the vendor prefix if it is used for the given property. 46 | if (_propSupported(propName)) { 47 | return ''; 48 | } 49 | else { 50 | for (var camel in prefixes) { 51 | if (_propSupported(camel + capitalize(propName), node)) { 52 | var dash = prefixes[camel]; 53 | getVendorPrefix = function () { return dash; }; 54 | return dash; 55 | } 56 | } 57 | return null; 58 | } 59 | } 60 | 61 | function cssProp (/* String */ propName, /* Boolean? */ checkVendorPrefixes, /* DOMNode? */ node) { 62 | // summary: Checks if a css property is supported by the current browser 63 | // propName: String - the camelCased property name to check 64 | // checkVendorPrefixes: Boolean? - if true, checks for vendor-specific variations 65 | // node: DOMNode? - a dom node to test (checks the body if omitted) 66 | // returns: String - If checkVendorPrefixes is true, returns the actual property 67 | // name, if any. Otherwise, returns true if the property is supported. 68 | // 69 | // example: hasRadius = sniff.cssProp('borderRadius', true); 70 | // inspired by kangax: http://thinkweb2.com/projects/prototype/feature-testing-css-properties/ 71 | // Also see: http://yura.thinkweb2.com/cft/ (common feature tests) 72 | var supported = _propSupported(propName, node) && propName; 73 | if (!supported && checkVendorPrefixes) { 74 | var pre = getVendorPrefix(propName, node), 75 | prop = pre && (pre + capitalize(propName)); 76 | return (pre && _propSupported(prop)) ? prop : void 0; 77 | } 78 | else 79 | return supported; 80 | 81 | } 82 | 83 | function cssValue (/* String */ propName, /* String */ testValue, /* Boolean? */ checkVendorPrefixes, /* DOMNode? */ node) { 84 | // summary: Checks if a css value is supported by the current browser. 85 | // propName: String - the camelCased property name to check 86 | // testValue: String - the property value to test 87 | // checkVendorPrefixes: Boolean? - if true, checks for vendor-specific variations 88 | // node: DOMNode? - a dom node to test (checks the body if omitted) 89 | // returns: String - If checkVendorPrefixes is true, returns the actual property 90 | // name, if any. Otherwise, returns true if the property is supported. 91 | // Also see: http://ryanmorr.com/archives/detecting-browser-css-style-support 92 | // TODO: check vendor prefixes! 93 | // NOTE: IE will always pass this test, so this is useless in IE! aarrrggghghhh 94 | var success = false; 95 | if (!_testRule) 96 | _testRule = stylesheet.appendRule('#cssx_test_rule', ''); 97 | try { 98 | _testRule.style[propName] = testValue; 99 | success = _testRule.style[propName] !== ''; 100 | _testRule.style[propName] = ''; // clean up 101 | } 102 | catch (ex) { /* squelch IE */ } 103 | return success; 104 | } 105 | 106 | function gcsValue (/* String */ propName, /* String */ testValue, /* Boolean? */ checkVendorPrefixes, /* DOMNode? */ node) { 107 | // summary: returns true if the browser supports the css property in the getComputedStyle / 108 | // currentStyle collections. be sure to supply a testValue that is not falsy already! (TODO: fix this?) 109 | // TODO: check vendor prefixes 110 | if (!node) { 111 | node = document.body; 112 | } 113 | var result = false, 114 | oldVal = node.style[propName]; 115 | node.style[propName] = testValue; 116 | try { 117 | result = !!(window.getComputedStyle ? window.getComputedStyle(node, null)[propName] : node.currentStyle[propName]); 118 | } 119 | finally { 120 | node.style[propName] = oldVal; 121 | } 122 | return result; 123 | } 124 | 125 | function getScrollbarSize () { 126 | // summary: figures out the height and width of the scrollbars on this system. 127 | // something like this exists in dojox, but we don't want to rely on dojox 128 | // Returns an object with w and h properties (width and height, Number) in pixels 129 | if (!sbSize) { 130 | sbSize = {w: 15, h: 15}; // default 131 | var testEl = document.createElement('div'); 132 | testEl.style.cssText = 'width:100px;height:100px;overflow:scroll;bottom:100%;right:100%;position:absolute;visibility:hidden;'; 133 | document.body.appendChild(testEl); 134 | try { 135 | sbSize = { 136 | w: testEl.offsetWidth - Math.max(testEl.clientWidth, testEl.scrollWidth), 137 | h: testEl.offsetHeight - Math.max(testEl.clientHeight, testEl.scrollHeight) 138 | }; 139 | document.body.removeChild(testEl); 140 | } 141 | catch (ex) { 142 | // squelch 143 | } 144 | } 145 | return sbSize; 146 | } 147 | 148 | return { 149 | 150 | prefixes: prefixes, 151 | 152 | cssProp: cssProp, 153 | 154 | cssValue: cssValue, 155 | 156 | gcsValue: gcsValue, 157 | 158 | getScrollbarSize: getScrollbarSize, 159 | 160 | getVendorPrefix: getVendorPrefix 161 | 162 | }; 163 | 164 | } 165 | ); 166 | -------------------------------------------------------------------------------- /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(function () { 10 | 11 | function every (a, cb) { 12 | var e = true, i, len = a.length; 13 | for (i = 0; i < len && e; i++) { 14 | e = cb(a[i], i, a); 15 | } 16 | return e; 17 | } 18 | 19 | return function (/* Object */ cb) { 20 | // summary: A fast, flexible CSS event-based DOM parser in 1kB! (minified) 21 | // See also the cssx.cssTextParser! 22 | // cb: Object 23 | // The cb parameter is a configuration object supplying callbacks that are called whenever 24 | // a new object is encountered in the CSS object hierarchy. Return an explicit false (not 25 | // a falsy value) from the callback to terminate processing that branch in the hierarchy. 26 | // For instance, to abort the parsing of the current rule, return false from the onRule 27 | // callback. To abort parsing of the current sheet, return false from the onSheet callback. 28 | // Call the stop() method to stop parsing altogether. The signatures of the callbacks 29 | // are as follows: 30 | // onSheet: function (/* CSSStyleSheet */ ss) {} 31 | // onRule: function (/* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 32 | // onImport: function (/* CSSStyleSheet */ importedSheet, /* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 33 | // onSelector: function (/* String */ selectorText, /* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 34 | // onProperty: function (/* String */ propName, /* String */ value, /* CSSStyleRule */ rule, /* CSSStyleSheet */ ss) {} 35 | // This css parser will only dig into the stylesheet as deeply as you require. If 36 | // you don't supply an onSelector or onProperty callback, it will not process that 37 | // deeply. 38 | // Other properties of cb: 39 | // dontSplit: Boolean. Prevents the parser from spliting compound selectors into 40 | // multiple single selectors in all browsers except IE. See Notes below. 41 | // skipImports: Boolean. If true, the parser will not process any imported stylesheets. 42 | // context: Object. If supplied, runs the callbacks in the context of this object. 43 | // If missing, runs the callbacks in the context of the CssParser instance. 44 | // Notes: 45 | // 1. The selectors are split at the comma in compound selectors, e.g. the selector, 46 | // ".dijitFoo, .dijitBar", results in two onSelector callbacks: one for ".dijitFoo" 47 | // and one for ".dijitBar". If you don't want the selectorText split, supply a truthy 48 | // dontSplit property on the cb parameter. 49 | // 2. IE breaks compound selectors into separate rules. If your stylesheet has a rule with 50 | // the selector, ".dijitFoo, .dijitBar", IE will break it into two rules having only 51 | // one of the pair of selectors, i.e. one with ".dijitFoo" and one with ".dijitBar". 52 | // Both rules will, of course, have the exact same style properties. 53 | // Thanks to PPK for clarification on IE: http://www.quirksmode.org/dom/w3c_css.html 54 | // 3. To obtain the style properties as text from a rule, use the rule's style.cssText 55 | // property. All browsers support this method. 56 | // Example 1: 57 | // var myCallbacks = { 58 | // dontSplit: true, 59 | // onSelector: function (st, r, ss) { console.log(st, r, ss); } 60 | // }; 61 | // (new CssParser(myCallbacks)).parse(); 62 | // Example 2: 63 | // function checkRuleForOpacity (rule, sheet) { 64 | // /* stop all processing if we hit a style property for opacity */ 65 | // if (rule.style.cssText.match(/opacity|rgba/)) 66 | // this.stop(); 67 | // } 68 | // var canceled = !(new CssParser({onRule: checkRuleForOpacity})).parse(); 69 | // 70 | // TODO: onSheet only gets called if a document (or null) is passed into parse() 71 | 72 | var 73 | // context in which to execute callbacks 74 | ctx = cb.context || this, 75 | // flag to detect if user has stopped (c is short for "continue") 76 | c = true, 77 | // preventative measure 78 | undefined; 79 | 80 | this.parse = function (/* DOMDocument|CSSStyleSheet|Array? */ w) { 81 | // summary: Call parse to start parsing the document. 82 | // w: DOMDocument|CSSStyleSheet|Array? - The item(s) to parse. 83 | // Defaults to currently-scoped document (dojo.doc). May be any number of 84 | // documents or stylesheet objects. 85 | // returns Boolean. true == parse was not stopped; false == it was stopped. 86 | c = true; 87 | if (!common.isArray(w)) w = [w || document]; 88 | every(w, function (obj) { 89 | return obj.nodeType == 9 ? doc(obj) : sheet(obj); // 9 == DOMDocument 90 | }); 91 | return c; 92 | }; 93 | 94 | this.stop = function () { 95 | // summary: Call stop() from within a callback to stop parsing. 96 | c = false; 97 | }; 98 | 99 | function doc (/* DOMDocument */ doc) { 100 | every(doc.styleSheets, function (s) { 101 | // Note: c should be checked AFTER the callback. 102 | sheet(s); 103 | return c; 104 | }); 105 | } 106 | 107 | function sheet (/* CSSStyleSheet */ s) { 108 | if (cb.onRule || cb.onImport || cb.onSelector || cb.onProperty || (cb.onSheet && cb.onSheet.call(ctx, s) !== false) && c) 109 | every(s.cssRules || s.rules /* <-- friggin IE! */, function (r) { 110 | // parse if there are callbacks AND the current callback (if any) didn't cancel and 111 | // caller didn't cancel (c == false). Note: c should be checked AFTER the callback. 112 | if (cb.onSelector || cb.onProperty || cb.onImport || (cb.onRule && cb.onRule.call(ctx, r, s) !== false) && c) 113 | rule(r, s); 114 | return c; 115 | }); 116 | } 117 | 118 | function rule (/* CSSStyleRule */ r, /* CSSStyleSheet */ s) { 119 | // if this is an @import 120 | if (r.styleSheet) { 121 | if (!cb.skipImports) { 122 | if (cb.onImport && cb.onImport(r.stylesheet, r, s) !== false && c) 123 | sheet(r.stylesheet); 124 | } 125 | } 126 | // otherwise 127 | else { 128 | var t; 129 | // if there is an onSelector callback 130 | if (cb.onSelector && (/* performance gain and less bytes: */ t = r.selectorText)) 131 | every(cb.dontSplit ? [t] : t.split(/\s*,\s*/g), function (p) { 132 | return cb.onSelector.call(ctx, p, r, s) !== false && c; 133 | }); 134 | // if there is an onProperty callback 135 | if (cb.onProperty) { 136 | // grab the style object and iterate over its properties 137 | t = r.style; 138 | // normal (fast) way 139 | if (t.length !== undefined) 140 | every(t, function (p) { 141 | return cb.onProperty.call(ctx, p, t[p], r, s) !== false && c; 142 | }); 143 | // IE (slow) way 144 | else { 145 | // Note: this regex is overly simple, but won't hurt because we'll catch invalid property names in the next loop 146 | var props = common.map(t.cssText.match(/([\w-]+):/g) || [], function (p) { 147 | return common.camelize(p.substr(0, p.length - 1).toLowerCase()); 148 | }); 149 | every(props, function (p) { 150 | var v = t[p]; // property value 151 | return (v == undefined || cb.onProperty.call(ctx, p, /* convert to string: */ '' + v, r, s) !== false) && c; 152 | }); 153 | } 154 | } 155 | } 156 | } 157 | 158 | } 159 | 160 | }); 161 | -------------------------------------------------------------------------------- /src/cssx/css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010 unscriptable.com 3 | */ 4 | 5 | /*jslint browser:true, on:true, sub:true */ 6 | 7 | (function (doc) { 8 | "use strict"; 9 | 10 | /* 11 | * RequireJS css! plugin 12 | * This plugin will load and wait for css files. This could be handy when 13 | * loading css files as part of a layer or as a way to apply a run-time theme. 14 | * Most browsers do not support the load event handler of the link element. 15 | * Therefore, we have to use other means to detect when a css file loads. 16 | * (The HTML5 spec states that the LINK element should have a load event, but 17 | * not even Chrome 8 or FF4b7 have it, yet. 18 | * http://www.w3.org/TR/html5/semantics.html#the-link-element) 19 | * 20 | * This plugin tries to use the load event and a universal work-around when 21 | * it is invoked the first time. If the load event works, it is used on 22 | * every successive load. Therefore, browsers that support the load event will 23 | * just work (i.e. no need for hacks!). FYI, Feature-detecting the load 24 | * event is tricky since most browsers have a non-functional onload property. 25 | * 26 | * The universal work-around watches a stylesheet until its rules are 27 | * available (not null or undefined). There are nuances, of course, between 28 | * the various browsers. The isLinkReady function accounts for these. 29 | * 30 | * Note: it appears that all browsers load @import'ed stylesheets before 31 | * fully processing the rest of the importing stylesheet. Therefore, we 32 | * don't need to find and wait for any @import rules explicitly. 33 | * 34 | * Note #2: for Opera compatibility, stylesheets must have at least one rule. 35 | * AFAIK, there's no way to tell the difference between an empty sheet and 36 | * one that isn't finished loading in Opera (XD or same-domain). 37 | * 38 | * Options: 39 | * !nowait - does not wait for the stylesheet to be parsed, just loads it 40 | * 41 | * Global configuration options: 42 | * 43 | * cssDeferLoad: Boolean. You can also instruct this plugin to not wait 44 | * for css resources. They'll get loaded asap, but other code won't wait 45 | * for them. This is just like using the !nowait option on every css file. 46 | * 47 | * cssWatchPeriod: if direct load-detection techniques fail, this option 48 | * determines the msec to wait between brute-force checks for rules. The 49 | * default is 50 msec. 50 | * 51 | * You may specify an alternate file extension: 52 | * require('css!myproj/component.less') // --> myproj/component.less 53 | * require('css!myproj/component.scss') // --> myproj/component.scss 54 | * 55 | * When using alternative file extensions, be sure to serve the files from 56 | * the server with the correct mime type (text/css) or some browsers won't 57 | * parse them, causing an error in the plugin. 58 | * 59 | * usage: 60 | * require(['css!myproj/comp']); // load and wait for myproj/comp.css 61 | * define(['css!some/folder/file'], {}); // wait for some/folder/file.css 62 | * require(['css!myWidget!nowait']); 63 | * 64 | * Tested in: 65 | * Firefox 1.5, 2.0, 3.0, 3.5, 3.6, and 4.0b6 66 | * Safari 3.0.4, 3.2.1, 5.0 67 | * Chrome 7 (8+ is partly b0rked) 68 | * Opera 9.52, 10.63, and Opera 11.00 69 | * IE 6, 7, and 8 70 | * Netscape 7.2 (WTF? SRSLY!) 71 | * Does not work in Safari 2.x :( 72 | * In Chrome 8+, there's no way to wait for cross-domain (XD) stylesheets. 73 | * See comments in the code below. 74 | * TODO: figure out how to be forward-compatible when browsers support HTML5's 75 | * load handler without breaking IE and Opera 76 | */ 77 | 78 | 79 | var 80 | // compressibility shortcuts 81 | onreadystatechange = 'onreadystatechange', 82 | onload = 'onload', 83 | createElement = 'createElement', 84 | // failed is true if RequireJS threw an exception 85 | failed = false, 86 | undef, 87 | insertedSheets = {}, 88 | features = { 89 | // true if the onload event handler works 90 | // "event-link-onload" : false 91 | }, 92 | // find the head element and set it to it's standard property if nec. 93 | head = doc.head || (doc.head = doc.getElementsByTagName('head')[0]); 94 | 95 | function has (feature) { 96 | return features[feature]; 97 | } 98 | 99 | // failure detection 100 | // we need to watch for onError when using RequireJS so we can shut off 101 | // our setTimeouts when it encounters an error. 102 | if (require['onError']) { 103 | require['onError'] = (function (orig) { 104 | return function () { 105 | failed = true; 106 | orig.apply(this, arguments); 107 | } 108 | })(require['onError']); 109 | } 110 | 111 | /***** load-detection functions *****/ 112 | 113 | function loadHandler (params, cb) { 114 | // We're using 'readystatechange' because IE and Opera happily support both 115 | var link = params.link; 116 | link[onreadystatechange] = link[onload] = function () { 117 | if (!link.readyState || link.readyState == 'complete') { 118 | features["event-link-onload"] = true; 119 | cleanup(params); 120 | cb(); 121 | } 122 | }; 123 | } 124 | 125 | function nameWithExt (name, defaultExt) { 126 | return name.lastIndexOf('.') <= name.lastIndexOf('/') ? 127 | name + '.' + defaultExt : name; 128 | } 129 | 130 | function parseSuffixes (name) { 131 | // creates a dual-structure: both an array and a hashmap 132 | // suffixes[0] is the actual name 133 | var parts = name.split('!'), 134 | suf, i = 1, pair; 135 | while ((suf = parts[i++])) { // double-parens to avoid jslint griping 136 | pair = suf.split('=', 2); 137 | parts[pair[0]] = pair.length == 2 ? pair[1] : true; 138 | } 139 | return parts; 140 | } 141 | 142 | function createLink (doc, optHref) { 143 | var link = doc[createElement]('link'); 144 | link.rel = "stylesheet"; 145 | link.type = "text/css"; 146 | if (optHref) { 147 | link.href = optHref; 148 | } 149 | return link; 150 | } 151 | 152 | // Chrome 8 hax0rs! 153 | // This is an ugly hack needed by Chrome 8+ which no longer waits for rules 154 | // to be applied to the document before exposing them to javascript. 155 | // Unfortunately, this routine will never fire for XD stylsheets since 156 | // Chrome will also throw an exception if attempting to access the rules 157 | // of an XD stylesheet. Therefore, there's no way to detect the load 158 | // event of XD stylesheets until Google fixes this, preferably with a 159 | // functional load event! As a work-around, use ready() before rendering 160 | // widgets / components that need the css to be ready. 161 | var testEl; 162 | function styleIsApplied () { 163 | if (!testEl) { 164 | testEl = doc[createElement]('div'); 165 | testEl.id = '_cssx_load_test'; 166 | testEl.style.cssText = 'position:absolute;top:-999px;left:-999px;'; 167 | doc.body.appendChild(testEl); 168 | } 169 | return doc.defaultView.getComputedStyle(testEl, null).marginTop == '-5px'; 170 | } 171 | 172 | function isLinkReady (link) { 173 | // This routine is a bit fragile: browser vendors seem oblivious to 174 | // the need to know precisely when stylesheets load. Therefore, we need 175 | // to continually test beta browsers until they all support the LINK load 176 | // event like IE and Opera. 177 | var sheet, rules, ready = false; 178 | try { 179 | // webkit's and IE's sheet is null until the sheet is loaded 180 | sheet = link.sheet || link.styleSheet; 181 | // mozilla's sheet throws an exception if trying to access xd rules 182 | rules = sheet.cssRules || sheet.rules; 183 | // webkit's xd sheet returns rules == null 184 | // opera's sheet always returns rules, but length is zero until loaded 185 | // friggin IE doesn't count @import rules as rules, but IE should 186 | // never hit this routine anyways. 187 | ready = rules ? 188 | rules.length > 0 : // || (sheet.imports && sheet.imports.length > 0) : 189 | rules !== undef; 190 | // thanks, Chrome 8, for this lovely hack 191 | if (ready && navigator.userAgent.indexOf('Chrome') >= 0) { 192 | sheet.insertRule('#_cssx_load_test{margin-top:-5px;}', 0); 193 | ready = styleIsApplied(); 194 | sheet.deleteRule(0); 195 | } 196 | } 197 | catch (ex) { 198 | // 1000 means FF loaded an xd stylesheet 199 | // other browsers just throw a security error here (IE uses the phrase 'Access is denied') 200 | ready = (ex.code == 1000) || (ex.message.match(/security|denied/i)); 201 | } 202 | return ready; 203 | } 204 | 205 | function ssWatcher (params, cb) { 206 | // watches a stylesheet for loading signs. 207 | if (isLinkReady(params.link)) { 208 | cleanup(params); 209 | cb(); 210 | } 211 | else if (!failed) { 212 | setTimeout(function () { ssWatcher(params, cb); }, params.wait); 213 | } 214 | } 215 | 216 | function loadDetector (params, cb) { 217 | // It would be nice to use onload everywhere, but the onload handler 218 | // only works in IE and Opera. 219 | // Detecting it cross-browser is completely impossible, too, since 220 | // THE BROWSERS ARE LIARS! DON'T TELL ME YOU HAVE AN ONLOAD PROPERTY 221 | // IF IT DOESN'T DO ANYTHING! 222 | var loaded; 223 | function cbOnce () { 224 | if (!loaded) { 225 | loaded = true; 226 | cb(); 227 | } 228 | } 229 | loadHandler(params, cbOnce); 230 | if (!has("event-link-onload")) ssWatcher(params, cbOnce); 231 | } 232 | 233 | function cleanup (params) { 234 | var link = params.link; 235 | link[onreadystatechange] = link[onload] = null; 236 | } 237 | 238 | /***** finally! the actual plugin *****/ 239 | 240 | var plugin = { 241 | 242 | //prefix: 'css', 243 | 244 | 'load': function (resourceDef, require, callback, config) { 245 | var resources = resourceDef.split(","), 246 | loadingCount = resources.length; 247 | 248 | // all detector functions must ensure that this function only gets 249 | // called once per stylesheet! 250 | function loaded () { 251 | // load/error handler may have executed before stylesheet is 252 | // fully parsed / processed in Opera, so use setTimeout. 253 | // Opera will process before the it next enters the event loop 254 | // (so 0 msec is enough time). 255 | if(--loadingCount == 0){ 256 | // TODO: move this setTimeout to loadHandler 257 | setTimeout(function () { callback(link); }, 0); 258 | } 259 | } 260 | 261 | // after will become truthy once the loop executes a second time 262 | for (var i = resources.length - 1, after; i >= 0; i--, after = url) { 263 | 264 | resourceDef = resources[i]; 265 | 266 | var 267 | // TODO: this is a bit weird: find a better way to extract name? 268 | opts = parseSuffixes(resourceDef), 269 | name = opts.shift(), 270 | url = require['toUrl'](nameWithExt(name, 'css')), 271 | link = createLink(doc), 272 | nowait = 'nowait' in opts ? opts['nowait'] != 'false' : !!config['cssDeferLoad'], 273 | params = { 274 | link: link, 275 | url: url, 276 | wait: config['cssWatchPeriod'] || 50 277 | }; 278 | 279 | if (nowait) { 280 | callback(link); 281 | } 282 | else { 283 | // hook up load detector(s) 284 | loadDetector(params, loaded); 285 | } 286 | 287 | // go! 288 | link.href = url; 289 | 290 | if (after) { 291 | head.insertBefore(link, insertedSheets[after].previousSibling); 292 | } 293 | else { 294 | head.appendChild(link); 295 | } 296 | insertedSheets[url] = link; 297 | } 298 | 299 | }, 300 | 301 | /* the following methods are public in case they're useful to other plugins */ 302 | 303 | 'nameWithExt': nameWithExt, 304 | 305 | 'parseSuffixes': parseSuffixes, 306 | 307 | 'createLink': createLink 308 | 309 | }; 310 | 311 | define(plugin); 312 | 313 | })(document); 314 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | cssx (Cujo Style Sheet eXtender) 2 | 3 | ***** This project is no longer being maintained! ***** 4 | 5 | If you are looking for an AMD plugin for loading css, check out these: 6 | 7 | link! - loads css files in a link element 8 | css! - loads via link element or embeds css text into javascript and injects a style tag 9 | 10 | Both of these are maintained in the cujoJS curl.js repo: 11 | https://github.com/cujojs/curl/ 12 | 13 | If you are interested in an AMD plugin that shims CSS3 features for older browsers (and more), check out Kris Zyp's xstyle: 14 | 15 | https://github.com/kriszyp/xstyle 16 | 17 | cssx: current version: 0.1 18 | 19 | The following shims modules are working: 20 | * boxOffsets (IE6) 21 | * childSelector (.parent > .chid) (IE6) 22 | * comboSelector (.class1.class2) (IE6) 23 | * hover (.anything:hover) (IE6) 24 | * ieOpacity (IE6-8) 25 | * opacity (makes future css3 "opacity" work) (all non-IE browsers) 26 | * inlineBlock (IE6 and 7) 27 | * minmax (min-width, max-width, min-height, max-height) (IE6) 28 | * scrollbar (adds a scrollbar-width unit of measurement) (all browsers) 29 | * @imported stylesheets are now processed! 30 | 31 | What's not working: 32 | * attribute selectors (e.g. input[type="checkbox"]) 33 | * the UA sniffs to load bundles are not complete 34 | * media types are fixed at "all", "screen", and "handheld" 35 | 36 | Next: 37 | * more shims: transtions, box-sizing, 38 | :first-child, :last-child 39 | * Kris Zyp's Xstyle extensions need to be added 40 | * try to get Kris Zyp's fast-parse mechanism working 41 | 42 | ---------------------------------------- 43 | 44 | What is cssx? 45 | 46 | Cssx is an AMD-compliant plugin that loads and augments css files. It's 47 | an important part of the cujo web app framework, but can be used 48 | independently of any other cujo micro-library. Cssx only requires an 49 | AMD-compliant module loader, such as RequireJS, Backdraft's loader, or 50 | curl.js (another awesome cujo micro-library). 51 | 52 | If you want to just use the css-loading capabilities of cssx, you can 53 | simply copy the css.js file into your project. It does not rely on any other 54 | files in this repo. css.js requires the use of an AMD-compliant module 55 | loader just like cssx does. More notes about using css.js are in the section 56 | "How do I just use css.js in my RequireJS or curl.js project?" (below). 57 | 58 | ---------------------------------------- 59 | 60 | Why would you want to augment css files? 61 | 62 | Mainly, to provide fixes for browsers that don't support CSS 3 or CSS 2.1. 63 | Cssx has it's own plugins. These plugins modify css in various ways, such as: 64 | 65 | 1) convert opacity and rgba to something that works in IE, e.g.: 66 | opacity:0.1; to filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=10); 67 | 2) implement "box offset positioning" in IE6 68 | (http://www.w3.org/TR/CSS2/visuren.html#position-props) 69 | 3) implement advanced selectors in IE 6&7, e.g.: 70 | input[type=checkbox], .class1.class2, .parent > .child 71 | 4) automatically convert css3 properties to vendor-specific properties, e.g.: 72 | transition:opacity 1s ease; to -webkit-transition:opacity 1s ease; 73 | 5) automatically convert machine-specific measurements, e.g.: 74 | margin-right: -cssx-scrollbar-width; to margin-right: 15px; 75 | 76 | All of the above (and several others) are already implemented and work 77 | dynamically (not statically like when using Selectivizr)! Because cssx is 78 | plugin-based, you can create and add your own cssx plugins. 79 | 80 | ---------------------------------------- 81 | 82 | Doesn't run-time augmentation of css take up valuable load time? 83 | 84 | Yes. It does. But probably not as much as you think. It takes a few 85 | milliseconds to process a reasonably-sized css file. cssx provides several 86 | configuration options to help streamline the parsing process. 87 | 88 | Current work on AMD is focused on building optimized bundles of javascript 89 | for each browser. This is called User Agent Profiling. UA Profiling also 90 | extends to AMD plugins like cssx. UA Profiling will allow css augmentation 91 | to run on the server, rather than in the browser, eliminating the expensive 92 | text parsing. 93 | 94 | cujo.js's build tool, cram (Cujo Resource AsseMbler), is only in the proof- 95 | of-concept stages. We expect it to be ready by mid 2011. 96 | 97 | ---------------------------------------- 98 | 99 | How do I start using cssx? 100 | 101 | To start using cssx, just copy (or clone) the src/cssx folder into your project 102 | and map one of your AMD loader paths to it. There are several ways to map your 103 | loader paths. Go RTFM if you want to get the intimate details. In summary, you 104 | should map your loader's baseUrl to a common folder to all your javascript 105 | modules (including plugins such as cssx), if a common folder exists. Then, 106 | create path configuration options for any module roots that aren't peers within 107 | the baseUrl folder. Here's a simple example: 108 | 109 | // this is just one way to set the configuration for RequireJS or curl.js: 110 | require = { 111 | baseUrl: 'js/', // maps to the js folder that is a peer to this page 112 | paths: { 113 | myApp: 'myCompany/myApp', // maps to js/myCompany/myApp/ 114 | cssx: '../libs/cssx' // maps to libs/cssx 115 | // Note: libs and js are peer folders 116 | } 117 | }; 118 | 119 | Once you've got the paths configured correctly, you can start referencing the 120 | cssx plugin in your code. Typically, you'll do this in define() calls. By 121 | convention, you should invoke the plugin by using a prefix with a complete path 122 | to the plugin. Prefixes are delineated by a ! symbol in the module name. (CSS 123 | files are considered resources by AMD loaders, but have the same syntax as 124 | modules.) 125 | 126 | define( 127 | [ 128 | 'myApp/moduleA', // a javascript module dependency 129 | 'text!templates/myTemplate.html', // example usage of the text plugin 130 | 'cssx/cssx!styles/moduleA.css' // our css file! 131 | ], 132 | function (moduleA, template, cssxDef) { 133 | // code some awesomeness here 134 | } 135 | ); 136 | 137 | In this example, the stylesheet, moduleA.css is loaded, parsed, and augmented 138 | by cssx before the callback function is executed. When the callback function 139 | executes, it is handed an object, cssxDef, which -- except for tinkerers and 140 | the curious -- is not of much use. It contains a reference to the original 141 | element inserted as well as a possible