/**
 * 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.20
 */

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?

                // Internals.
                _span:              null,
                _id:                null,
                _xmlReq:            null,
                _openArg:           null,
                _reqHeaders:        null,

                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,
                                'password':  password
                        };
                        this._id = null;
                        this._xmlReq = null;
                        this._reqHeaders = [];
                        return true;
                },

                send: function(content) {
                        var id = 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 + "=" + 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;
                        if (this._xmlReq && hasSetHeader && (""+this._openArg.method).toUpperCase() == "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 {
                                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 "&".
                                var a = this._openArg;
                                this._xmlReq.open(a.method, href+"&", 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, cannot use XMLHttpRequest!
                        if (url.match(new RegExp('^[a-z]+://', 'i'))) 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() {
                                        if (req.readyState == 4) {
                                                // Call associated dataReady().
                                                eval(req.responseText);
                                        } else {
                                                th.readyState = req.readyState;
                                                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.
                        span = body.appendChild(createElement("SPAN"));
                        span.style.display = 'none';
                        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) {
                        var query = [];
                        if (content instanceof Object) {
                                // TODO: build more nested objects in PHP-style.
                                for (var k in content) {
                                        query[query.length] = escape(k) + "=" + escape(content[k]);
                                }
                        } 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='(;|^)\\s*'+this.session_name+'=([^;]*)'));
                                if (m) sid = m[2];
                        }
                        return sid;
                }
        }
})();

