/**
 *$Id: element.js 129 2009-03-18 15:38:46Z aanpilogov $
 *
 * Отвечает за методы по работе с DOM элементами
 */
PuskFramework.elem = new function()
{
    var pf = PuskFramework;

    /**
    * Замена document.getElementById
    */
    this.get = function(elementId)
    {
        return (typeof elementId == 'string') ? document.getElementById(elementId) : elementId;
    };

    /**
     * Конструирует DOM элемент с заданными свойствами и стилем
     *
     * @param {Object} elem тэг
     * @param {Object} attributes свойства элемента
     * @param {Object} cssStyle стиль
     * @return {Object}
     */
    this.construct = function(elem, attributes, cssStyle)
    {
        if (!elem) return false;
        var node, handlers = {};
        var IE = /*@cc_on!@*/false;
        if (typeof cssStyle == 'string') { cssStyle = pf.str.toHash(cssStyle, ';', ':'); }

        if (IE) { var str = '<' + elem } else { node = document.createElement(elem); }
            for (var i in attributes)
            {
            switch (true)
                {
                case (i == 'innerHTML'):
                    break;
                case (i.startsWith('on') && (typeof attributes[i] == "function")):
                    handlers[i.substr(2)] = attributes[i];
                        break;
                case (i == 'className'):
                    if (IE) { str += (' class="' + attributes[i] + '"') } else { node[i]=attributes[i]; }
                        break;
                    default:
                    if (IE) { str += (' ' + i + '="' + attributes[i] + '"') } else { node.setAttribute(i, attributes[i]); }
                        break;
                }
            }
        if (IE) {
            str += '>';
            node = document.createElement(str);
        };
        if (attributes && attributes.innerHTML) { node.innerHTML = attributes['innerHTML']; }

        for (var i in handlers)
                {
            pf.evt.add(node, i, handlers[i]);
        }
        for (var i in cssStyle)
        {
            node.style[pf.str.camelize(i)] = cssStyle[i];
        }

        return node;
    };

    /**
     * Переключает видимость элемента
     *
     * @param {Object} elem элемент
     * @param {Object} arg on|off
     * @return {Bool}
     */
    this.toggle = function(elem, arg)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.toggle(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem || elem.nodeType != 1) return false;
        switch (arg)
        {
            case 'on':
            case true:
                elem.style.display = '';
                return true;
            case 'off':
            case false:
                elem.style.display = 'none';
                return false;
            default:
                return (elem.style.display = (elem.style.display == 'none' ? '' : 'none')) ? false : true;
        }
    };

    /**
     * Возвращает стиль заданного элемента
     *
     * @param {Object} elem
     * @param {Object} CSSStyleProp
     * @return {String}
     */
    this.getStyle = function(elem, CSSStyleProp)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.getStyle(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem || elem.nodeType != 1) return false;
        if (elem.currentStyle)
        {
            var IEStyleProp = pf.str.camelize(CSSStyleProp);
            return elem.currentStyle[IEStyleProp];
        }
        else if (window.getComputedStyle)
        {
            var compStyle = window.getComputedStyle(elem, "");
            return compStyle.getPropertyValue(CSSStyleProp);
        }
        return '';
    };

    /**
     * Проверить, содержит ли заданный DOM элемент указанное имя класса
     *
     * @param {Object} elem элемент
     * @param {Object} cname имя класса
     * @return {Bool}
     */
    this.hasClass = function(elem, cname)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.hasClass(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem || elem.nodeType != 1) return false;
        var a = elem.className.split(/\s+/);
        for (var i = 0; i < a.length; i++)
        {
            if (a[i] == cname) return true;
        }
        return false;
    };

    /**
     * Добавляет указанное имя класса к элементу, не затрагивая другие
     *
     * @param {Object} elem элемент
     * @param {Object} cname имя класса
     * @return {Bool}
     */
    this.addClass = function(elem, cname)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.addClass(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem) return false;
        if (pf.elem.hasClass(elem, cname)) return false;
        elem.className += ' ' + cname;
        return true;
    };

    /**
     * Удаляет указанное имя класса из списка классов элемента, не затрагивая другие
     *
     * @param {Object} elem элемент
     * @param {Object} cname имя класса
     * @return {Bool}
     */
    this.delClass = function(elem, cname)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.delClass(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem) return false;
        var a = elem.className.split(/\s+/);
        var newName = '';
        for (var i = 0; i < a.length; i++)
            if (a[i] != cname) newName += ' ' + a[i];
        elem.className = newName;
        return true;
    };

    /**
     * Ищет в дереве DOM наверх элемент с заданными критериями.
    * Установленный флаг choise указывает, что хотя бы одно условие должно быть выполнено
    * (по умолчанию все условия должны быть выполнены).
     *
     * @param {Object} elem
    * @param {Object} conditions
    * @param {Object} choise
     * @return {Object}
     */
   this.findAncestor = function(elem, conditions, choise)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.findAncestor(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');

        do
        {
            if (pf.$empty(elem)) { return null; }
            var ok = true;
            for (var key in conditions)
            {
                var cond = (key=='hasClass')
                    ? pf.elem.hasClass(elem, conditions[key])
                    : (elem[key] == conditions[key] || elem.getAttribute(key) == conditions[key]);
                if (cond && choise) return elem;
                if (!cond)
                {
                    ok = false;
                    if (!choise) break;
                }
            }
            if (ok) { return elem; }
        }
        while ((elem = elem.parentNode) != document.documentElement);

        return false;
    };

    /**
     * Установить прозрачность элемента в %
     *
     * @param {Object} elem элемент
     * @param {Object} opacity прозрачность (%)
     */
    this.setOpacity = function(elem, opacity)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.setOpacity(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem) return false;
        if (opacity == null) opacity = 100;
        if (isNaN(opacity)) opacity = parseFloat(opacity);
        if (opacity <= 1) opacity = opacity * 100;
        if (opacity < 0) opacity = 0;
        elem.style.filter = "alpha(opacity=" + opacity + ")";
        opacity = opacity / 100;
        elem.style.MozOpacity = opacity;
        elem.style.KhtmlOpacity = opacity;
        elem.style.opacity = opacity;
    };

    /**
     * Возвращает текстовую часть контента заданного элемента
     *
     * @param {Object} elem элемент
     * @return {String}
     */
    this.getText = function(elem)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.getText(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem) return '';
        return elem.text || elem.textContent || elem.innerText;
    };

    /**
     * Удаляет заданный элемент из DOM
     *
     * @param {Object} elem элемент
     * @return {Bool}
     */
    this.remove = function(elem)
    {
        elem = pf.$(elem);
        if (pf.$type(elem) == "string") elem = pf.$(elem);

        if (elem && elem.parentNode)
        {
            var p = elem.parentNode;
            return (p.removeChild(elem));
        }
        return (false);
    };

    /**
     * Заменяет один элемент DOM другим
     *
     * @param {Object} oldElem старый элемент
     * @param {Object} newElem новый элемент
     */
    this.replaceWith = function(oldElem, newElem)
    {
        if (pf.$type(oldElem) == "string") oldElem = pf.$(oldElem);
        if (pf.$type(oldElem) != "element") throw pf.$exGen('pf.element.replaceWith(): ' + 'typeof(oldElem) argument is ' + pf.$type(oldElem) + ', "element" expected');
        if (pf.$type(newElem) == "string") newElem = pf.$(newElem);
        if (pf.$type(newElem) != "element") throw pf.$exGen('pf.element.replaceWith(): ' + 'typeof(newElem) argument is ' + pf.$type(newElem) + ', "element" expected');
        if (!oldElem || oldElem.nodeType != 1 || !newElem || newElem.nodeType != 1) return false;
        oldElem.parentNode.insertBefore(newElem, oldElem);
        pf.elem.remove(oldElem);
    };

    /**
     * Загружает в заданный элемент innerHTML с определенного URL(внутри домена)
     * функция работает асинхронно!!!
     *
     * @param {Object} elem элемент
     * @param {Object} url адрес контента
     * @param {Object} callback функция, которую надо вызвать после завершения операции
     * @param {Object} onerror функция, которую надо вызвать при ошибке загрузки
     */
    this.loadContent = function(elem, url, callback, onerror)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.loadContent(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem || !url) return false;
        return pf.ajax.get({
            'url': url,
            'onSuccess': function(req)
            {
                try
                {
                    elem.innerHTML = req.responseText;
                    if (callback)
                    {
                        callback(req, url);
                    }
                }
                catch (err)
                {
                    debugError('не получается выполнить коллбэк! ' + err.name + ' : ' + err.message);
                    serverSetStatus('error');
                }
            },

            'onError': function(req)
            {
                debugError('Ошибка запроса!\nСтатус=' + req.statusText);
                serverSetStatus('error');
                if (onerror)
                {
                    onerror(req);
                }
            }
        });
    };

    /**
     * Возвращает все элементы заданного узла, являющиеся тэгами
     *
     * @param {Object} elem элемент
     * @param {Object} tag фильтрация по имени тэга
     * @return {Array}
     */
    this.getChildElements = function(elem, tag)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.getChildElements(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem || !elem.childNodes) return false;
        var ch = [];
        var el = elem.childNodes;
        for (var i = 0, len = el.length; i < len; i++)
        {
            if (el[i].tagName && el[i].tagName == tag)
            {
                ch.push(el[i]);
            }
        }
        return ch;
    };

    /**
     * Возвращает первый дочерний не #text элемент
     *
     * @param {Object} elem элемент
     * @return {Array}
     */
    this.getFirstChild = function(elem)
    {
        if (pf.$type(elem) != "element") throw pf.$exGen('pf.element.getFirstChild(): ' + 'typeof(elem) argument is ' + pf.$type(elem) + ', "element" expected');
        if (!elem || !elem.childNodes) return false;
        var ch = [];
        var el = elem.childNodes;
        for (var i = 0, len = el.length; i < len; i++)
        {
            if (el[i].tagName)
            {
                return(el[i]);
            }
        }
        return false;
    };

    /**
     * Дополнение к системной функции insertBefore.
     * Вставляет элемент после заданного элемента
     *
     * @param {Object} newNode элемент
     * @param {Object} existingNode существующий элемент
     * @return {Bool}
     */
    this.insertAfter = function(newNode, existingNode)
    {
        if (pf.$type(newNode) == "string") newNode = pf.$(newNode);
        if (pf.$type(newNode) != "element") throw pf.$exGen('pf.element.getFirstChild(): ' + 'typeof(newNode) argument is ' + pf.$type(newNode) + ', "element" expected');
        if (pf.$type(existingNode) == "string") existingNode = pf.$(existingNode);
        if (pf.$type(existingNode) != "element") throw pf.$exGen('pf.element.getFirstChild(): ' + 'typeof(existingNode) argument is ' + pf.$type(existingNode) + ', "element" expected');

        if (!existingNode || !existingNode.parentNode) return false;
        return existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
    };

    /**
     * Проверить, содержит ли заданный контейнер заданный элемент
     * @param {Object} container контейнер
     * @param {Object} containee элемент
     * @return {Bool}
     */
    this.containsDOM = function(container, containee)
    {
        if (!containee || !containee.parentNode) return false;
        var isParent = false;
        do
        {
            if (isParent = (container == containee)) break;
            containee = containee.parentNode;
        }
        while (containee != null);
        return isParent;
    };

    /**
     * Возвращает сдвиг заданного элемента, вызванный его уходом за скролл
     *
     * @param {Object} elem элемент
     * @return {Object}
     */
    this.getScrolls =  function(elem)
    {
        var docElem = document.documentElement, pos = {'x': 0, 'y': 0};
        if (!elem || elem.nodeType != 1) return pos;
        while (elem && elem != docElem)
        {
            pos.x += elem.scrollLeft;
            pos.y += elem.scrollTop;
            if (pf.elem.getStyle(elem, 'position') == 'fixed')
            {
                pos.x -= (docElem.scrollLeft || document.body.scrollLeft);
                pos.y -= (docElem.scrollTop  || document.body.scrollTop);
                return pos;
            }
            elem = elem.parentNode;
        }
        return pos;
    };

 /**
     * Возвращает абсолютные координаты заданного элемента
     *
     * @param {Object} elem элемент
     * @return {Object}
     */
    this.getPosition = function(elem)
    {
        if (!elem || elem == document.documentElement) return {'x': 0, 'y': 0};

        var pos = this.getScrolls(elem);

        if (document.body.getBoundingClientRect)
        {
            var rect = elem.getBoundingClientRect();
            pos.x += parseInt(rect.left);
            pos.y += parseInt(rect.top);
            return pos;
        }

        if (elem.offsetParent)
        {
            do {
                pos.x += elem.offsetLeft || 0;
                pos.y += elem.offsetTop  || 0;
            } while (elem = elem.offsetParent)
        }
        return pos;
    };
    
    /**
     * Выставляет заданному элементу класс, позволяющий визуально показать,
     * что происходит загрузка контента
     *
     * @param {Object} elem элемент
     * @param {Object} isLoading (on|off)
     * @return {String} (on|off)
     */
    this.setLoading = function(elem, isLoading)
    {
        if (!elem || elem.nodeType != 1) return false;
        var cname = 'loading';
        switch (isLoading)
        {
            case 'on':
                pf.elem.addClass(elem, cname);
                return true;
            case 'off':
                pf.elem.delClass(elem, cname);
                return false;
            default:
                return this.setloading(elem, pf.elem.hasClass(elem, cname) ? 'off' : 'on');
        }
    };
};

