﻿if (!self.INDIGO) (function () {
    self.INDIGO = {};

    //simple warn function for testing, requires a div with id warning
    self.warn = function (str, clear) {
        var div = $('warning');
        if (clear)
            while (div.firstChild)
                div.removeChild(div.firstChild);
        div.appendChild(document.createTextNode(str));
        div.appendChild(document.createElement("br"));
    }
    //trim //found this in a case study of several trim techniques, this was generally the fastest one for trimming on small/normal strings
    String.prototype.trim = function () {
        return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    }
    
    //capitalize strings... clever!
    String.prototype.capitalize = function(limit){ //v1.0
        return this.replace(limit ? /\w+/ : /\w+/g, function(a){
            return a.charAt(0).toUpperCase() + a.substr(1).toLowerCase();
        });
    };
    String.prototype.capitalise = String.prototype.capitalize; //what's the convention here anyway? Behaviour.js is a known script...

    //Tracking... BETA!
    /*
    //Used to track 'invalid' set properties //browsers tested so far: IE/FF/O/S
    function read(d) {
        var invalid = [];
        if (d.nodeType === 1 && d.nodeName !== "APPLET") {
            var a, i, l, x, c = document.createElement(d.nodeName);
            for (var x in d) {
                if (!collInvalid[x] && d[x] !== c[x]) {
                    try {
                        d[x] = d[x];
                    } catch (err) {
                        invalid.push(x + ":true");
                    }
                }
            }
            a = d.childNodes;
            if (a) {
                l = a.length;
                for (i = 0; i < l; i += 1) {
                    read(d.childNodes[i]);
                }
            }
        }
        warn(invalid);
    }
    */

    self.collInvalid = {parentNode:true,childNodes:true,firstChild:true,lastChild:true,previousSibling:true,nextSibling:true,
                       attributes:true,cellIndex:true,offsetTop:true,offsetLeft:true,offsetWidth:true,offsetHeight:true,offsetParent:true,
                       scrollHeight:true,scrollWidth:true,clientHeight:true,clientWidth:true,style:true,children:true,outerText:true,
                       currentStyle:true,document:true,parentTextEdit:true,parentElement:true,runtimeStyle:true,filters:true,clientTop:true,
                       clientLeft:true,behaviorUrns:true,all:true,sourceIndex:true,innerHTML:true,outerHTML:true,textContent:true,host:true,
                       hostname:true,pathname:true,search:true,port:true,hash:true,selectionStart:true,selectionEnd:true,
                       sectionRowIndex:true,naturalHeight:true,naturalWidth:true,x:true,y:true,text:true,rows:true,
                       tBodies:true,rowIndex:true,cells:true,textLength:true,controllers:true,
                       innerText:true,fileModifiedDate:true,fileCreatedDate:true,mimeType:true,href:true,protocol:true,complete:true,
                       nameProp:true,isMultiLine:true,readyState:true,height:true,forms:true,labels:true,validity:true,forms:true,
                       labels:true,validity:true,forms:true,labels:true,validity:true,boxObject:true,elements:true,contentDocument:true,
                       contentWindow:true,areas:true,outerDiv:true,innerDiv:true,width:true,options:true,canHaveHTML:true,
                       canHaveChildren:true,length:true,isindex:true,form:true,isContentEditable:true,templateElements:true,
                       selectedOptions:true};

    self.events = {
        maxEvents: 10,
        step: 0,
        collection: [],
        add: function (e) {
            this.collection[this.step++] = e;
            while (this.collection.length > this.maxEvents) {
                this.collection.shift();
                this.step--;
            }
        },
        undo: function () {
            if (this.step > 0) {
                this.collection[--this.step].undo();
            }
        },
        redo: function () {
            if (this.step < this.history && this.collection[this.step] && this.collection[this.step++].redo) {
                this.collection[this.step - 1].redo();
            }
        },
        cEvent: function (undo, redo) {
            return {
                undo: undo,
                redo: redo/*,
                track: function () {};
                capture: function () {};
                */
            }
        },
        Event: function () {
            var past = [];
            var future = [];
            var captured = false;
            this.track = function (o, deep) {
                recordElement(o, deep);
            }
            
            function recordElement(o, deep/*object [, deep/level][, object[, deep/level]]...*/) {
                //deep/level can be either true, or a number
                //analyze arguments, assume single object+deep/level pair for now
                function save(curO, level) {
                    //check if we're allowed to add
                    if (!(captured && future.length === past.length)) {
                        level = level || 0;
                        var history = captured ? future : past;
                        var record = {};
                        record.element = curO;
                        record.childOrder = [];
                        record.cssText = "";
                        record.level = level;
                        record.deep = deep;
                        history.push(record);
                        if (curO.nodeType === 1 && curO.nodeName !== "APPLET") {
                            record.properties = {};
                            record.attributes = {};
                            var a, i, l, x, attr, c;
                            c = document.createElement(curO.nodeName);
                            //Record properties
                            for (x in curO) {
                                if (!collInvalid[x] && curO[x] !== c[x]) {
                                    //warn("Storing property " + x + " with value: " + curO[x]);
                                    record.properties[x] = curO[x];
                                }
                            }
                            //Record attributes
                            if (!document.all || window.opera) {
                                attr = curO.attributes;
                                if (attr) {
                                    l = attr.length;
                                    for (i = 0; i < l; i ++) {
                                        if (attr[i].name) {
                                            //alert("attr: " + attr[i].name.toLowerCase());
                                            if (record.properties[attr[i].name.toLowerCase()]) {
                                                //alert("Duplicate event handler detected: " + attr[i].name);
                                            }
                                            //warn("Storing attribute " + attr[i].name + " with value: " + attr[i].value);
                                            record.attributes[attr[i].name] = attr[i].value;
                                        }
                                    }
                                }
                            }
                            //Record style
                            record.cssText = curO.style.cssText;
                            if (level < deep || deep === true) {
                                a = curO.childNodes;
                                if (a) {
                                    l = a.length;
                                    for (i = 0; i < l; i++) {
                                        //save(a[i], level + 1); = 
                                        arguments.callee(a[i], level + 1);
                                        record.childOrder.push(a[i]);
                                    }
                                }
                            }
                        } else if (curO.nodeType === 3) {
                            record.nodeValue = curO.nodeValue;
                        }
                    }
                }
                if (o) {
                    save(o);
                }
            };
            
            this.capture = function () {
                captured = true;
                var i, l, record;
                l = past.length;
                for (i = 0; i < l; i++) {
                    record = past[i];
                    if (record.level === 0) {
                        recordElement(record.element, record.deep);
                    }
                }
            };

            function restore(history) {
                var x, i, l, record, element, j, m;
                l = history.length;
                //Restore attributes, properties, style and childnodes
                for (i = 0; i < l; i++) {
                    record = history[i];
                    element = record.element;
                    //warn("Restoring element " + element);
                    //RESTORE ATTRIBUTES BEFORE PROPERTIES -> PROPERTIES WILL OVERRIDE ATTRIBUTES IN BEHAVIOUR
                    for (x in record.attributes) {
                        //warn("Restoring attribute " + x + " with value " + record.attributes[x]);
                        element.setAttribute(x, record.attributes[x]);
                    }
                    for (x in record.properties) {
                        //warn("Restoring property " + x + " with value " + record.properties[x]);
                        element[x] = record.properties[x];
                    }
                    if (record.nodeValue) {
                        //warn("Restoring nodeValue " + record.nodeValue);
                        element.nodeValue = record.nodeValue;
                    }
                    //Restore style
                    if (element.style) {
                        element.style.cssText = record.cssText;
                    }
                    //Restore childnodes positions
                    
                    //purge existing elements
                    while (element.firstChild) {
                        element.removeChild(element.firstChild);
                    }
                    //restore
                    m = record.childOrder.length;
                    if (m > 0) {
                        for (j = 0; j < m; j++) {
                            element.appendChild(record.childOrder[j]);
                        }
                    }
                }
            };
            
            this.redo = function () {
                restore(future);
            };
            
            this.undo = function () {
                restore(past);
            };
        }
    }
    ////End tracking


    //style functions
    //get computed style
    //FLOAT CASE: FF: float (even though it's style.cssFloat) //IE: styleFloat (also style.styleFloat)
    //MARGIN/PADDING CASE: top/right/bottom/left
    //BORDER-x CASE: top/right/bottom/left | width/style/color
    //BORDER CASE: returns undefined when borders don't match
    self.getStyle = function (el, cssRule){ //non-border cssRule can be in either style: camelCase or hyphen-ated //handles float
        var types, i, result, check;
        if (cssRule === "margin" || cssRule === "padding") {
            types = ["top", "right", "bottom", "left"];
            result = "";
            for (i = 0; i < 4; i++) {
                result += " " + arguments.callee(el, cssRule + "-" + types[i]);
            }
            return result.replace(/( .+?)\1\1\1/, "$1").trim(); //reduce to only 1 element if all 4 sides are identical
        }
        if (cssRule === "border") {
            types = ["top", "right", "bottom", "left"];
            check = arguments.callee(el, "border-" + types[0]);
            for (i = 1; i < 4; i++) {
                result = arguments.callee(el, "border-" + types[i]);
                if (result !== check)
                    return undefined;
            }
            return result;
        }
        if (cssRule.match(/^border-(width|style|color)$/)) {
            types = ["top", "right", "bottom", "left"];
            result = "";
            for (i = 0; i < 4; i++) {
                result += " " + arguments.callee(el, cssRule.replace(/-(.*)/, "-" + types[i] + "-$1"));
            }
            return result.replace(/( .+?)\1\1\1/, "$1").trim(); //reduce to only 1 element if all 4 sides are identical (IE style style retrieval, differs with FF, good, bad? hm)
        }
        if (cssRule.match(/^border-(top|right|bottom|left)$/)) {
            types = ["width", "style", "color"];
            result = "";
            for (i = 0; i < 3; i++) {
                result += " " + arguments.callee(el, cssRule + "-" + types[i]);
            }
            return result.trim();
        }
        //Opera has both 'currentStyle' (IE) as defaultView.getComputedStyle (FF), but only currentStyle returns correct/useful/consistent values, so check for that first
	    if (el.currentStyle) {
	        if (window.opera && cssRule === 'width' && el.currentStyle[cssRule] !== 'auto') //Opera's width detection is off
	            return (getDimensions(el)[0] - parseInt("0" + getStyle(el, 'padding-left')) - parseInt("0" + getStyle(el, 'padding-right')) - parseInt("0" + getStyle(el, 'border-left')) - parseInt("0" + getStyle(el, 'border-right'))) + "px"
		    return el.currentStyle[cssRule.replace(/-(\w)/g, function (match, p1) {
			    return p1.toUpperCase();
		    }).replace(/^(float|cssFloat)$/, "styleFloat")];
	    } else if (document.defaultView && document.defaultView.getComputedStyle) {
		    return document.defaultView.getComputedStyle(el, "").getPropertyValue(cssRule.replace(/^(styleFloat|cssFloat)$/, "float").replace(/([A-Z])/g, function (match, p1) {
		        return "-" + p1.toLowerCase();
		    }));
	    }
	    return undefined;
    }
    
    self.applyStyle = function (item, style) { //apply styles in object
        for (var x in style) {
            if (style.hasOwnProperty(x)) {
                item.style[x] = style[x];
            }
        }
    }
    
    self.removeStyle = function (item, style) { //remove styles in object
        for (var x in style) {
            if (style.hasOwnProperty(x)) {
                item.style[x] = "";
            }
        }
    }

    self.getOPos = function (obj) { //Object position relative to document
	    var curLeft = 0, curTop = 0;
	    if (obj.offsetParent) {
		    curLeft = obj.offsetLeft;
		    curTop = obj.offsetTop;
		    while (obj = obj.offsetParent) {
			    curLeft += obj.offsetLeft;
			    curTop += obj.offsetTop;
		    }
	    }
	    return [curLeft, curTop];
    }
    
    self.getFPos = function (obj) { //Object position in the flow
        var position, coords, orCoords = [obj.style.left, obj.style.top];
        if (obj.parentNode !== obj.offsetParent) {
            position = obj.parentNode.style.position;
            obj.parentNode.style.position = "relative";
            coords = [obj.offsetLeft, obj.offsetTop];
            obj.parentNode.style.position = position;
        } else
            coords = [obj.offsetLeft, obj.offsetTop];
        return coords;
    }
    
    self.getOFPos = function (obj) { //Original relative position
        var coords, orCoords = [obj.style.left, obj.style.top];
        obj.style.left = "0px";
        obj.style.top = "0px";
        coords = getFPos(obj);
        obj.style.left = orCoords[0];
        obj.style.top = orCoords[1];
        return coords;
    }

    self.getMPos = function (e) { //Mouse position of event
      e = e || event;
      var x, y;
      if (e.pageX) {
        x = e.pageX;
        y = e.pageY;
      } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6/7 Strict Mode
        x = e.clientX + document.documentElement.scrollLeft;
        y = e.clientY + document.documentElement.scrollTop;
      } else if (e) {
        x = e.clientX + document.body.scrollLeft;
        y = e.clientY + document.body.scrollTop;
      }
      return [x, y];
    }

    //Viewport dimensions
    self.getVDims = function () {
        var x, y;
        if (self.innerHeight) { // all except Explorer
	        x = self.innerWidth;
	        y = self.innerHeight;
        }
        else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6/7 Strict Mode
	        x = document.documentElement.clientWidth;
	        y = document.documentElement.clientHeight;
        }
        else if (document.body) { // other Explorers
	        x = document.body.clientWidth;
	        y = document.body.clientHeight;
        }
        return [x, y];
    }

    //Scrolling dimensions
    self.getSDims = function () {
        var x, y;
        if (self.pageYOffset) { // all except Explorer
            x = self.pageXOffset;
            y = self.pageYOffset;
        }
        else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6/7 Strict Mode
            x = document.documentElement.scrollLeft;
            y = document.documentElement.scrollTop;
        }
        else if (document.body) { // all other Explorers
            x = document.body.scrollLeft;
            y = document.body.scrollTop;
        }
        return [x, y];
    }
    
    self.getDimensions = function (el) { //returns dimensions of element [width, height]
        el = $(el);
        var width, height;
        if (el.style.display == "none") {            
            el.style.display = "";
            width = el.offsetWidth;
            height = el.offsetHeight;
            el.style.display = "none";
        } else {
            width = el.offsetWidth;
            height = el.offsetHeight;
        }
        return [width, height];
    }

    //Copy event... allow for editing and passing on (used by TSwap)
    //outdated anyway, use built-in event constructors
    self.copyEvent = function (e) {
        e = e || event || {}; //...
        return {
            target: e.target || e.srcElement,
            relatedTarget: e.relatedTarget || e.toElement,
            pageX: e.pageX || e.clientX + document.body.scrollLeft,
            pageY: e.pageY || e.clientY + document.body.scrollTop,
            returnValue: e.returnValue, //useless, has to be set on real event
            cancelBubble: e.cancelBubble, //useless, has to be set on real event
            type: e.type,
            button: e.button,
            altKey: e.altKey,
            shiftKey: e.shiftKey,
            ctrlKey: e.ctrlKey,
            preventDefault: e.preventDefault && function () {
                e.preventDefault();
            },
            stopPropagation: e.stopPropagation && function () {
                e.stopPropagation();
            },
            isCopy: true
        }
    }
    
    self.getHZ = function () { //get highest Z-index
        var hZ = 0, zi;
        getAll().match(function (el) {
            return true || (getStyle(el, "position") !== "static");
        }).forEach(function (el) {
            zi = el.style.zIndex;
            if (zi)
                if (parseInt(zi, 10) > hZ)
                    hZ = zi;
        });
        return parseInt("0" + hZ, 10);
    }
    
    //revise "indicator"... attribute/property ok, include class?
    //indicator values? not compatible with classes
    //check for an 'attribute' set as either attribute, classname or property
    self.hasIndicator = function (el, ind, val) {
        val = val || ind;
        var reg = new RegExp("(^|\\s)" + ind + "(\\s|$)");
        return !!((el.className && reg.test(el.className)) || (el.getAttribute && el.getAttribute(ind) === val) || el[ind]);
    }
    
    self.hasParentIndicator = function (el, ind, val) {
        do {
            if (hasIndicator(el, ind, val))
                return true;
        } while (el = el.parentNode);    
        return false;
    }
    
    self.addIndicator = function (el, ind, val) {
        val = val || ind;
        addClass(el, ind);
        el.setAttribute(ind, val);
        el[ind] = true;
    }
    
    self.removeIndicator = function (el, ind, val) {
        removeClass(el, ind);
        el[ind] = undefined;
        el.removeAttribute(ind);
    }
    
    self.getIndicatedParent = function (el, ind, val) { //return first occuring parentnode (or node itself) with given indicator
        do {
            if (hasIndicator(el, ind, val))
                return el;
        } while (el = el.parentNode);    
        return null;
    }
    
    //...remove element safely
    self.remove = function (el) {
        purge(el);
        return el.parentNode.removeChild(el);
    }
    //remove all child nodes
    self.empty = function (el) {
        while (el.firstChild)
            remove(el.firstChild);
    }
    
    //remove classname
    self.removeClass = function (el, name) {
        if (el.className)
            el.className = el.className.replace(new RegExp("(^|\\s)" + name + "(\\s|$)", "g"), "");
    }
    
    //add classname
    self.addClass = function (el, name) {
        el.className = (el.className + " " + name).trim();
    }
    
    //get first occuring attribute of parentnodes
    self.getParentAttribute = function (el, attr) {
        if (el.hasAttribute && el.hasAttribute(attr))
            return el.getAttribute(attr);
        if (el.parentNode)
            return arguments.callee(el.parentNode, attr);
        return null;
    }
    
    //get first occuring non-empty style property of parentnodes (replace by a generic getStyle on the parentnode? hm
    self.getSetParentStyle = function (el, attr) {
        if (el.style && el.style[attr])
            return el.style[attr];
        if (el.parentNode)
            return arguments.callee(el.parentNode, attr);
        return null;
    }
    
    //...
    self.stopEvent = function (e) {
        if (e.preventDefault) {
            e.preventDefault();
            e.stopPropagation();
        }
        e.returnValue = false;
        e.cancelBubble = true;
        return false;
    }
    
    //walk every element of the DOM and pass it to the function provided //return an array of all elements the function returned true for
    self.walk = function (func, o) {
        o = o || document.body;
        var match = [];
        if (func(o)) {
            match.push(o);
        }
        if (o.hasChildNodes) {
            (function (o) {
                o = o.firstChild;
                if (o) {
                    do {
                        if (func(o)) {
                            match.push(o);
                        }
                        if (o.hasChildNodes) {
                            arguments.callee(o);
                        }
                    } while(o = o.nextSibling);
                }
            })(o);
        }
        return match;
    }

    //Converts an array-like object to a real array
    self.toArray = function (arO) {      
        var i, l = arO.length, ar = [];
        for (i = 0; i < l; i++) {
            ar[i] = arO[i];
        }
        return ar;
    }

    //Get all document/body elements
    self.getAll = function (o, incParent) {
        if (incParent) {
            o = document;
        }
        o = o || document.body; 
        var coll = o.getElementsByTagName("*");
        return toArray((coll && (typeof coll.length === "number")) ? coll : o.all).concat(incParent ? [o] : []); //prevent returning an unrecognised collection
    }

    //Common Array augmentations:
    if (!Array.prototype.forEach) {
        //Apply function to each element in an array
        Array.prototype.forEach = function (fun, scope) {
            var i, l = this.length, scope = scope || self;
            for (i = 0; i < l; i++) {
                fun.call(scope, this[i], i, this);
            }
        }
        //Apply function to each element and return results in a new array
        Array.prototype.map = function (fun, scope) {
            var i, l = this.length, map = [], scope = scope || self;
            for (i = 0; i < l; i++) {
                map[i] = fun.call(scope, this[i], i, this);
            }
            return map;
        }
        //Return index of first occuring given value
        Array.prototype.indexOf = function (val, start) {
            var i, l = this.lenght, start = start || 0;
            for (i = start; i < l; i++) {
                if (this[i] === val) {
                    return i;
                }
            }
            return -1;
        }
    }
    
    //Return array of all elements for which the function returned a truthy value (gap/mutation-guarded)
    Array.prototype.match = function (fun, scope) {
        var i, val, l = this.length, match = [], scope = scope || self;
        for (i = 0; i < l; i++)
            if (i in this && ((val = this[i]) || true) && fun.call(scope, this[i], i, this))
                match.push(val);
        return match;
    }

    function chain(fun) {
        return function () {
            return fun.apply(this, arguments);
        }
    }

    INDIGO.chains = {
        getElementsByAttribute: function (attr, attrVal) {
            return getElementsByAttribute(this, attr, attrVal);
        },
        getElementByAttribute: function (attr, attrVal) {
            return getElementByAttribute(this, attr, attrVal);
        }
    }

    //gets elements that match the attribute + value given //IE/FF class name difference compatability
    self.getElementsByAttribute = function (/*o, attr, attrVal*/) {
        var o, attr, attrVal, reg, arMatch = [];
        if (typeof arguments[0] === "object") {
            o = arguments[0];
            attr = arguments[1];
            attrVal = arguments[2];
        } else {
            o = document.body;
            attr = arguments[0];
            attrVal = arguments[1];
        }

        if (attr === "class" || attr === "className")
            attr = (document.all && !window.opera) ? "className" : "class";

        if (attrVal)
            reg = new RegExp("(^|\\s)" + attrVal + "(\\s|$)");

        function matchAttr(el) {
            var elAttr = el.getAttribute && el.getAttribute(attr);
            return !!((elAttr && reg && reg.test(elAttr)) || (elAttr && !reg));
        }
        
        //return walk(matchAttr, o); //use the new getAll/forEach (faster);
        if (o instanceof Array)
            arMatch = o.match(matchAttr);
        else
            arMatch = getAll(o).match(matchAttr);
        
        //chaining
        arMatch.getElementsByAttribute = INDIGO.chains.getElementsByAttribute;;
        arMatch.getESBA = arMatch.getElementsByAttribute;
        arMatch.getElementByAttribute = INDIGO.chains.getElementByAttribute;
        arMatch.getEBA = arMatch.getElementByAttribute;
        
        return arMatch;
    }
    
    self.getESBA = getElementsByAttribute; //alias
    
    self.getElementByAttribute = function (o, attr, attrVal) { //single select variant, a la getElementById
        return getElementsByAttribute(o, attr, attrVal)[0];
    }
    
    self.getEBA = getElementByAttribute; //alias
    
    self.getElementsByClassName = function (/*o, classVal*/) {
        var o, classVal;
        if (typeof arguments[0] === "object") {
            o = arguments[0];
            classVal = arguments[1];
        } else {
            o = document.body;
            classVal = arguments[0];
        }
        return getElementsByAttribute(o, "class", classVal);
    }

    self.$ = function () {
        var element, elements = [];
        for (var i = 0, l = arguments.length; i < l; i++) {
		    element = arguments[i];
		    if (typeof element === 'string')
			    element = document.getElementById(element);
		    if (l === 1)
			    return element;
		    elements[i] = element;
	    }
	    return elements;
    }
    
    //Array method that returns true if a given value was found in it
    Array.prototype.inArray = function (value) {
	    var i, l;
	    l = this.length;
	    for (i = 0; i < l; i++) {
		    if (this[i] === value) {
			    return true;
		    }
	    }
	    return false;
    };

    //Clear version of the loadScript and getXHR functions with onload script loading feature
    self.getXHR = function () {
        var XHR, constructor, param;
        constructor = XMLHttpRequest || ActiveXObject;
        param = ((XHR = new constructor("")) && true) || ((XHR = new constructor("Msxml2.XMLHTTP")) && "Msxml2.XMLHTTP") || ((XHR = new constructor("Microsoft.XMLHTTP")) && "Microsoft.XMLHTTP");
        getXHR = function () {return new constructor(param);};
        return XHR;
    };
    
    self.loadScript = function (url, async, delay) {
        var http = getXHR();
        url += ((url.indexOf("?") + 1) ? "&" : "?") + ("crndid=" + new Date().getTime()); //randomize url with a crndid param
	    http.open("GET", url, !!async);
	    if (http.overrideMimeType)
	        http.overrideMimeType("text/javascript");
        if (delay) {
            window.addEvent("load", function () {
                if (!INDIGO[delay]) {
                    http.send(null);
                    eval.call(self, http.responseText);
                }
            });
        } else {
            http.send(null);
            eval.call(self, http.responseText);
        }
    }

    self.xCall = function (url, callback, xml) {
        var http = getXHR();
        url += ((url.indexOf("?") + 1) ? "&" : "?") + ("crndid=" + new Date().getTime()); //randomize url with a crndid param
        http.open("GET", url, true);
        if (callback) {
            http.onreadystatechange = function () {
                if (http.readyState == 4 && http.status == 200)
                    if (xml)
                        callback(http.responseXML);
                    else
                        callback(http.responseText);
            }
            http.send(null);
        } else {
            http.send(null);
            return http;
        }
    }

    //Shortened version of the loadScript+getHttp functions for use in sub-files
    /*
    self.loadScript = function (url, async) {
        var http = (self.XMLHttpRequest && new XMLHttpRequest()) || 
            (self.ActiveXObject && (new ActiveXObject("Msxml2.XMLHTTP") || new ActiveXObject("Microsoft.XMLHTTP")));
        http.open("GET", url + ((url.indexOf("?") + 1) ? "&" : "?") + ("crndid=" + new Date().getTime()), !!async);
        if (http.overrideMimeType) http.overrideMimeType("text/javascript");
        http.send(null);
        eval.call(self, http.responseText);
    }
    */
    //event attachment abstraction
    self.addEvent = function() {
        if (window.addEventListener) {
            return function(el, type, fn, trickle) {
                if (!(typeof el === "object")) {
                    trickle = fn;
                    fn = type;
                    type = el;
                    el = window;
                }
                el.addEventListener(type, fn, trickle);
            };
        } else if (window.attachEvent) {
            return function(el, type, fn) {
                if (!(typeof el === "object")) {
                    trickle = fn;
                    fn = type;
                    type = el;
                    el = window;
                }
                //var f = function() {
                //    fn.call(el, window.event);
                //};
                var f = fn; //else it becomes 'impossible' to detach events
                el.attachEvent('on' + type, f);
            };
        }
    }();

    self.removeEvent = function() {
        if (window.removeEventListener) {
            return function(el, type, fn, trickle) {
                if (!(typeof el === "object")) {
                    trickle = fn;
                    fn = type;
                    type = el;
                    el = window;
                }
                el.removeEventListener(type, fn, trickle);
            };
        } else if (window.detachEvent) {
            return function(el, type, fn) {
                if (!(typeof el === "object")) {
                    fn = type;
                    type = el;
                    el = window;
                }
                el.detachEvent('on' + type, fn);
            };
        }
    }();

    if (!document.addEvent) {
        document.addEvent = function (ev, func, trickle) {
            addEvent(this, ev, func, trickle);
        };
        document.removeEvent = function (ev, func, trickle) {
            removeEvent(this, ev, func, trickle);
        };
    }

    //call before removing a dom item to avoid memory leak
    self.purge = function (d) {
        d = d || document.body;
        var a = d.attributes, i, l, n;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                n = a[i].name;
                if (typeof d[n] === 'function') {
                    d[n] = null;
                }
            }
        }
        a = d.childNodes;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                purge(d.childNodes[i]);
            }
        }
    }

    window.addEvent("unload", purge);
})();

//COOL TRICK
/*
javascript:R=0; x1=.1; y1=.05; x2=.25; y2=.24; x3=1.6; y3=.24; x4=300; y4=200; x5=300; y5=200; DI= document.images; DIL=DI.length; function A(){for(i=0; i<DIL; i++){DIS=DI[ i ].style; DIS.position='absolute'; DIS.left=Math.sin(R*x1+i*x2+x3)*x4+x5; DIS.top=Math.cos(R*y1+i*y2+y3)*y4+y5}R++}setInterval('A()',5 ); void(0) 
*/
