Statistics
| Branch: | Tag: | Revision:

root / snf-app / synnefo / ui / static / snf / js / lib / selectivizr.js @ 483c9197

History | View | Annotate | Download (18.6 kB)

1
/*
2
selectivizr v1.0.2 - (c) Keith Clark, freely distributable under the terms 
3
of the MIT license.
4

5
selectivizr.com
6
*/
7
/* 
8
  
9
Notes about this source
10
-----------------------
11

12
 * The #DEBUG_START and #DEBUG_END comments are used to mark blocks of code
13
   that will be removed prior to building a final release version (using a
14
   pre-compression script)
15
  
16
  
17
References:
18
-----------
19
 
20
 * CSS Syntax          : http://www.w3.org/TR/2003/WD-css3-syntax-20030813/#style
21
 * Selectors           : http://www.w3.org/TR/css3-selectors/#selectors
22
 * IE Compatability    : http://msdn.microsoft.com/en-us/library/cc351024(VS.85).aspx
23
 * W3C Selector Tests  : http://www.w3.org/Style/CSS/Test/CSS3/Selectors/current/html/tests/
24
 
25
*/
26

    
27
(function(win) {
28

    
29
        // If browser isn't IE, then stop execution! This handles the script 
30
        // being loaded by non IE browsers because the developer didn't use 
31
        // conditional comments.
32
        if (/*@cc_on!@*/true) return;
33

    
34
        // =========================== Init Objects ============================
35

    
36
        var doc = document;
37
        var root = doc.documentElement;
38
        var xhr = getXHRObject();
39
        var ieVersion = /MSIE (\d+)/.exec(navigator.userAgent)[1];
40
        
41
        // If were not in standards mode, IE is too old / new or we can't create
42
        // an XMLHttpRequest object then we should get out now.
43
        if (doc.compatMode != 'CSS1Compat' || ieVersion<6 || ieVersion>8 || !xhr) {
44
                return;
45
        }
46
        
47
        
48
        // ========================= Common Objects ============================
49

    
50
        // Compatiable selector engines in order of CSS3 support. Note: '*' is
51
        // a placholder for the object key name. (basically, crude compression)
52
        var selectorEngines = {
53
                "NW"                                                                : "*.Dom.select",
54
                "MooTools"                                                        : "$$",
55
                "DOMAssistant"                                                : "*.$", 
56
                "Prototype"                                                        : "$$",
57
                "YAHOO"                                                                : "*.util.Selector.query",
58
                "Sizzle"                                                        : "*", 
59
                "jQuery"                                                        : "*",
60
                "dojo"                                                                : "*.query"
61
        };
62

    
63
        var selectorMethod;
64
        var enabledWatchers                                         = [];     // array of :enabled/:disabled elements to poll
65
        var ie6PatchID                                                         = 0;      // used to solve ie6's multiple class bug
66
        var patchIE6MultipleClasses                                = true;   // if true adds class bloat to ie6
67
        var namespace                                                         = "slvzr";
68
        
69
        // Stylesheet parsing regexp's
70
        var RE_COMMENT                                                        = /(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)\s*/g;
71
        var RE_IMPORT                                                        = /@import\s*(?:(?:(?:url\(\s*(['"]?)(.*)\1)\s*\))|(?:(['"])(.*)\3))[^;]*;/g;
72
        var RE_ASSET_URL                                                 = /\burl\(\s*(["']?)(?!data:)([^"')]+)\1\s*\)/g;
73
        var RE_PSEUDO_STRUCTURAL                                = /^:(empty|(first|last|only|nth(-last)?)-(child|of-type))$/;
74
        var RE_PSEUDO_ELEMENTS                                        = /:(:first-(?:line|letter))/g;
75
        var RE_SELECTOR_GROUP                                        = /(^|})\s*([^\{]*?[\[:][^{]+)/g;
76
        var RE_SELECTOR_PARSE                                        = /([ +~>])|(:[a-z-]+(?:\(.*?\)+)?)|(\[.*?\])/g; 
77
        var RE_LIBRARY_INCOMPATIBLE_PSEUDOS                = /(:not\()?:(hover|enabled|disabled|focus|checked|target|active|visited|first-line|first-letter)\)?/g;
78
        var RE_PATCH_CLASS_NAME_REPLACE                        = /[^\w-]/g;
79
        
80
        // HTML UI element regexp's
81
        var RE_INPUT_ELEMENTS                                        = /^(INPUT|SELECT|TEXTAREA|BUTTON)$/;
82
        var RE_INPUT_CHECKABLE_TYPES                        = /^(checkbox|radio)$/;
83

    
84
        // Broken attribute selector implementations (IE7/8 native [^=""], [$=""] and [*=""])
85
        var BROKEN_ATTR_IMPLEMENTATIONS                        = ieVersion>6 ? /[\$\^*]=(['"])\1/ : null;
86

    
87
        // Whitespace normalization regexp's
88
        var RE_TIDY_TRAILING_WHITESPACE                        = /([(\[+~])\s+/g;
89
        var RE_TIDY_LEADING_WHITESPACE                        = /\s+([)\]+~])/g;
90
        var RE_TIDY_CONSECUTIVE_WHITESPACE                = /\s+/g;
91
        var RE_TIDY_TRIM_WHITESPACE                                = /^\s*((?:[\S\s]*\S)?)\s*$/;
92
        
93
        // String constants
94
        var EMPTY_STRING                                                = "";
95
        var SPACE_STRING                                                = " ";
96
        var PLACEHOLDER_STRING                                        = "$1";
97

    
98
        // =========================== Patching ================================
99

    
100
        // --[ patchStyleSheet() ]----------------------------------------------
101
        // Scans the passed cssText for selectors that require emulation and
102
        // creates one or more patches for each matched selector.
103
        function patchStyleSheet( cssText ) {
104
                return cssText.replace(RE_PSEUDO_ELEMENTS, PLACEHOLDER_STRING).
105
                        replace(RE_SELECTOR_GROUP, function(m, prefix, selectorText) {        
106
                            var selectorGroups = selectorText.split(",");
107
                            for (var c = 0, cs = selectorGroups.length; c < cs; c++) {
108
                                    var selector = normalizeSelectorWhitespace(selectorGroups[c]) + SPACE_STRING;
109
                                    var patches = [];
110
                                    selectorGroups[c] = selector.replace(RE_SELECTOR_PARSE, 
111
                                            function(match, combinator, pseudo, attribute, index) {
112
                                                    if (combinator) {
113
                                                            if (patches.length>0) {
114
                                                                    applyPatches( selector.substring(0, index), patches );
115
                                                                    patches = [];
116
                                                            }
117
                                                            return combinator;
118
                                                    }                
119
                                                    else {
120
                                                            var patch = (pseudo) ? patchPseudoClass( pseudo ) : patchAttribute( attribute );
121
                                                            if (patch) {
122
                                                                    patches.push(patch);
123
                                                                    return "." + patch.className;
124
                                                            }
125
                                                            return match;
126
                                                    }
127
                                            }
128
                                    );
129
                            }
130
                            return prefix + selectorGroups.join(",");
131
                    });
132
        };
133

    
134
        // --[ patchAttribute() ]-----------------------------------------------
135
        // returns a patch for an attribute selector.
136
        function patchAttribute( attr ) {
137
                return (!BROKEN_ATTR_IMPLEMENTATIONS || BROKEN_ATTR_IMPLEMENTATIONS.test(attr)) ? 
138
                        { className: createClassName(attr), applyClass: true } : null;
139
        };
140

    
141
        // --[ patchPseudoClass() ]---------------------------------------------
142
        // returns a patch for a pseudo-class
143
        function patchPseudoClass( pseudo ) {
144

    
145
                var applyClass = true;
146
                var className = createClassName(pseudo.slice(1));
147
                var isNegated = pseudo.substring(0, 5) == ":not(";
148
                var activateEventName;
149
                var deactivateEventName;
150

    
151
                // if negated, remove :not() 
152
                if (isNegated) {
153
                        pseudo = pseudo.slice(5, -1);
154
                }
155
                
156
                // bracket contents are irrelevant - remove them
157
                var bracketIndex = pseudo.indexOf("(")
158
                if (bracketIndex > -1) {
159
                        pseudo = pseudo.substring(0, bracketIndex);
160
                }                
161
                
162
                // check we're still dealing with a pseudo-class
163
                if (pseudo.charAt(0) == ":") {
164
                        switch (pseudo.slice(1)) {
165

    
166
                                case "root":
167
                                        applyClass = function(e) {
168
                                                return isNegated ? e != root : e == root;
169
                                        }
170
                                        break;
171

    
172
                                case "target":
173
                                        // :target is only supported in IE8
174
                                        if (ieVersion == 8) {
175
                                                applyClass = function(e) {
176
                                                        var handler = function() { 
177
                                                                var hash = location.hash;
178
                                                                var hashID = hash.slice(1);
179
                                                                return isNegated ? (hash == EMPTY_STRING || e.id != hashID) : (hash != EMPTY_STRING && e.id == hashID);
180
                                                        };
181
                                                        addEvent( win, "hashchange", function() {
182
                                                                toggleElementClass(e, className, handler());
183
                                                        })
184
                                                        return handler();
185
                                                }
186
                                                break;
187
                                        }
188
                                        return false;
189
                                
190
                                case "checked":
191
                                        applyClass = function(e) { 
192
                                                if (RE_INPUT_CHECKABLE_TYPES.test(e.type)) {
193
                                                        addEvent( e, "propertychange", function() {
194
                                                                if (event.propertyName == "checked") {
195
                                                                        toggleElementClass( e, className, e.checked !== isNegated );
196
                                                                }                                                         
197
                                                        })
198
                                                }
199
                                                return e.checked !== isNegated;
200
                                        }
201
                                        break;
202
                                        
203
                                case "disabled":
204
                                        isNegated = !isNegated;
205

    
206
                                case "enabled":
207
                                        applyClass = function(e) { 
208
                                                if (RE_INPUT_ELEMENTS.test(e.tagName)) {
209
                                                        addEvent( e, "propertychange", function() {
210
                                                                if (event.propertyName == "$disabled") {
211
                                                                        toggleElementClass( e, className, e.$disabled === isNegated );
212
                                                                } 
213
                                                        });
214
                                                        enabledWatchers.push(e);
215
                                                        e.$disabled = e.disabled;
216
                                                        return e.disabled === isNegated;
217
                                                }
218
                                                return pseudo == ":enabled" ? isNegated : !isNegated;
219
                                        }
220
                                        break;
221
                                        
222
                                case "focus":
223
                                        activateEventName = "focus";
224
                                        deactivateEventName = "blur";
225
                                                                
226
                                case "hover":
227
                                        if (!activateEventName) {
228
                                                activateEventName = "mouseenter";
229
                                                deactivateEventName = "mouseleave";
230
                                        }
231
                                        applyClass = function(e) {
232
                                                addEvent( e, isNegated ? deactivateEventName : activateEventName, function() {
233
                                                        toggleElementClass( e, className, true );
234
                                                })
235
                                                addEvent( e, isNegated ? activateEventName : deactivateEventName, function() {
236
                                                        toggleElementClass( e, className, false );
237
                                                })
238
                                                return isNegated;
239
                                        }
240
                                        break;
241
                                        
242
                                // everything else
243
                                default:
244
                                        // If we don't support this pseudo-class don't create 
245
                                        // a patch for it
246
                                        if (!RE_PSEUDO_STRUCTURAL.test(pseudo)) {
247
                                                return false;
248
                                        }
249
                                        break;
250
                        }
251
                }
252
                return { className: className, applyClass: applyClass };
253
        };
254

    
255
        // --[ applyPatches() ]-------------------------------------------------
256
        // uses the passed selector text to find DOM nodes and patch them        
257
        function applyPatches(selectorText, patches) {
258
                var elms;
259
                
260
                // Although some selector libraries can find :checked :enabled etc. 
261
                // we need to find all elements that could have that state because 
262
                // it can be changed by the user.
263
                var domSelectorText = selectorText.replace(RE_LIBRARY_INCOMPATIBLE_PSEUDOS, EMPTY_STRING);
264
                
265
                // If the dom selector equates to an empty string or ends with 
266
                // whitespace then we need to append a universal selector (*) to it.
267
                if (domSelectorText == EMPTY_STRING || domSelectorText.charAt(domSelectorText.length - 1) == SPACE_STRING) {
268
                        domSelectorText += "*";
269
                }
270
                
271
                // Ensure we catch errors from the selector library
272
                try {
273
                        elms = selectorMethod( domSelectorText );
274
                } catch (ex) {
275
                        // #DEBUG_START
276
                        log( "Selector '" + selectorText + "' threw exception '" + ex + "'" );
277
                        // #DEBUG_END
278
                }
279

    
280

    
281
                if (elms) {
282
                        for (var d = 0, dl = elms.length; d < dl; d++) {        
283
                                var elm = elms[d];
284
                                var cssClasses = elm.className;
285
                                for (var f = 0, fl = patches.length; f < fl; f++) {
286
                                        var patch = patches[f];
287
                                        
288
                                        if (!hasPatch(elm, patch)) {
289
                                                if (patch.applyClass && (patch.applyClass === true || patch.applyClass(elm) === true)) {
290
                                                        cssClasses = toggleClass(cssClasses, patch.className, true );
291
                                                }
292
                                        }
293
                                }
294
                                elm.className = cssClasses;
295
                        }
296
                }
297
        };
298

    
299
        // --[ hasPatch() ]-----------------------------------------------------
300
        // checks for the exsistence of a patch on an element
301
        function hasPatch( elm, patch ) {
302
                return new RegExp("(^|\\s)" + patch.className + "(\\s|$)").test(elm.className);
303
        };
304
        
305
        
306
        // =========================== Utility =================================
307
        
308
        function createClassName( className ) {
309
                return namespace + "-" + ((ieVersion == 6 && patchIE6MultipleClasses) ?
310
                        ie6PatchID++
311
                :
312
                        className.replace(RE_PATCH_CLASS_NAME_REPLACE, function(a) { return a.charCodeAt(0) }));
313
        };
314

    
315
        // --[ log() ]----------------------------------------------------------
316
        // #DEBUG_START
317
        function log( message ) {
318
                if (win.console) {
319
                        win.console.log(message);
320
                }
321
        };
322
        // #DEBUG_END
323

    
324
        // --[ trim() ]---------------------------------------------------------
325
        // removes leading, trailing whitespace from a string
326
        function trim( text ) {
327
                return text.replace(RE_TIDY_TRIM_WHITESPACE, PLACEHOLDER_STRING);
328
        };
329

    
330
        // --[ normalizeWhitespace() ]------------------------------------------
331
        // removes leading, trailing and consecutive whitespace from a string
332
        function normalizeWhitespace( text ) {
333
                return trim(text).replace(RE_TIDY_CONSECUTIVE_WHITESPACE, SPACE_STRING);
334
        };
335

    
336
        // --[ normalizeSelectorWhitespace() ]----------------------------------
337
        // tidies whitespace around selector brackets and combinators
338
        function normalizeSelectorWhitespace( selectorText ) {
339
                return normalizeWhitespace(selectorText.
340
                        replace(RE_TIDY_TRAILING_WHITESPACE, PLACEHOLDER_STRING).
341
                        replace(RE_TIDY_LEADING_WHITESPACE, PLACEHOLDER_STRING)
342
                );
343
        };
344

    
345
        // --[ toggleElementClass() ]-------------------------------------------
346
        // toggles a single className on an element
347
        function toggleElementClass( elm, className, on ) {
348
                var oldClassName = elm.className;
349
                var newClassName = toggleClass(oldClassName, className, on);
350
                if (newClassName != oldClassName) {
351
                        elm.className = newClassName;
352
                        elm.parentNode.className += EMPTY_STRING;
353
                }
354
        };
355

    
356
        // --[ toggleClass() ]--------------------------------------------------
357
        // adds / removes a className from a string of classNames. Used to 
358
        // manage multiple class changes without forcing a DOM redraw
359
        function toggleClass( classList, className, on ) {
360
                var re = RegExp("(^|\\s)" + className + "(\\s|$)");
361
                var classExists = re.test(classList);
362
                if (on) {
363
                        return classExists ? classList : classList + SPACE_STRING + className;
364
                } else {
365
                        return classExists ? trim(classList.replace(re, PLACEHOLDER_STRING)) : classList;
366
                }
367
        };
368
        
369
        // --[ addEvent() ]-----------------------------------------------------
370
        function addEvent(elm, eventName, eventHandler) {
371
                elm.attachEvent("on" + eventName, eventHandler);
372
        };
373

    
374
        // --[ getXHRObject() ]-------------------------------------------------
375
        function getXHRObject()
376
        {
377
                if (win.XMLHttpRequest) {
378
                        return new XMLHttpRequest;
379
                }
380
                try        { 
381
                        return new ActiveXObject('Microsoft.XMLHTTP');
382
                } catch(e) { 
383
                        return null;
384
                }
385
        };
386

    
387
        // --[ loadStyleSheet() ]-----------------------------------------------
388
        function loadStyleSheet( url ) {
389
                xhr.open("GET", url, false);
390
                xhr.send();
391
                return (xhr.status==200) ? xhr.responseText : EMPTY_STRING;        
392
        };
393
        
394
        // --[ resolveUrl() ]---------------------------------------------------
395
        // Converts a URL fragment to a fully qualified URL using the specified
396
        // context URL. Returns null if same-origin policy is broken
397
        function resolveUrl( url, contextUrl ) {
398
        
399
                function getProtocolAndHost( url ) {
400
                        return url.substring(0, url.indexOf("/", 8));
401
                };
402
                
403
                // absolute path
404
                if (/^https?:\/\//i.test(url)) {
405
                        return getProtocolAndHost(contextUrl) == getProtocolAndHost(url) ? url : null;
406
                }
407
                
408
                // root-relative path
409
                if (url.charAt(0)=="/")        {
410
                        return getProtocolAndHost(contextUrl) + url;
411
                }
412

    
413
                // relative path
414
                var contextUrlPath = contextUrl.split(/[?#]/)[0]; // ignore query string in the contextUrl        
415
                if (url.charAt(0) != "?" && contextUrlPath.charAt(contextUrlPath.length - 1) != "/") {
416
                        contextUrlPath = contextUrlPath.substring(0, contextUrlPath.lastIndexOf("/") + 1);
417
                }
418
                
419
                return contextUrlPath + url;
420
        };
421
        
422
        // --[ parseStyleSheet() ]----------------------------------------------
423
        // Downloads the stylesheet specified by the URL, removes it's comments
424
        // and recursivly replaces @import rules with their contents, ultimately
425
        // returning the full cssText.
426
        function parseStyleSheet( url ) {
427
                if (url) {
428
                        return loadStyleSheet(url).replace(RE_COMMENT, EMPTY_STRING).
429
                        replace(RE_IMPORT, function( match, quoteChar, importUrl, quoteChar2, importUrl2 ) { 
430
                                return parseStyleSheet(resolveUrl(importUrl || importUrl2, url));
431
                        }).
432
                        replace(RE_ASSET_URL, function( match, quoteChar, assetUrl ) { 
433
                                quoteChar = quoteChar || EMPTY_STRING;
434
                                return " url(" + quoteChar + resolveUrl(assetUrl, url) + quoteChar + ") "; 
435
                        });
436
                }
437
                return EMPTY_STRING;
438
        };
439
        
440
        // --[ init() ]---------------------------------------------------------
441
        function init() {
442
                // honour the <base> tag
443
                var url, stylesheet;
444
                var baseTags = doc.getElementsByTagName("BASE");
445
                var baseUrl = (baseTags.length > 0) ? baseTags[0].href : doc.location.href;
446
                
447
                /* Note: This code prevents IE from freezing / crashing when using 
448
                @font-face .eot files but it modifies the <head> tag and could
449
                trigger the IE stylesheet limit. It will also cause FOUC issues.
450
                If you choose to use it, make sure you comment out the for loop 
451
                directly below this comment.
452

453
                var head = doc.getElementsByTagName("head")[0];
454
                for (var c=doc.styleSheets.length-1; c>=0; c--) {
455
                        stylesheet = doc.styleSheets[c]
456
                        head.appendChild(doc.createElement("style"))
457
                        var patchedStylesheet = doc.styleSheets[doc.styleSheets.length-1];
458
                        
459
                        if (stylesheet.href != EMPTY_STRING) {
460
                                url = resolveUrl(stylesheet.href, baseUrl)
461
                                if (url) {
462
                                        patchedStylesheet.cssText = patchStyleSheet( parseStyleSheet( url ) )
463
                                        stylesheet.disabled = true
464
                                        setTimeout( function () {
465
                                                stylesheet.owningElement.parentNode.removeChild(stylesheet.owningElement)
466
                                        })
467
                                }
468
                        }
469
                }
470
                */
471
                
472
                for (var c = 0; c < doc.styleSheets.length; c++) {
473
                        stylesheet = doc.styleSheets[c]
474
                        if (stylesheet.href != EMPTY_STRING) {
475
                                url = resolveUrl(stylesheet.href, baseUrl);
476
                                if (url) {
477
                                        stylesheet.cssText = patchStyleSheet( parseStyleSheet( url ) );
478
                                }
479
                        }
480
                }
481
                
482
                // :enabled & :disabled polling script (since we can't hook 
483
                // onpropertychange event when an element is disabled) 
484
                if (enabledWatchers.length > 0) {
485
                        setInterval( function() {
486
                                for (var c = 0, cl = enabledWatchers.length; c < cl; c++) {
487
                                        var e = enabledWatchers[c];
488
                                        if (e.disabled !== e.$disabled) {
489
                                                if (e.disabled) {
490
                                                        e.disabled = false;
491
                                                        e.$disabled = true;
492
                                                        e.disabled = true;
493
                                                }
494
                                                else {
495
                                                        e.$disabled = e.disabled;
496
                                                }
497
                                        }
498
                                }
499
                        },250)
500
                }
501
        };
502
        
503
        // Bind selectivizr to the ContentLoaded event. 
504
        ContentLoaded(win, function() {
505
                // Determine the "best fit" selector engine
506
                for (var engine in selectorEngines) {
507
                        var members, member, context = win;
508
                        if (win[engine]) {
509
                                members = selectorEngines[engine].replace("*", engine).split(".");
510
                                while ((member = members.shift()) && (context = context[member])) {}
511
                                if (typeof context == "function") {
512
                                        selectorMethod = context;
513
                                        init();
514
                                        return;
515
                                }
516
                        }
517
                }
518
        });
519
        
520
        
521
        /*!
522
         * ContentLoaded.js by Diego Perini, modified for IE<9 only (to save space)
523
         *
524
         * Author: Diego Perini (diego.perini at gmail.com)
525
         * Summary: cross-browser wrapper for DOMContentLoaded
526
         * Updated: 20101020
527
         * License: MIT
528
         * Version: 1.2
529
         *
530
         * URL:
531
         * http://javascript.nwbox.com/ContentLoaded/
532
         * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
533
         *
534
         */
535

    
536
        // @w window reference
537
        // @f function reference
538
        function ContentLoaded(win, fn) {
539

    
540
                var done = false, top = true,
541
                init = function(e) {
542
                        if (e.type == "readystatechange" && doc.readyState != "complete") return;
543
                        (e.type == "load" ? win : doc).detachEvent("on" + e.type, init, false);
544
                        if (!done && (done = true)) fn.call(win, e.type || e);
545
                },
546
                poll = function() {
547
                        try { root.doScroll("left"); } catch(e) { setTimeout(poll, 50); return; }
548
                        init('poll');
549
                };
550

    
551
                if (doc.readyState == "complete") fn.call(win, EMPTY_STRING);
552
                else {
553
                        if (doc.createEventObject && root.doScroll) {
554
                                try { top = !win.frameElement; } catch(e) { }
555
                                if (top) poll();
556
                        }
557
                        addEvent(doc,"readystatechange", init);
558
                        addEvent(win,"load", init);
559
                }
560
        };
561
})(this);