﻿/*
 *        Constructor.js
 * DHTML construction framework
 *         version  4.2
 * ****************************
 * (c) 2002-2008 Photocenter.ru
 */

var _d = document;
var _w = window;

var isFF = navigator.appName == 'Netscape'; // TODO: сделать проверку на оперу

if (!window.external) // Чтобы не возникало лишних ошибок при обращении к внешней программе вне IE
    window.external = {};

// добавить аргументы со второго и далее потомками в первый
function _ac () {
    for (var i = 1; i < arguments.length; i++) {
        if (arguments[i] || typeof (arguments[i]) == 'string')
            arguments[0].appendChild (typeof (arguments[i]) == "string" ? _d.createTextNode(arguments[i]) : arguments[i]);
    }
    return arguments[0];
}

function _cp (d,s) {
    if (s)
        for (var i in s) {
            var v = s[i];
            d[i] = s[i];
        }
    return d;
}

function _cev (tag, a) {
    var o = _d.createElement(tag);
    if(a) {
        var p = a[0];
        if (p) for (var i in p) {
            if (i == 'style')   // TODO: сделать возможность задавать стиль строкой
                _cp (o.style,p[i]);
            else {
                var v = p[i];
                    o[i] = v;
            }
        }

        // копируем все объекты из списка аргументов в свойства свежесозданного объекта, при необходимости, приводя элементы
        for (var i=1;i<a.length;i++) {
            var d = a[i];
            if (!d && typeof (d) != 'string')
                continue;
            if (typeof (d) == 'string') {
                        o.appendChild (_d.createTextNode (d));
            } else if (d && d.constructor == Array)
                for (var j in d)
                    o.appendChild(d[j]);
            else if (d)
                o.appendChild(d);
        }
    }
    return o;
}

