/**

 * Subsys_JsHttpRequest_Js: JavaScript DHTML data loader.

 * (C) 2005 Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/

 *

 * This library is free software; you can redistribute it and/or

 * modify it under the terms of the GNU Lesser General Public

 * License as published by the Free Software Foundation; either

 * version 2.1 of the License, or (at your option) any later version.

 * See http://www.gnu.org/copyleft/lesser.html

 *

 * Do not remove this comment if you want to use script!

 * Не удаляйте данный комментарий, если вы хотите использовать скрипт!

 *

 * This library tries to use XMLHttpRequest (if available), and on 

 * failure - use dynamically created <script> elements. Backend code

 * is the same for both cases.

 *

 * @author Dmitry Koterov 

 * @version 3.34

 */



function Subsys_JsHttpRequest_Js() { this._construct() }

(function() { // to create local-scope variables

    var COUNT       = 0;

    var PENDING     = {};

    var CACHE       = {};



    // Called by server script on data load.

    Subsys_JsHttpRequest_Js.dataReady = function(id, text, js) {

        var undef;

        var th = PENDING[id];

        delete PENDING[id];

        if (th) {

            delete th._xmlReq;

            if (th.caching) CACHE[th.hash] = [text, js];

            th._dataReady(text, js);

        } else if (typeof(th) != typeof(undef)) {

            alert("ScriptLoader: unknown pending id: "+id);

        }

    }

    

    Subsys_JsHttpRequest_Js.prototype = {

        // Standard properties.

        onreadystatechange: null,

        readyState:         0,

        responseText:       null,

        responseXML:        null,

        status:             200,

        statusText:         "OK",

        

        // Additional properties.

        session_name:       "PHPSESSID",  // set to SID cookie or GET parameter name

        responseJS:         null,         // JavaScript response array/hash

        caching:            false,        // need to use caching?

        fallbackToScript:   false,



        // Internals.

        _span:              null,

        _id:                null,

        _xmlReq:            null,

        _openArg:           null,

        _reqHeaders:        null,



        dummy: function() {}, // empty function



        abort: function() {

            if (this._xmlReq) return this._xmlReq.abort();

            if (this._span) {

                this.readyState = 0;

                if (this.onreadystatechange) this.onreadystatechange();

                this._cleanupScript();

            }

        },

            

        open: function(method, url, asyncFlag, username, password) {

            this._openArg = {

                'method':    method,

                'url':       url,

                'asyncFlag': asyncFlag,

                'username':  username != null? username : '',

                'password':  password != null? password : ''

            };

            this._id = null;

            this._xmlReq = null;

            this._reqHeaders = [];

            return true;

        },

        

        send: function(content) {

            var id = (new Date().getTime()) + "" + COUNT++;

            

            // Build QUERY_STRING from query hash.

            var query = this._hash2query(content);



            // Append SID to original URL now.

            var url = this._openArg.url;

            var sid = this._getSid();

          //  if (sid) url += (url.indexOf('?')>=0? '&' : '?') + this.session_name + "=" + this.escape(sid);



            // Solve hash BEFORE appending ID.

            var hash = this.hash = url + '?' + query;

            if (this.caching && CACHE[hash]) {

                var c = CACHE[hash];

                this._dataReady(c[0], c[1]);

                return false;

            }



            // Try to use XMLHttpRequest.

            this._xmlReq = this._obtainXmlReq(id, url);



            // Pass data in URL (GET, HEAD etc.) or in request body (POST)?

            var hasSetHeader = this._xmlReq && (window.ActiveXObject || this._xmlReq.setRequestHeader); 

            var href, body;

            var method = (""+this._openArg.method).toUpperCase();

            if (this._xmlReq && hasSetHeader && method == "POST") {

                // Use POST method. Pass query in request body.

                // Opera 8.01 does not support setRequestHeader, so no POST method.

                this._openArg.method = "POST";

                href = url;

                body = query;

            } else {

                if (method != 'GET' && !this.fallbackToScript && query.length > 2000) {

                    throw 'Cannot use XMLHttpRequest nor Microsoft.XMLHTTP for long POST query: object not implemented or disabled in browser.';

                }

                this._openArg.method = "GET";

                href = url + (url.indexOf('?')>=0? '&' : '?') + query;

                body = null;

            }



            // Append ID: a=aaa&b=bbb&<id>

            href = href + (href.indexOf('?')>=0? '&' : '?') + id;



            // Save loading script.

            PENDING[id] = this;



            if (this._xmlReq) {

                // Open request now & send it.

                // In XMLHttpRequest mode request URL MUST be ended with "<id>-xml".

                var a = this._openArg;

                this._xmlReq.open(a.method, href+"-xml", a.asyncFlag, a.username, a.password);

                if (hasSetHeader) {

                    // Pass pending headers.

                    for (var i=0; i<this._reqHeaders.length; i++)

                        this._xmlReq.setRequestHeader(this._reqHeaders[i][0], this._reqHeaders[i][1]);

                    // Set non-default Content-type. We cannot use 

                    // "application/x-www-form-urlencoded" here, because 

                    // in PHP variable HTTP_RAW_POST_DATA is accessible only when 

                    // enctype is not default (e.g., "application/octet-stream" 

                    // is a good start). We parse POST data manually in backend 

                    // library code.

                    this._xmlReq.setRequestHeader('Content-Type', 'application/octet-stream');

                }

                // Send the request.

                return this._xmlReq.send(body);

            } else {

                // Create <script> element and run it.

                this._obtainScript(id, href);

                return true;

            }

        },



        getAllResponseHeaders: function() {

            if (this._xmlReq) return this._xmlReq.getAllResponseHeaders();

            return '';

        },

            

        getResponseHeader: function(label) {

            if (this._xmlReq) return this._xmlReq.getResponseHeader(label);

            return '';

        },



        setRequestHeader: function(label, value) {

            // Collect headers.

            this._reqHeaders[this._reqHeaders.length] = [label, value];

        },





        //

        // Internal functions.

        //



        // Constructor.

        _construct: function() {},



        // Do all work when data is ready.

        _dataReady: function(text, js) { with (this) {

            if (text !== null || js !== null) {

                readyState = 4;

                responseText = responseXML = text;

                responseJS = js;

            } else {

                readyState = 0;

                responseText = responseXML = responseJS = null;

            }

            if (onreadystatechange) onreadystatechange();

            _cleanupScript();

        }},



        // Create new XMLHttpRequest object.

        _obtainXmlReq: function(id, url) {

            // If url.domain specified and differ from current, cannot use XMLHttpRequest!

            // XMLHttpRequest (and MS ActiveX'es) cannot work with different domains.

            var p = url.match(new RegExp('^[a-z]+://(.*)', 'i'));

            if (p) {

                var curHost = document.location.host.toLowerCase();

                if (p[1].substring(0, curHost.length).toLowerCase() == curHost) {

                    url = p[1].substring(curHost.length, p[1].length);

                } else {

                    return null;

                }

            }

            

            // Try to use built-in loaders.

            var req = null;

            if (window.XMLHttpRequest) {

                try { req = new XMLHttpRequest() } catch(e) {}

            } else if (window.ActiveXObject) {

                try { req = new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}

                if (!req) try { req = new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {}

            }

            if (req) {

                var th = this;

                req.onreadystatechange = function() { 

                    var s = req.readyState;

                    if (s == 4) {

                        // Avoid memory leak by removing closure.

                        req.onreadystatechange = th.dummy;

                        // Remove possible junk from response.

                        var responseText = req.responseText;

                        try {

                            // Call associated dataReady().

                            eval(responseText);

                        } catch (e) {

                            Subsys_JsHttpRequest_Js.dataReady(id, "JavaScript code generated by backend is invalid!\n"+responseText, null);

                        }

                    } else {

                        th.readyState = s;

                        if (th.onreadystatechange) th.onreadystatechange() 

                    }

                };

                this._id = id;

            }

            return req;

        },



        // Create new script element and start loading.

        _obtainScript: function(id, href) { with (document) {

            var span = null;

            // Oh shit! Damned stupid fucked Opera 7.23 does not allow to create SCRIPT 

            // element over createElement (in HEAD or BODY section or in nested SPAN - 

            // no matter): it is created deadly, and does not respons on href assignment.

            // So - always create SPAN.

            var span = createElement("SPAN");

            span.style.display = 'none';

            body.appendChild(span);

            span.innerHTML = 'Text for stupid IE.<s'+'cript></' + 'script>';

            setTimeout(function() {

                var s = span.getElementsByTagName("script")[0];

                s.language = "JavaScript";

                if (s.setAttribute) s.setAttribute('src', href); else s.src = href;

            }, 10);

            this._id = id;

            this._span = span;

        }},



        // Remove last used script element (clean memory).

        _cleanupScript: function() {

            var span = this._span;

            if (span) {

                this._span = null;

                setTimeout(function() {

                    // without setTimeout - crash in IE 5.0!

                    span.parentNode.removeChild(span);

                }, 50);

            }

            return false;

        },



        // Convert hash to QUERY_STRING.

        _hash2query: function(content, prefix) {

            if (prefix == null) prefix = "";

            var query = [];

            if (content instanceof Object) {

                for (var k in content) {

                    var v = content[k];

                    if (v == null || ((v.constructor||{}).prototype||{})[k]) continue;

                    var curPrefix = prefix? prefix+'['+this.escape(k)+']' : this.escape(k);

                    if (v instanceof Object)

                        query[query.length] = this._hash2query(v, curPrefix);

                    else

                        query[query.length] = curPrefix + "=" + this.escape(v);

                }

            } else {

                query = [content];

            }

            return query.join('&');

        },



        // Return value of SID based on QUERY_STRING or cookie

        // (PHP compatible sessions).

        _getSid: function() {

            var m = document.location.search.match(new RegExp('[&?]'+this.session_name+'=([^&?]*)'));

            var sid = null;

            if (m) {

                sid = m[1];

            } else {

                var m = document.cookie.match(new RegExp('(;|^)\\s*'+this.session_name+'=([^;]*)'));

                if (m) sid = m[2];

            }

            return sid;

        },

        

        // Stupid JS escape() does not quote '+'.

        escape: function(s) {

            return escape(s).replace(new RegExp('\\+','g'), '%2B');

        }

    }

})();

