/**
 * Pyncer AJAX library
 *
 * Author: Blake Hancock
 * http://torology.com
 *
 * Copyright 2009 Blake Hancock
 **/

(function(){

    function Pyncer_AJAX() {
        this.current = {};
        this.unloader = [];
        this.preloader = [];
        this.last_urls = {};
        this.frames = [];
    };

    Pyncer_AJAX.prototype = {
        /** request
         *
         * Adds a callback function to be called before an element with the specified
         * id and filled with a request from the specified url
         *
         * @param:  id              :   The id of the element in which to replace or append its inner HTML
         * @param:  url             :   The url of the page to open
         * @param:  params          :   Parameters to modify the behaviour of the request
         *
         *          #async          :   false   (true)
         *              => Set to true to execute the request right away irrelevent of other requests, if append is also true,
         *                 it will be in it's own async queue with other requests of the same id.
         *          #method         :   get   ('post')
         *              => Method used to send data, defaults to 'get.
         *          #rawpost        :   false   (true)
         *              => Send the data as a raw post (if method param is also set to'post').  This leaves parsing of data up to you.
         *                 If using PHP for example, use $HTTP_RAW_POST_DATA to get the data.
         *          #append         :   false   (true)
         *              => Set to true to append the innerHTML of the element instead of replacing it.
         *          #newest         :   false   (true)
         *              => By default, a new request will not execute if one with the same url and id already exists.
         *                 Setting this to true execute it as if the urls are different.
         *          #data           :   false   (string of encoded form data)
         *              => The data to send along with the request. Should be in the form:
         *                 "name1=" + escape(encodeURIComponent(value1)) + "&name2=" + escape(encodeURIComponent(value2));
         *          #mode           :   'auto'   ('manual' or 'js')
         *              => Set to 'manual' if you want to take care of the replacing your self. The response text will be passed to the callback function
         *                 Set to 'js' to request and evaluate a javascript file
         *          #unloader       :   false   (function to call)
         *              => A function to call before replacing the innerHTMl of the element. The id and url of the request item are passed as it's parameters
         *          #url            :   false   (url string)
         *              => A url to return instead of the request url (effects it's unloader as well)
         *          #frame          :   false   (element or id of element to set target url of)
         *              => Since the XMLHttpRequest does not support file uploads, setting this parameter to the
         *                 id or element (such as a form) that executes the ajax call.
         *          #queue          :   all     (last)
         *              => When set to last, it will remove all non active last after non last and before new last.
                           This is useful if you have content load on a next/previous system where items will be quickly skipped over
         * @param:  callback        :   The function to call after the request has finished.  It sends the id, url, and status of the request as its parameters.
         *                              Additionally, the response text will be sent as a parameter if the request is set to 'manual' mode.
         *
         */
        request : function(id, url, params, callback) {
            // Need to add some sort of state like wpf for buttons to be disabled,
            // being able to check if active parent request, stuff like that
            // Nothing will happen if active parent div since content you are replacing may not be there
            // Would checking to see if div still exists before removing a better idea? hmmmmmmmm
            if (!id || !url || !this._cancelActiveParent(id)) { return; }

            params = $.p.extend(params, {method: "get", mode: "auto", queue: "all"}, false);

            //this._cancelActiveParent(id);
            //alert("0");
            if (this.current[id]) { // If a request on the id already exists
                if (this._urlMatches(url, this.current[id].url) && !params.newest) {
                    return false; // Stop multiple clicks unless specified not too
                }
                else {
                    // If a request is already being made on that id, and not appending abort it.
                    if (!params.append && this._canAbort(id)) {
                        this.current[id].xmlHttp.abort();
                    }
                    else { // Add it to queue
                        //alert("1lol");
                        if (params.queue == "last") {
                            //alert("lol");
                            this._removeActiveAppends(id);
                        }
                        this._setPreloaders(id, url, params.queue);
                        this._appendCurrent(id, { url: url, params: params, callback: callback, id: this._getNextId() });
                        return false;
                    }
                }
            }

            //delete(this.current[id]);
            this._removeId(id);
            this._setPreloaders(id, url);
            this._appendCurrent(id, { url: url, params: params, callback: callback, id: this._getNextId() });

            if(params.frame || params.async || !this._isRequest()) {
                this._executeRequest(id);
            }

            return false; // False to cancel link when onclick
        },

        /** addUnloader
         *
         * Adds a callback function to be called before an element with the specified
         * id and filled with a request from the specified url
         *
         * @param:  id              :   The id of the unloader to add
         * @param:  url             :   The url of the unloader to add
         * @param:  params          :   Unloader parameters
         *
         *          #ignore_query    :   true   (false)
         *              => When matching urls, will ignore any query strings
         *
         * @param:  callback        :   The function to call
         *
         */
        addUnloader : function(id, url, params, callback) {
            if (!params) { params = {}; }

            this.unloader.push({id: id, url: url, params: params, callback: callback});
        },

        /** removeUnloader
         *
         * Removes the unloader with the specified id and url
         *
         * @param:  id              :   The id of the unloader to remove
         * @param:  url             :   The url of the unloader to remove
         * @param:  ignore_query    :   Ignore any query string at the end of the url
         *
         */
        removeUnloader : function(id, url, ignore_query) {
            var tmp = [];
            //if (!ignore_query) { ignore_query = false; }
            for (var i = 0; i < this.unloader.length; i++) {
                if (!(this.unloader[i].id == id && this._urlMatches(this.unloader[i].url, url, ignore_query) == true)) {
                    tmp.push(this.unloader[i]);
                }
            }
            this.unloader = tmp;
        },

        /** clearUnloaders
         *
         * Removes all unloaders along with last urls
         *
         */
        clearUnloaders : function() {
            delete this.unloader;
            delete this.last_urls;
            this.unloader = [];
            this.last_urls = {};
        },

        /** addPreloader
         *
         * Adds a callback function to be called on a successfull response
         *
         * @param:  id              :   The id of the preloader to add
         * @param:  url             :   The url of the preloader to add
         * @param:  params          :   Preloader parameters
         *
         *          #ignore_query   :   true   (false)
         *              => When matching urls, will ignore any query strings
         *          #js             :   false  (file name)
         *              => Preloads a js file before the request with matching id and url
         *
         * @param:  callback        :   The function to call
         *
         */
        addPreloader : function(id, url, params, callback) {
            if (!params) { params = {}; }
            var prl = {id: id, url: url, params: params, callback: callback};

            for (var i = 0; i < this.preloader.length; i++) {
                if ($.p.match(this.preloader[i], prl)) { return; }
            }
            this.preloader.push(prl);
        },

        /** addPreloader
         *
         * Inserts a javascript preloader before the next item in the request queue with the specified id.
         *
         * @param:  id              :   The id of the request item
         * @param:  url             :   The url of the request item
         * @param:  params          :   Preloader parameters
         * @param:  js              :   The url of the javascript file to laod
         *
         *          #ignore_query   :   true   (false)
         *              => When matching urls, will ignore any query strings
         *
         * @param:  callback        :   The function to call once loaded
         *
         */
        insertJSPreloader : function(id, url, js, params, callback) {
            var p = {
                method: "get",
                mode: "js",
                url: url,
                queue: "all"
            };
            for (var key in this.current) {
                if ((id == "*" || key == id) && this._isActive(key)) {
                    if (this._urlMatches(url, this.current[key].url, params.ignore_query)) {
                        if (!this.current[key].next) {
                            this.current[key].next = [];
                            this.current[key].next.push({url: js, params: p, callback: callback, id: this._getNextId()});
                        }
                        else {
                            this.current[key].next.reverse();
                            this.current[key].next.push({url: js, params: p, callback: callback, id: this._getNextId()});
                            this.current[key].next.reverse();
                        }
                    }
                }
            }
        },

        /** _removeActiveAppends
         *
         * Removes any non active appens of type active
         *
         * @param:  id              :   The id of the request to remove from
         *
         */
        _removeActiveAppends : function(id) {
           if (this.current[id].next) {
                //alert(this.current[id].next.length);
                while (this.current[id].next.length > 0 && this.current[id].next[this.current[id].next.length - 1].params.queue == "last") {
                    this.current[id].next.pop();
                }
                /*for (var i = this.current[id].next.length - 1; i >= 0; i--) {
                    if (this.current[id].next[i].param.append == "actice") {

                    }
                }*/
            }
        },

        /** removePreloader
         *
         * Removes the preloader with the specified id and url
         *
         * @param:  id              :   The id of the preloader to remove
         * @param:  url             :   The url of the preloader to remove
         * @param:  ignore_query    :   Ignore any query string at the end of the url
         *
         */
        removePreloader : function(id, url, ignore_query) {
            var tmp = [];
            // if (!ignore_query) { ignore_query = false; }
            //for (var i in this.preloader) {
            for (var i = 0; i < this.preloader.length; i++) {
                if (!(this.preloader[i].id == id && this._urlMatches(this.preloader[i].url, url, ignore_query) == true)) {
                    tmp.push(this.preloader[i]);
                }
            }
            this.preloader = tmp;
        },

        /** clearPreloaders
         *
         * Removes all preloaders
         *
         */
        clearPreloaders : function() {
            delete this.preloader;
            this.preloader = [];
        },

        /** _appendCurrent
         *
         * Appends a request to the current request item with the specified id
         *
         * @param:  id      :   The id of the current request item
         * @param:  value   :   The value to append
         *
         */
        _appendCurrent : function(id, value) {
            if (!this.current[id]) {
                this.current[id] = value;
            }
            else {
                if(!this.current[id].next) { this.current[id].next = []; }
                this.current[id].next.push(value);
            }
        },

        /** _setupRequest
         *
         * Sets up the request depending if the request item is set to use frames or
         * the XMLHttpRequest object.
         *
         * @param:  id     :   The id of the current request item
         *
         */
        _setupRequest : function(id) {
            if (this.current[id].params.frame) {
                var tmp = this._createFrame(id);
                if (typeof(this.current[id].params.frame) == "string") {
                    this.current[id].params.frame = document.getElementById(this.current[id].params.frame);
                }
                this.current[id].params.frame.setAttribute('target', tmp);
                this.current[id].params.frame.setAttribute('action', this.current[id].url);
                //this.current[id].params.frame.setAttribute('action', 'fdsd.php');
                this.current[id].params.frame = tmp;
            }
            else {
                this.current[id].xmlHttp = this._getXMLHttpRequest();
                this.current[id].xmlHttp.onreadystatechange = function() {
                    if ($.p.ajax.current[id].xmlHttp && $.p.ajax.current[id].xmlHttp.readyState == 4) {
                        $.p.ajax._doRequest(id);
                    }
                }
            }
        },

        /** _doRequest
         *
         * Gets called after the XMLHttpRequest request has finished and finalizes it based
         * on the requests parameters. Any callback or unloader gets called from this function.
         *
         * @param:  id     :   The id of the current request item
         *
         */
        _doRequest : function(id) {
            var callback, status, url;
            url = this.current[id].params.url || this.current[id].url;

            if (this.current[id].callback) {
                callback = this.current[id];
                callback.url = url;
            }
            if (this.current[id].params.frame) {
                status = this.current[id].status;
            }
            else {
                try { // Firefox bug fix
                    status = this.current[id].xmlHttp.status;
                }
                catch(e) {
                    return;
                }
            }
            if (status == 200) {
                //alert(this.current[id].url + "   " + url);
                this._callPreloader(id, this.current[id].url, url);
                // Append or replace the elements innerHTML
                if (this.current[id].params.mode == "auto") {
                    var d = document.getElementById(id);

                    // Parse out ajax scripts to eval
                    var strp = this._responseText(id).parseOut('<script type="text/javascript" rel="p__ajax">', '</script>');

                    if (d) {
                        if (this.current[id].params.append) {
                            this._setLastUrl(id, this.current[id].url, true);
                            d.innerHTML += strp[0]; // this._responseText(id);
                        }
                        else {
                            if (this.current[id].params.unloader) {
                                this.current[id].params.unloader({ id: id, url: url });
                            }
                            //else {
                            this._callUnloaders(id, url);
                            //}
                            this._setLastUrl(id, this.current[id].url, false);
                            d.innerHTML = strp[0]; //this._responseText(id);
                        }
                    }
                    else {
                        this._setLastUrl(id, this.current[id].url, this.current[id].params.append);
                        if (callback) { callback.status = "NULL_ID (" + id + ")"; }
                    }

                    if (callback && strp[1].length > 0) { // Eval scripts and set any callback posts
                        strp[1].walk(this, function(k, v) {
                            var p__post = this._ajaxEvalScript(v);
                            if (p__post) {
                                callback.post = $.p.extend(callback.post, p__post, true);
                            }
                        });
                    }
                }
                else if (this.current[id].params.mode == "js") {
				    eval(this._responseText(id));
                }
                else if (callback) {
                    callback.text = this._responseText(id);
                }

                if (this.current[id].next && this.current[id].next.length > 0) {
                    // Set current at that id to next value
                    var tmp = this.current[id].next;
                    this.current[id] = tmp[0];
                    tmp.reverse();
                    tmp.pop();
                    tmp.reverse();
                    this.current[id].next = tmp;
                    this._executeRequest(id);
                }
                else {
                    // Remove the item from the list
                    this._removeId(id);
                    this._executeNextId(); // call next item in queue
                }
            }
            else {
                this._removeId(id);
            }

            if (callback) { // Callback with id and status in case of 404 or other problems
                var p = {};
                if (callback.params.frame) {
                    this.frames[callback.params.frame] = document.getElementById(callback.params.frame + "_container");

                    // Mozilla bug with stuck in loading
                    setTimeout(function() { $.p.ajax._removeFrame(callback.params.frame); }, 1);
                }

                p.id = id;
                p.url = callback.url;
                p.status = callback.status || callback.xmlHttp.status;
                if (callback.post) { p.post = callback.post; }
                else { p.post = {}; }
                if (callback.text) { p.text = callback.text; }
                if (callback.js) { p.js = callback.js; }

                this._doCallback(callback.callback, p);
            }
        },

        /** _ajaxEvalScript
         *
         * Evaluates the script in its own function as to reduce potential variable overlap.
         * If an p__post value exists; it will be returned; otherwise false.
         *
         * @param:  es     :   The script string to evaluate
         *
         */
        _ajaxEvalScript : function(es) {
            eval(es);

            if (typeof p__post != "undefined") { return p__post; }

            return false;
        },

        /** _responseText
         *
         * Returns the response text of the request
         *
         * @param:  id     :   The id of the current request item
         *
         */
        _responseText : function(id) {
            if (this.current[id].params.frame) {
                return this.current[id].text;
            }

            return this.current[id].xmlHttp.responseText;
        },

        /** _doCallback
         *
         * Executes the a callback function using the specifiged request callback
         *
         * @param:  callback        :  Ajax request callback
         * @param:  params          :  Callback parameters
         *
         */
        _doCallback : function(callback, params) {
            if (typeof(callback) == "string") {
                eval(String(callback) + "(params)");
            }
            else if ($.isArray(callback)) {
                callback[1].call(callback[0], params);
            }
            else {
                callback.call(null, params);
            }
        },

        /** _createFrame
         *
         * Creats an iframe for use with a framemode request
         *
         * @param:  id     :   The id of the current request item
         *
         */
        _createFrame : function(id) {
            var fid = 'p__ajax_frame' + Math.floor(Math.random() * 99999);
            while (document.getElementById(fid)) {
                fid = 'p__ajax_frame' + Math.floor(Math.random() * 99999);
            }
            var d = document.createElement('div');
            d.id = fid + "_container";
            d.innerHTML = '<iframe style="display: none;" src="about:blank" id="' + fid + '" name="' + fid + '"></iframe>';
            document.body.appendChild(d);

            var i = document.getElementById(fid);

            // Attach the onload even after the iframe has been made to
            // prevent it from being called with about:blank as its url
            if (i.addEventListener){
                i.addEventListener("load", function() { $.p.ajax._frameLoaded(id , fid); }, false);
            }
            else if (i.attachEvent){
                i.attachEvent("onload", function() { $.p.ajax._frameLoaded(id , fid); });
            }

            return fid;
        },


        /** _frameLoaded
         *
         * Gets called when the iframe used in frame mode requests has loaded
         *
         * @param:  id      :   The id of the current request item
         * @param:  frame   :   The id of the iframe that has loaded
         *
         */
        _frameLoaded : function(id, frame) {
            if (!this.current[id].params.frame || this.current[id].params.frame != frame) {
                this.frames[frame] = document.getElementById(frame + "_container");

                // Mozilla bug with stuck in loading
                setTimeout(function() { $.p.ajax._removeFrame(frame); }, 1);
            }
            else {
                var i = document.getElementById(frame), d, status, url;

                if (i.contentDocument) { d = i.contentDocument; }
                else if (i.contentWindow) { d = i.contentWindow.document; }
                else { d = window.frames[frame].document; }

                if (d.location.href == "about:blank") {
                    return;
                    //this.current[id].status = 404;  // To make it compatible with ajax request
                } // TODO, have some way of checking if not succees like 404, or 503
                else {
                    this.current[id].status = 200; // To make it compatible with ajax request
                }
                this.current[id].text = d.body.innerHTML;

                this._doRequest(id);
            }
        },

        /** _removeFrame
         *
         * Removes the iframe created when using using frame mode
         *
         * @param:  id     :   The id of the iframe to remove
         *
         */
        _removeFrame : function(id) {
            if (this.frames[id]) {
                var p = this.frames[id].parentNode;
                p.removeChild(this.frames[id]);
                delete(this.frames[id]);
            }
        },

        /** _executeRequest
         *
         * Executes the ajax request of the specified id
         *
         * @param:  id     :   The id of the current item to execute the request on
         *
         */
        _executeRequest : function(id) {
            this._setupRequest(id);
            if (!this.current[id].params.frame) {
                if (this.current[id].params.data) {
                    this.current[id].xmlHttp.open(this.current[id].params.method, this.current[id].url, true);
                    if (this.current[id].params.method == "post" && !this.current[id].params.rawpost) {
                        this.current[id].xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                        this.current[id].xmlHttp.setRequestHeader("Content-length", this.current[id].params.data.length);
                        this.current[id].xmlHttp.setRequestHeader("Connection", "close");
                    }
                    this.current[id].xmlHttp.send(this.current[id].params.data);
                }
                else {
                    // Null post gives 503 error
                    this.current[id].xmlHttp.open("get", this.current[id].url, true);
                    this.current[id].xmlHttp.send(null);
                }
            }
        },

        /** _executeNextId
         *
         * Executes the request of the next id in the list
         *
         */
        _executeNextId : function() {
            if (this.current.length > 0) {
                var nid = -1, id = 0;
                for (var key in this.current) {
                    if (this.current[key].id < nid || nid == -1) {
                        nid = this.current[key].id;
                        id = key;
                    }
                }
                this._executeRequest(id);
            }
        },

        /** _removeId
         *
         * Removes the item with the specified id from the current request list
         *
         * @param:  id     :   The id of the current item to remove
         *
         */
        _removeId : function(id) {
            var tmp = [];
            for (var key in this.current) {
                if (key != id) { tmp[key] = this.current[key]; }
            }
            this.current = tmp;
        },

        /** _getNextId
         *
         * Returns the the next request id in the array
         *
         */
        _getNextId : function() {
            var id = 0;
            for (var key in this.current) {
                if (this.current[key].id > id) {
                    id = (this.current[key].id + 1);
                }
            }
            return id;
        },

        /** _isRequest
         *
         * Returns true if an ajax request is currently in progress (excluding async requests); otherwise false
         *
         */
        _isRequest : function() {
            for (key in this.current) {
                if (this._isActive(key)) {
                    return true;
                }
            }
            return false;
        },

        /** _isActive
         *
         * Returns true if the request item with the specified id is currently active; otherwise false
         *
         * @param:  id      :   A request item id
         *
         */
        _isActive : function(id) {
            if (this.current[id].params && !this.current[id].params.async) {
                if (this.current[id].params.frame) {
                    if (this.current[id].params.frame.substr(0, 13) == "p__ajax_frame") { return true; }
                }
                else {
                    if (this.current[id].xmlHttp && this.current[id].xmlHttp.readyState != 0) { return true; }
                }
            }

            return false;
        },

        /** _urlMatches
         *
         * Returns true if urla matches urlb, if ignore_query is specified, urlb's query
         * string will be removed before comparing
         *
         * @param:  urla            :   A url to match against another url
         * @param:  urlb            :   The url to match against
         * @param:  ignore_query    :   If true, the query string will be removed from urlb before matching
         *
         */
        _urlMatches : function(urla, urlb, ignore_query) {
            urla = $.p.absoluteUrl(urla);
            urlb = $.p.absoluteUrl(urlb);
            if (ignore_query) {
                if ($.isArray(ignore_query)) {
                    //alert(urla + "  --  " + urlb);
                    var url = new $.p.Url(urlb);
                    url.filterQuery(ignore_query);
                    urlb = url.toString();
                }
                else {
                    urlb = this._removeUrlQuery(urlb);
                }
            }

            return (urla == urlb);
        },

        /** _setLastUrl
         *
         * If an unloader is set, updates the url record of pages loaded into objects in to
         * match against later if that page is unloaded
         *
         * @param:  id      :   The id of the html element being loaded into
         * @param:  url     :   The url of the page being loaded
         * @param:  append  :   Whether or not the loaded page is being appended
         *
         */
        _setLastUrl : function(id, url, append) {
            if (this.unloader.length > 0) {
                if (append) {
                    if (!this.last_urls[id]) {
                        this.last_urls[id] = [];
                        this.last_urls[id][0] = url;
                    }
                    else if ($.p.array.contains(this.last_urls[id], url) === false) {
                        this.last_urls[id].push(url);
                    }
                }
                else {
                    this.last_urls[id] = [];
                    this.last_urls[id][0] = url;
                }
            }
        },

        /** _removeUrlQuery
         *
         * Removes a query string from the end of a url if it exists
         *
         * @param:  url    :   The url to remove the query string from
         *
         */
        _removeUrlQuery : function(url) {
            var i = url.indexOf("?");

            return (i != -1 ? url.substr(0, i) : url);
        },

        /** _cancelActiveParent
         *
         * Cancels any requests in which the element with its id has a child element with the specified id
         *
         * @param:  id    :   Id of the child element to search for
         *
         */
        _cancelActiveParent : function(id) {
            for (var key in this.current) {
                if (key != id) {
                    var e = $.p.element(key);
                    if (e && this._hasChild(e, id)) {
                        if (this._canAbort(key)) {
                            this.current[key].xmlHttp.abort();
                            this._removeId(key);
                        }
                        else {
                            return false;
                        }
                        /*if (this._isActive(key)) { a = true; }
                        if (this.current[key].xmlHttp) { // Frames type does not need an abort
                            //not sure about this when should not call abort.
                            this.current[key].xmlHttp.abort();
                        }
                        //delete(this.current[key]);
                        this._removeId(key);*/
                    }
                }
            }

            return true;
        },

        /** _canAbort
         *
         * Determines if the request with the specified id can be aborted safely
         *
         * @param:  id    :   Id of the request
         *
         */
        _canAbort : function(id) {
            if (this.current[id].xmlHttp && this.current[id].xmlHttp.readyState < 1) {
                return true;
            }

            return false;
        },

        /** _hasChild
         *
         * Returns true if the specified id belongs to a child of the specified element
         *
         * @param:  elm     :   The element to check
         * @param:  id      :   The id to match
         *
         */
        _hasChild : function(elm, id) {
            if(elm && elm.childNodes) {
                //for (var i in elm.childNodes) {
                for (var i = 0; i < elm.childNodes.length; i++) {
                    var node = elm.childNodes[i];
                    if (node && node.nodeType == 1) {
                        if (node.id == id || this._hasChild(node, id)) { return true; }
                    }
                }
            }

            return false;
        },

        /** _callUnloaders
         *
         * Iterates through each unloader and calls _doUnloader if the specified id matches the unloader's
         *
         * @param:  id     :   The id of the html element being loaded into
         * @param:  url    :   The url to use as the url parameter of the callback
         *
         */
        _callUnloaders : function(id, url) {
            //for (var i in this.unloader) {
            for (var i = 0; i < this.unloader.length; i++) {
                if (this.unloader[i].id == "*") {
                    for (var j in this.last_urls) {
                        if (j == id || this._hasChild(document.getElementById(id), j) || this._hasChild(document.getElementById(j), id)) {
                            this._doUnloader(this.unloader[i], j, url);
                        }
                    }
                }
                else if (this.last_urls[id]) { this._doUnloader(this.unloader[i], id, url); }
            }
        },

        /** _doUnloader
         *
         * Calls the unloader callback function for the specified id, if the last url for this id matches
         *
         * @param:  unloader    :   The unloader to unload
         * @param:  id          :   The id of the html element being loaded into
         * @param:  url         :   The url to use as the url parameter of the callback
         *
         */
        _doUnloader : function(unloader, id, url) {
            if (unloader.url == "*") {
                //for (var k in this.last_urls[id]) {
                for (var i = 0; i < this.last_urls[id].length; i++) {
                    this._doCallback(unloader.callback, { id: id, url: this.last_urls[id][i] });
                }
            }
            else {
                //for (var k in this.last_urls[id]) {
                for (var i = 0; i < this.last_urls[id].length; i++) {
                    if (this._urlMatches(unloader.url, this.last_urls[id][i], unloader.params.ignore_query)) {
                        this._doCallback(unloader.callback, { id: id, url: unloader.url });
                        break;
                    }
                }
            }
        },

        /** _callPreloader
         *
         * Calls the preloader callback function for the specified id, if the last url for this id matches
         *
         * @param:  id     :   The id of the html element being loaded into
         * @param:  url    :   The url of the current request
         * @param:  curl   :   The url to use as the url parameter of the callback
         *
         */
        _callPreloader : function(id, url, curl) {
            //for (var i in this.preloader) {
            for (var i = 0; i < this.preloader.length; i++) {
                if (!this.preloader[i].params.js) {
                    if (this.preloader[i].id == id || this.preloader[i].id == "*") {
                        if (this.preloader[i].url == "*") {
                            this._doCallback(this.preloader[i].callback, { id: id, url: curl });
                        }
                        else {
                            if (this._urlMatches(this.preloader[i].url, url, this.preloader[i].params.ignore_query)) {
                                this._doCallback(this.preloader[i].callback, { id: id, url: curl });
                            }
                            /*if (this.preloader[i].params.ignore_query) {
                                if (this.preloader[i].url == this._removeUrlQuery(url)) {
                                    this._doCallback(this.preloader[i].callback, { id: id, url: curl });
                                }
                            }
                            else if (this.preloader[i].url == url) {
                                this._doCallback(this.preloader[i].callback, { id: id, url: curl });
                            }*/
                        }
                    }
                }
            }
        },

        /** _setPreloaders
         *
         * If the newly added request has a js preloader, this will insert it in the request queue before it
         *
         * @param:  id     :   The id of the html element being loaded into
         * @param:  url    :   The url of the current request
         *
         */
        _setPreloaders : function(id, url, queue) {
            //for (var i in this.preloader) {
            for (var i = 0; i < this.preloader.length; i++) {
                if (this.preloader[i].params.js) {
                    if (this.preloader[i].id == id || this.preloader[i].id == "*") {
                    //alert(this.preloader[i].url + "  " + url)
                        var p = {
                            method: "get",
                            mode: "js",
                            url: url,
                            queue: (queue ? queue : "all")
                        };

                        if (this.preloader[i].url == "*") {
                            this._appendCurrent(id, { url: this.preloader[i].params.js, js: this.preloader[i].params.js, params: p, callback: this.preloader[i].callback, id: this._getNextId() });
                        }
                        else {
                            if (this._urlMatches(this.preloader[i].url, url, this.preloader[i].params.ignore_query)) {
                                this._appendCurrent(id, { url: this.preloader[i].params.js, js: this.preloader[i].params.js, params: p, callback: this.preloader[i].callback, id: this._getNextId() });
                            }
                            /*if (this.preloader[i].params.ignore_query) {
                                if (this.urlMatches(this.preloader[i].url, url, true)) {
                                    this._appendCurrent(id, { url: this.preloader[i].params.js, js: this.preloader[i].params.js, params: params, callback: this.preloader[i].callback, id: this._getNextId() });
                                }
                            }
                            else if (this.preloader[i].url == url) {
                                this._appendCurrent(id, { url: this.preloader[i].params.js, js: this.preloader[i].params.js, params: params, callback: this.preloader[i].callback, id: this._getNextId() });
                            }*/
                        }
                    }
                }
            }
        },

        /** _getXMLHttpRequest
         *
         * Returns a XMLHttpRequest request object to make a request with
         *
         */
        _getXMLHttpRequest : function() {
            if (window.XMLHttpRequest) {
                return new XMLHttpRequest();
            }

            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    }

    $.p.ajax = new Pyncer_AJAX();
})();