// создаем функции-хелперы для всех требуетмых элементов
var tags = ['script', 'select', 'option', 'textarea', 'fieldset', 'legend', 'label', 'hr', 'p', 'a', 'div', 'span', 'form', 'object', 'param', 'embed', 'img', 'br', 'input', 'ul', 'li', 'iframe', 'map', 'area', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'center', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
for (var i in tags)
    window['_'+tags[i]] = new Function (["return _cev('", tags[i], "', arguments);"].join (''));

// два варианта вывода прозрачного .png (нормальный и для IE6)
function _transparent () { return _cev ('img', arguments); }
function _transparent6 () {
    var o = _cev ('img', arguments);
    var s = o.src;
    o.src = 'images/blank.gif';
    o.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+s+'", sizingMethod="scale")';
    return o;
}


//=============================================================================
// AJAX stuff begins here
//
//============================================================================
//                              AjaxRequester
// ---------------------------------------------------------------------------
// очередь запросов
// TODO: по-хорошему, стоило бы организовать еще и очередь ответов, чтобы не тормозить на ожидании обработки ответа.
//      Есть, правда, подозрение, что на этом заткнется браузер
function AjaxRequester (objectName) {
    this.queue = [];
    this.onready = new Function (objectName+".ready ();");  // генериуем функцию, вызываемую по onreadystatechange запроса
    window[objectName] = this;
}

AjaxRequester.prototype = {
    req: null,          // запрос XMLHTTP
    queue: null,        // очередь запросов
    currentReq: null,   // выполняемый запрос
    inProgress: 0,  // флаг выполнения зпросов
    onready: null,      // метод, прописываемый запросу в onreadystatechange. Генерируется конструктором, чтобы учесть имя переменной, в которой хранится объект

    request: function (reqObject) { // добавить запрос в очередь
        this.queue.push (reqObject);
        this.send ();
    },

    send: function () { // послать запрос
         if (this.queue.length < 1) // эта проверка более легкая, поэтому она делается первой
             return;

        if (this.inProgress) {
            if (new Date().getTime()-this.inProgress > 60000) {
                alert ("AjaxRequester: ERROR: send timeout!");
                // experimental: очищаем очередь запросов
                this.queue = [];
                this.inProgress = 0;
            }
            return;
        }

        if (this.queue.length >= 100)
            alert ("AjaxRequester: ERROR: queue too long!");

        // this.inProgress = true;
        this.inProgress = new Date().getTime();
        this.currentReq = this.queue[0];
        // alert (objectAsText (this.currentReq));

        this.queue.splice (0, 1);   // выбираем запрос из очереди
        this.req = window.XMLHttpRequest ? new XMLHttpRequest () : new ActiveXObject ("Microsoft.XMLHTTP");
        this.req.onreadystatechange = this.onready;
        if (this.currentReq.data) {
            this.req.open ("POST", this.currentReq.url, true);
            this.req.setRequestHeader ("Content-type", "application/x-www-form-urlencoded");
            this.req.send (this.currentReq.data);
        } else {
            this.req.open ("GET", this.currentReq.url, true);
            this.req.send ();
        }
    },

    ready: function () {// обработчик onreadystatechange
        if (this.req.readyState != 4)   // запрос еще не завершился
            return;
        if (this.req.status != 200) {
            if (this.currentReq.errorMethod)
                this.currentReq.errorMethod (this.req.status);
            else
                this.defaultError ();
        } else if (this.currentReq.doneMethod) {    // если для запроса нет обработчика, то и делать ничего не надо
            this.currentReq.doneMethod (this.parseResponse ());
        }

        this.inProgress = 0;
        this.send ();   // пошлем следующий запрос из очереди, если он там есть
    },

    parseResponse: function () {    // разбор принятого XML
         var root = this.req.responseXML;
         if (!root)
             return false;
         // return "done";
         return this.parseNode (root);
    },

    // TODO: добавить в xmlObjects два метода. Один будет создавать объект по имени (если в массиве определен конструктор,
    // вызываем его, иначе - возвращаем новый объект), другой - проверять, нужно создавать для него массив или нет

    // разбор уровня DOM-дерева.
    // Параметром передается node, на выходе возвращается созданный объект
    // Обрабатываются только nodeType=1 и nodeType=3 (element и text),
    // nodeType=2 (attribute) обрабатывается автоматически.
    parseNode: function (obj) {
        var xo = this.currentReq.xmlObjects?this.currentReq.xmlObjects[obj.nodeName]:null;     // получим описатель объекта, если он есть
        var r = (xo && xo.o) ? new xo.o : new Object;   // создадим новый объект, представляющий разбираемый узел
        // если у узла есть атрибуты - добавим их полями объекта
        if (obj.attributes)
            for (var i = 0; i < obj.attributes.length; i++)
                r[obj.attributes[i].name] = obj.attributes[i].nodeValue;    // TODO:!
        // последовательно разберем всех потомков
        for (i = 0; i < obj.childNodes.length; i++) {
            var c = obj.childNodes[i];
            var o;
            if (c.hasChildNodes() && c.childNodes.length == 1 && (c.childNodes[0].nodeType == 3 || c.childNodes[0].nodeType == 4))
                o = c.childNodes[0].nodeValue;
            else
                o = this.parseNode (c); // разбираем потомка

            if (r[c.nodeName]) {
                if (typeof (r[c.nodeName]) == "object" && r[c.nodeName].constructor == Array)
                    r[c.nodeName].push(o);
                else
                    r[c.nodeName] = new Array (r[c.nodeName], o);
            } else {
                // проверим, нужно создавать для этого элемента массив или нет
                xo = this.currentReq.xmlObjects?this.currentReq.xmlObjects[c.nodeName]:null;
                r[c.nodeName] = (xo && xo.a) ? new Array (o) : o;   // можно создавать массив так, т.к. o - гарантировано объект
            }

        }
        return r;
    },

    defaultError: function () { // обработка ошибки - метод по умолчанию
        try { alert ("AJAX ERROR: "+this.req.status+" in "+this.currentReq.url); } catch (e) {}
        // alert ("AJAX ERROR: "+this.req.status+" in "+this.currentReq.url);
    }
};

// запрос к серверу
function Request () {
    this.xmlObjects = [];   // чтобы не плодить проверки при разборе (т.е. req.xmlObjects гарантированно определен)
}

Request.prototype = {
    url: null,          // адрес скрипта, которому посылается запрос
    data: null,         // данные для посылки POST-ом. Если в этом поле null, выполняется GET
    doneMethod: null,   // метод, вызываемый по успешному выполнению запроса
    errorMethod: null,  // метод, вызываемый по возникновению ошибки (если в этом поле null - вызывается метод по умолчанию). Параметром передается код ошибки
    xmlObjects: null    // набор хинтов для разбора
};
//============================================================================
//                              AjaxProcessor
// ---------------------------------------------------------------------------
//  Предполагается, что данный объект существует в одном экземпляре, на который
// ссылается переменная ajax.
// TODO: для использования нескольких объектов одновременно, требуется передавать
// функцию, которая будет возвращать нужный объект (впрочем, над этим нужно подумать)
var ajax = null;
var xmlObjects = new Array ();  // массив с параметрами разбора XML. Индекс в массиве - имя тэга,
                                // значение - объект, который может содержать поля: o - конструктор объекта,
                                // a - должен объект помещаться в массив или нет (true/false)
function AjaxProcessor () {
}

AjaxProcessor.prototype = {
    req: null,          // объект XMLHTTP
    doneMethod: null,   // функция, вызываемая по успешному завершению запроса
    errorMethod: null,  // функция, вызываемая при ошибке

    checkChanges: false,// при выставлении этого параметра в true, результат запроса сравнимается с предыдущим значением и,
                        // если ничего не поменялось, ничего не происходит.
    lastResponse: null, // предыдущее сохраненное значение

    // основной и единственный пользовательский метод этого объекта.
    // отправляет запрос на указанный URL с помощью GET, по завершению вызывает
    // соответствующий метод (либо запрос завершился успешно, и тогда вызывается
    // doneMethod, параметром которому передается разобранный XML, либо неуспешно,
    // и тогда вызывается errorMethod, параметром которому передается объект запроса
    process: function (url, doneMethod, errorMethod, data) {
        // TODO: что делать, если запрос уже выполняется? if (this.req) this.req.close (); ?
        this.doneMethod = doneMethod;
        this.errorMethod = errorMethod;
        this.req = window.XMLHttpRequest ? new XMLHttpRequest () : new ActiveXObject ("Microsoft.XMLHTTP");
        this.req.onreadystatechange = this.ready;
        if (data) {
            this.req.open ("POST", url, true);
            req.setRequestHeader ("Content-type", "application/x-www-form-urlencoded");
        } else {
            this.req.open ("GET", url, true);
            data = '';
        }
        this.req.send (data);
    },

    // callback readystatechange
    ready: function () {
         if (!ajax.req ||  ajax.req.readyState != 4)
             return ;   // не готово еще
         if (ajax.req.status != 200) {
             alert ("HTTP error: "+ajax.req.status+". Please reload page");  // TODO: а что вообще с этим делать?
             return ajax.errorMethod ? ajax.errorMethod (ajax.req) : null;
         }
         var o = ajax.parseResponse ();
         if (ajax.doneMethod)
             ajax.doneMethod (o);
         ajax.req = null;
    },

    // разбор XML
    parseResponse: function (obj) {
         var root = ajax.req.responseXML;
         if (!root)
             return false;
         return this.parseNode (root);
    },

    // TODO: добавить в xmlObjects два метода. Один будет создавать объект по имени (если в массиве определен конструктор,
    // вызываем его, иначе - возвращаем новый объект), другой - проверять, нужно создавать для него массив или нет

    // разбор уровня DOM-дерева.
    // Параметром передается node, на выходе возвращается созданный объект
    // Обрабатываются только nodeType=1 и nodeType=3 (element и text),
    // nodeType=2 (attribute) обрабатывается автоматически.
    parseNode: function (obj) {
        var xo = xmlObjects[obj.nodeName];     // получим описатель объекта, если он есть
        var r = (xo && xo.o) ? new xo.o : new Object;   // создадим новый объект, представляющий разбираемый узел
        // если у узла есть атрибуты - добавим их полями объекта
        if (obj.attributes)
            for (var i = 0; i < obj.attributes.length; i++)
                r[obj.attributes[i].name] = obj.attributes[i].nodeValue;    // TODO:!
        // последовательно разберем всех потомков
        for (i = 0; i < obj.childNodes.length; i++) {
            var c = obj.childNodes[i];
            var o;
            if (c.hasChildNodes() && c.childNodes.length == 1 && (c.childNodes[0].nodeType == 3 || c.childNodes[0].nodeType == 4))
                o = c.childNodes[0].nodeValue;
            else
                o = this.parseNode (c); // разбираем потомка

            if (r[c.nodeName]) {
                if (typeof (r[c.nodeName]) == "object" && r[c.nodeName].constructor == Array)
                    r[c.nodeName].push(o);
                else
                    r[c.nodeName] = new Array (r[c.nodeName], o);
            } else {
                // проверим, нужно создавать для этого элемента массив или нет
                xo = xmlObjects[c.nodeName];
                r[c.nodeName] = (xo && xo.a) ? new Array (o) : o;   // можно создавать массив так, т.к. o - гарантировано объект
            }

        }
        return r;
    }
};

// вывести содержимое объекта в текстовом виде.
// Параметром передается строка, содержащая отступ при выводе (т.е. содержимое этой строки добавляется
// перед выводом полей объекта). При разборе потомков, последний символ строки дублируется
function objectAsText (obj, indent) {
    indent = indent || '>';
    var newIndent = indent+'>'; // TODO: сделать дублирование последнего символа
    var descend = false;    // требуется разбор объекта вглубь
    var s = "";
    switch (typeof (obj)) {
    case 'number':
    case 'boolean':
         s = obj.toString ();
         break;
    case 'string':
         s = '"'+obj+'"';
         break;
    case 'function':
         var m = (/^function([^(]*)\(/).exec (obj.toString());
         s = m ? 'function '+m[1] : obj.toString ();
         descend = true;
         break;
    case 'object':
         switch (obj) {
         case null:
             s = null;
             break;
         case window:
             s = 'window';
             break;
         case window.event:
             s = 'event';
             descend = true;
             break;
         case document:
             s = 'document';
             break;
         default:
             switch (obj.constructor) {
             case RegExp:
             case Date:
             case Math:
             case Error:    // ?
                 s = obj.toString();
                 break;
             default:
                 descend = true;
                 if (obj.nodeType) {
                   s = obj.nodeType == 1 ? 'domnode ('+obj.nodeName+')':'textnode';
                   descend = false;
                 } else {
                   //var m = (/^\s*function([^(]*)\(/).exec (obj.constructor.toString());
                   //s = (m ? m[1] : "");
                 }
             }
         }
         break;
    case 'undefined':
         s = 'undefined';
         break;
    default:
         alert ("Unhandled type "+typeof(obj));
    }

    s += '\n';
    if (descend) // если объект имеет какую-то структуру
       for (i in obj)
          s += newIndent+' '+i+":"+objectAsText (obj[i], newIndent);
    return s;

}




