├── 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