//********************************************************************************************
//Funciones genéricas
//********************************************************************************************
//Devuelve un objeto o un array de objetos.
function $() {
    var elements = new Array();
    for (var i = 0; i < arguments.length; i++) {
        var element = arguments[i];
        if (typeof element == 'string')
            element = document.getElementById(element);
        if (arguments.length == 1)
            return element;
        elements.push(element);
    }
    return elements;
}

//Devuelve el valor de un objeto (cualquiera sea su tipo: SELECT, INPUT, etc.)
function $F() {
    var obj = $(arguments[0]);
    if (obj == null)
        return null;

    if (obj.tagName == "SELECT" || obj.tagName == "INPUT")
        return obj.value;

    return obj.innerHTML;
}

//Lanza un error de JavaScript
ThrowJSError = function(message) {
    MM.VCWUtils.ErrorHandler.Base("Base", 1000, message, null, null);

    //Lanza una excepción de JS para detener la ejecución de scripts en la página.
    throw new Error(message);
};

//--------- FUNCIONES PENDIENTES DE ELIMINAR ------------------
// ---- CAMBIAR POR LAS FUNCIONES DEL OBJETO DOM DE YUI. ---
function GetObjectDimensions(obj) {
    var curheight = curwidth = 0;
    if (obj) {
        if (obj.offsetParent) {
            curheight = obj.offsetHeight;
            curwidth = obj.offsetWidth;
        }
    }
    return [curheight, curwidth];
}

//Aplica css a un objeto a partir de una cadena
function ApplyCss(obj, cadena) {
    if (YAHOO.env.ua.ie && YAHOO.env.ua.ie < 8)
        obj.setAttribute("className", cadena);
    else
        obj.setAttribute("class", cadena);
}

//Aplica estilo a un objeto a partir de una cadena
function ApplyStyle(obj, cadena) {
    if (YAHOO.env.ua.ie)
        obj.style.cssText = cadena;
    else
        obj.setAttribute("style", cadena);
}

//Aplica estilo a un objeto a partir de una cadena o un objeto que contiene los estilos a modificar.
//La diferencia con la función ApplyStyle es que esta mantiene el estilo actual y solo modifica los valores nuevos, mientras que la primera reemplaza todos los atributos de estilo.
function ApplyStyleOverride(obj, newStyle) {
    if (obj) {
        if (typeof newStyle != "string") {
            //newStyle debe ser un objeto con la forma { "color": "black", "fontSize":"11pt", ... }
            for (var key in newStyle) {
                var value = newStyle[key];
                if (value != null && value != "")
                    obj.style[key] = value;
            }
        }
        else {
            //Si es una cadena la procesa.
            var values = newStyle.split(";");
            var lgt = values.length;

            //Carga los valores
            for (i = 0; i < lgt; i++) {
                var x = values[i].indexOf(":");

                if (x != -1) {
                    var key = MM.VCWUtils.ParseUtils.Trim(values[i].substring(0, x));

                    //Reemplaza los guiones y la letra siguiente por mayúscula. Ejemplo: "background-color: black;" --> style.backgroundColor
                    var keyFragment = '-.?';
                    var matchTag = new RegExp(keyFragment, 'im');
                    var keyMatch = key.match(matchTag);

                    if (keyMatch != undefined && keyMatch.length > 0) {
                        var newValue = keyMatch.toString();
                        newValue = newValue.substring(1, 2).toUpperCase();

                        key = key.replace(keyMatch, newValue);
                    }

                    var value = MM.VCWUtils.ParseUtils.Trim(values[i].substring(x + 1, values[i].length));

                    if (value != "")
                        obj.style[key] = value;
                }
            }
        }
    }
}

//Obtiene el estilo de un objeto
function GetStyle(obj) {
    if (obj)
        return obj.style.cssText;
}

//********************************************************************************************
//Funciones para manejar eventos
//********************************************************************************************
function FireEvent(element, event) {
    if (document.createEventObject) {
        // dispatch for IE
        var evt = document.createEventObject();
        try {
            element.fireEvent('on' + event);
        }
        catch (e) { }
    }
    else {
        // dispatch for firefox + others
        var evt = document.createEvent("HTMLEvents");
        evt.initEvent(event, true, true); // event type,bubbling,cancelable
        !element.dispatchEvent(evt);
    }
}

function numericKeyPress(e) {
    tecla = (document.all) ? e.keyCode : e.which; //IE : FireFox
    //Transformación de la cadena de acuerdo a la selección del usuario
    if (CurrentCulture == "es-es") {
        if (tecla == 46) //46 (.)
        {
            if (document.all)
                e.keyCode = 44; //44 (,)
            else { //FireFox
                var newEvent = document.createEvent("KeyEvents");
                newEvent.initKeyEvent("keypress", true, true, document.defaultView,
                                      e.ctrlKey, e.altKey, e.shiftKey,
                                      e.metaKey, 0, ",".charCodeAt(0));
                e.preventDefault();
                e.target.dispatchEvent(newEvent);
            }
        }
    }
}
//-----------------------------------------------

///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////        MM       ///////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////

var MM = {};

MM.AdvManager = {
    AdvFrames: new Object(),

    AddAdvFrame: function(frameId, mmAdv) {
        this.AdvFrames[frameId] = { "MMAdv": mmAdv, "LastRefresh": new Date() };
    },

    SendRequest: function(frameId, url) {
        var advFrame = MM.AdvManager.AdvFrames[frameId];
        if (advFrame == null || url.toLowerCase().indexOf('xmlservice=') != -1)
            return;

        //Prepara la URL a actualizar
        var urlComponents = url.split('?');
        url = urlComponents[0].toLowerCase().replace("mmajax.aspx", "MarketMonitor.aspx") + '?';
        var urlParams = urlComponents[1].split('&');
        var lgt = urlParams.length;
        for (var i = 0; i < lgt; i++) {
            var urlParam = urlParams[i];

            //Verifica si se trata del parámetro MMAdv
            var x = urlParam.indexOf('=');
            var pName = urlParam.substring(0, x).toLowerCase();
            var pValue = urlParam.substring(x + 1).toLowerCase();

            //TODO:Solución temporal para no realizar la petición cuando se trata de las páginas privadas de la cartera y gráficos.
            if (pName == "page" && (pValue.substring(0, 4) == "por_" || pValue == "eco_java_edition"))
                return;

            if (pName != "mmadv")
                url += urlParam + "&";
        }

        url += "MMAdv=" + advFrame.MMAdv;
        //Actualiza el IFrame
        $(frameId).src = url;
    },

    Refresh: function(url) {
        //Si hay contenedores bloqueados, no actualiza el iframe.
        if (MM.Ajax.Html == null || MM.Ajax.Html.LockedRequests.length > 0)
            return;

        for (var frameId in MM.AdvManager.AdvFrames) {
            MM.AdvManager.SendRequest(frameId, url);
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////   MM.VCWUtils   ///////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////

//Espacio de trabajo que contiene funciones genéricas.
MM.VCWUtils = {

    //Tabla Hash. Solo asocia cadenas. Si se necesitara utilizar una tabla más potente (que asocie objetos), considerar la siguiente: http://code.google.com/p/jshashtable/downloads/list
    HashTable: function() {
        var length = 0;

        //Elimina todos los elementos de la tabla.
        this.ClearAll = function() {
            for (var key in this.Items) {
                delete this.Items[key];
            }

            length = 0;
        }

        this.Get = function(key) {
            return this.Items[key];
        }

        this.HasItem = function(key) {
            return typeof (this.Items[key]) != 'undefined';
        }

        this.Items = new Object();

        this.Length = function() {
            return length;
        }

        this.Remove = function(key) {
            if (typeof (this.Items[key]) != 'undefined') {
                length--;
                this.Items[key];
                delete this.Items[key];

                return true;
            }

            return false;
        }

        this.Set = function(key, value) {
            if (typeof (value) != 'undefined') {
                if (typeof (this.Items[key]) == 'undefined')
                    length++;

                this.Items[key] = value;

                return true;
            }

            return false;
        }
    },

    //Función utilizada por controles como AutoComplete y HtmlInputSelect para disparar acciones cuando se seleccione un elemento.
    SelectedItemEvent: function(url, valObjectId, txtObjectId, targetType, targetName) {
        //Verifica si es una llamada a javascript
        var isJS = false;
        if (url.length > 10 && url.substring(0, 10).toLowerCase() == 'javascript') {
            url = url.substring(11, url.length);
            isJS = true;
        }

        var valObj = $(valObjectId);
        var txtObj = $(txtObjectId);

        if (valObj.tagName == "SELECT") {
            url = url.replace('@VAL_ELEM@', encodeURIComponent(valObj[valObj.selectedIndex].value));
            url = url.replace('@TXT_ELEM@', encodeURIComponent(valObj[valObj.selectedIndex].text));
        } else {
            url = url.replace('@VAL_ELEM@', encodeURIComponent(valObj.value));
            url = url.replace('@TXT_ELEM@', encodeURIComponent(txtObj.value));
        }

        if (isJS)
            eval(url);
        else {
            //Solo toma el primer caracter de targetType
            if (targetType.Length > 0)
                targetType = targetType.Substring(0, 1);

            if (targetType == "F")
                MM.VCWUtils.Frames.LoadFrame(targetName, url);
            else if (targetType == "W")
                window.open(url, targetName);
        }
    },

    //********************************************************************************************
    //Funciones relacionadas con formularios y recarga de páginas.
    //********************************************************************************************
    //Crea un parámetro único (se añade en las urls para invalidar la caché del explorador)
    GetUniqueValue: function() {
        var today = new Date();
        var uniqueValue = today.getTime();

        return uniqueValue;
    },

    //Devuelve un valor booleano indicando si el array proporcionado contiene el elemento item.
    HasItem: function(array, item) {
        if (array == null)
            return false;

        var lgt = array.length;
        for (var i = 0; i < lgt; i++) {
            if (array[i] == item)
                return true;
        }

        return false;
    },

    //objsToEn: Array con los objetos que se habilitarán (puede pasarse una array con los nombres)
    //objsToDis: Array los objetos que se deshabilitarán.
    //behaviors: Array de elementos que indican como se comportará la función al habilitar y deshabilitar elementos. Valores que acepta:
    // - null: No realiza ninguna tarea adicional.
    // - 1: Establece foco al habilitar.
    // - 2: Borra el contenido al deshabilitar.
    EnDisObjs: function(objsToEn, objsToDis, behavior) {
        if (objsToDis != null) {
            for (var i = 0; i < objsToDis.length; i++) {
                var obj = $(objsToDis[i]);
                if (obj) {
                    if (MM.VCWUtils.HasItem(behavior, 2))
                        obj.value = "";

                    obj.disabled = "disabled";
                }
            }
        }

        if (objsToEn != null) {
            var focusSet = false;
            for (var i = 0; i < objsToEn.length; i++) {
                var obj = $(objsToEn[i]);
                if (obj) {
                    obj.disabled = "";
                    if (MM.VCWUtils.HasItem(behavior, 1) && !focusSet) {
                        obj.focus();
                        focusSet = true;
                    }
                }
            }
        }
    }
};

//Contiene el nombre y el estado (oculta/visible) de una sección. Objeto utilizado por HiddenSections.
MM.VCWUtils.SectionInfo = function(sectionId, state) {
    this.SectionId = sectionId;
    this.State = state;

    MM.Ajax.JSDOM_Manager.AddRelation($(sectionId), [this]);

    this.destroy = function() {
        MM.VCWUtils.HiddenSections.RemoveSection(this.SectionId);
    };
};

//Clase estática que maneja las secciones ocultas
MM.VCWUtils.HiddenSections = {
    //Alamacena objetos SectionInfo con la informacion de las secciones que se muestran/ocultan en la página.
    Sections: new Array(),

    //Añade una sección al array de secciones ocultas
    AddSections: function(sectionsName) {
        var aSections = sectionsName.split(',');
        var lgt = aSections.length;
        for (var i = 0; i < lgt; i++) {
            var sectionId = aSections[i];
            if (sectionId != "")
                MM.VCWUtils.HiddenSections.Sections.push(new MM.VCWUtils.SectionInfo(sectionId, true));
        }
    },

    //Elimina una sección del array de secciones ocultas.
    RemoveSection: function(sectionId) {
        if (typeof (MM.Ajax.NodeSetsManager) != "undefined")
            sectionId = MM.Ajax.NodeSetsManager.EvalObjectId(sectionId);

        var end = MM.VCWUtils.HiddenSections.Sections.length - 1;
        for (var i = end; i >= 0; i--)
            if (MM.VCWUtils.HiddenSections.Sections[i].SectionId == sectionId) {
            MM.VCWUtils.HiddenSections.Sections.splice(i, 1);
            break;
        }
    },

    //Reinicia el array de funciones
    Show: function(sections) {

        var lgt = sections.length;
        for (var i = 0; i < lgt; i++) {
            var sectionId = sections[i];
            if (typeof (MM.Ajax.NodeSetsManager) != "undefined")
                sectionId = MM.Ajax.NodeSetsManager.EvalObjectId(sectionId);

            MM.VCWUtils.HiddenSections.showSection(sectionId);
        }

        //Al mostrar ocultar hay que redimensionar el frame (si está dentro de uno)
        MM.VCWUtils.Frames.DYNIFS.resizeIFrame();
    },

    showSection: function(sectionId) {
        //Índices = 0: Nombre sección; 1: Estado actual (oculta/visible)
        var lgt = MM.VCWUtils.HiddenSections.Sections.length;
        var x = -1;
        for (var i = 0; i < MM.VCWUtils.HiddenSections.Sections.length; i++) {
            //Oculta todas las demás secciones
            if (MM.VCWUtils.HiddenSections.Sections[i].SectionId == sectionId)
                x = i;
            else if (!MM.VCWUtils.HiddenSections.Sections[i].State)
                MM.VCWUtils.HiddenSections.showHide(MM.VCWUtils.HiddenSections.Sections[i].SectionId, i);
        }

        //Añade sección al array
        if (x == -1) {
            x = MM.VCWUtils.HiddenSections.Sections.length;
            MM.VCWUtils.HiddenSections.Sections[x] = new MM.VCWUtils.SectionInfo(sectionId, false); //Indica el nombre de la sección y si está oculta o no
        }

        MM.VCWUtils.HiddenSections.showHide(sectionId, x);
    },

    showHide: function(sectionId, x) {
        var obj = $(sectionId);
        MM.VCWUtils.HiddenSections.Sections[x].State = !MM.VCWUtils.HiddenSections.Sections[x].State;
        if (!MM.VCWUtils.HiddenSections.Sections[x].State) { //Muestra
            if (obj.parentNode.cells.length == 1) //Muestra la fila
                obj.parentNode.style.display = "inline";
            else
                obj.style.display = "inline";
        }
        else { //Oculta
            //Determina la cantidad de celdas que contiene
            if (obj.parentNode.cells.length == 1) //Oculta la fila
                obj.parentNode.style.display = "none";
            else //Oculta la celda
                obj.style.display = "none";
        }
    }
};

//Funciones para la manipulación de Iframes.
MM.VCWUtils.Frames = {
    //Oculta el scroll vertical del frame actual. (Devuelve el ancho del scroll)
    HideScrollY: function() {
        //Obtiene el ancho de la página con scroll.
        var widthPage = document.documentElement.clientWidth;

        //Elimina el scroll vertical de la página.
        document.documentElement.style.overflowY = "hidden";

        //Obtiene el ancho del scroll
        widthScroll = document.documentElement.clientWidth - widthPage;

        return widthScroll;
    },

    //Muestra el scroll vertical de la página.
    ShowScrollY: function() {
        document.documentElement.style.overflowY = "scroll";
    },

    //Redirige el documento padre a una url concreta.
    RedirectInParent: function(url) {
        var mainWin = MM.VCWUtils.Frames.GetParentWindow();
        //Si no se proporciona una url recarga la página actual.
        if (url == null)
            url = mainWin.location;

        mainWin.location = url;
    },

    //Obtiene el objeto window padre recorriendo iterativamente los windows de abajo a arriba
    //hasta que llega al padre (cuando hijo y padre son el mismo) o hasta que sale del dominio
    /*
    La condicion (curWindow.location.host==curWindow.parent.location.host) está puesta solo por seguridad
    ya que los navegadores generan una excepción al intentar acceder al window.location de otro dominio.
    Si algún navegador no cumpliera esta restricción, la propia comprobación hace que no nos salgamos del dominio.
    */
    GetParentWindow: function() {
        var curWindow = window;
        for (var i = 0; i < 10; i++) {
            try {
                if (curWindow.location.host == curWindow.parent.location.host)
                    curWindow = curWindow.parent;
                else
                    break;

                if (curWindow == curWindow.parent)
                    break;
            } catch (ex) {
                break;
            }
        }

        return curWindow;
    },

    //Busca un iframe en todos los objetos windows del documento actual (tanto padres como hijos).
    GetIFrame: function(iframe) {
        var curWindow = window;
        for (var i = 0; i < 10; i++) {
            var objFrame = curWindow[iframe];
            if (objFrame != undefined)
                return objFrame;

            try {
                if (curWindow.location.host == curWindow.parent.location.host)
                    curWindow = curWindow.parent;
                else
                    break;

                if (curWindow == curWindow.parent)
                    break;
            } catch (ex) {
                break;
            }
        }
        return null;
    },

    //Busca de manera recursiva un tipo de nodo padre en el que está insertado un objeto. (Dentro del DOM actual, no verifica niveles superiores de frames).
    GetParentNode: function(obj, type) {
        var parentRow;
        var parentNode = obj.parentNode;

        if (parentNode.tagName.toUpperCase() == type)
            parentRow = parentNode;
        else
            parentRow = MM.VCWUtils.Frames.GetParentNode(parentNode, type);

        return parentRow;
    },

    //Reemplaza la página principal por el contenido de bodyHtml. Si loadInParent es true, buscará recursivamente el documento padre (si esta función se estuviese invocando desde un iframe).
    LoadInParent: function(bodyHtml) {
        var mainDoc = MM.VCWUtils.Frames.GetParentWindow().document;
        //Remueve todos los elementos hijos de mainDoc.pageMainDiv
        var mainDiv = mainDoc.getElementById("pageMainDiv");
        if (mainDiv != null) {
            var lgt = mainDiv.childNodes.length - 1;
            for (var i = lgt; i >= 0; i--) {
                var obj = mainDiv.childNodes[i];
                MM.Ajax.JSDOM_Manager.RemoveDOMElement(obj);
            }
        }

        var div = mainDoc.createElement("div");
        div.innerHTML = bodyHtml;
        mainDoc.body.appendChild(div);
    },

    //Carga un iFrame
    LoadFrame: function(frame, src) {
        var obj = $(frame);
        if (obj == null) {
            var mainWin = MM.VCWUtils.Frames.GetParentWindow();
            if (mainWin != null)
                obj = mainWin.$(frame);
        }
        if (obj != null)
            obj.src = src;
    },

    //********************************************************************************************
    //Función que redimensiona un iframe.
    //********************************************************************************************
    DYNIFS: {
        // Storage for known IFrames.
        iframes: {},
        // Here we save any previously installed onresize handler.
        oldresize: null,
        // Flag that tell us if we have already installed our onresize handler.
        ready: false,
        // The document dimensions last time onresize was executed.
        dim: [-1, -1],
        // Timer ID used to defer the actual resize action.
        timerID: 0,
        // Obtain the dimensions (width,height) of the given document.
        getDim: function(d) {
            var w = 200, h = 200, scr_h, off_h;
            if (d.height) { return [d.width, d.height]; }
            with (d.body) {
                if (scrollHeight) { h = scr_h = scrollHeight; w = scrollWidth; }
                if (offsetHeight) { h = off_h = offsetHeight; w = offsetWidth; }
                if (scr_h && off_h) h = Math.max(scr_h, off_h);
            }
            return [w, h];
        },
        // This is our window.onresize handler.
        onresize: function() {
            // Invoke any previously installed onresize handler.
            if (typeof this.oldresize == 'function') { this.oldresize(); }
            // Check if the document dimensions really changed.
            var dim = this.getDim(document);
            if (this.dim[0] == dim[0] && this.dim[1] == dim[1]) return;
            // Defer the resize action to prevent endless loop in quirksmode.
            if (this.timerID) return;
            this.timerID = setTimeout('MM.VCWUtils.Frames.DYNIFS.deferred_resize();', 10);
        },
        // This is where the actual IFrame resize is invoked.
        deferred_resize: function() {
            // Walk the list of known IFrames to see if they need to be resized.
            for (var id in this.iframes) this.resize(id);
            // Store resulting document dimensions.
            this.dim = this.getDim(document);
            // Clear the timer flag.
            this.timerID = 0;
        },
        // This is invoked when the IFrame is loaded or when the main window is resized.
        resize: function(id) {
            // Browser compatibility check.
            if (!window.frames || !window.frames[id] || !document.getElementById || !document.body)
                return;
            // Get references to the IFrame window and layer.
            var iframe = window.frames[id];
            var div = $(id);
            if (!div) return;
            // Save the IFrame id for later use in our onresize handler.
            if (!this.iframes[id]) {
                this.iframes[id] = true;
            }
            // Should we inject our onresize event handler?
            if (!this.ready) {
                this.ready = true;
                this.oldresize = window.onresize;
                //window.onresize = new Function('DYNIFS.onresize();');
            }
            // This appears to be necessary in MSIE to compute the height when the IFrame'd document is in quirksmode.
            // OTOH, it doesn't seem to break anything in standards mode, so...
            //if (document.all) div.style.height = '0px';

            //Se incluye el Try-Catch para evitar posibles errores al acceder a dominios de terceros. 
            try {
                // Resize the IFrame container.
                var dim;
                if (iframe.document != null)
                    dim = this.getDim(iframe.document);
                else
                    dim = this.getDim(div.contentDocument);

                div.style.height = (dim[1]) + 'px';
            }
            catch (e) { }

            //SCRIPT antiguo que colocaba el scroll de la ventana en la parte superior del IFrame redimensionado.
            //            //Obtiene el scroll actual de la página
            //            var scrollPosition = YAHOO.util.Dom.getDocumentScrollTop(document);
            //            var divPos = YAHOO.util.Dom.getXY(div);

            //            if (divPos[1] < scrollPosition)
            //                parent.scrollTo(0, divPos[1]);

            //Verifica si a su vez está contenido en un iFrame y se asegura de que pertenece al mismo dominio con GetParentWindow
            this.resizeIFrame();
        },

        resizeIFrame: function() {
            try {
                //Normalmente saltará la excepción al acceder al window.parent perteneciente a otro dominio. Si algun browser no lo hiciera tampoco pasará la condicion
                if (window.location.host == window.parent.location.host) {
                    var isInsideAnIframe = window.frameElement;
                    if (isInsideAnIframe != null)
                        parent.MM.VCWUtils.Frames.DYNIFS.resize(isInsideAnIframe.name);
                }

            } catch (e) { }
        }
    }
};

//Funciones para el tratamiendo de Urls.
MM.VCWUtils.Url = {

    //********************************************************************************************
    //Funciones para el tratamiento de cadenas, números y fechas.
    //********************************************************************************************
    //Añade un parámetro en un URL
    AddUrlParam: function(url, param) {
        var idx = url.indexOf("?");
        if (idx == -1)
            url += "?" + param;
        else
            url += "&" + param;

        return url;
    },

    //A partir de una cadena "name=value" obtiene un objeto { "Name": name, "Value": value }
    GetParam: function(param) {
        var x = param.indexOf("=");
        var paramName = param;
        var paramValue = "";
        if (x != -1) {
            paramName = param.substring(0, x);
            paramValue = param.substring(x + 1, param.length);
        }

        return { "Name": paramName, "Value": paramValue };
    },

    //Devuelve un array con los parámetros de una URL. Forma: [ {"Name": paramName, "Value": paramValue }, ...]
    GetUrlParams: function(url) {
        var idx = url.indexOf("?");
        var urlParams = new Array()
        if (idx != -1) {
            var aParams = url.substring(idx + 1, url.length).split("&");
            var lgt = aParams.length;
            for (var i = 0; i < lgt; i++) {
                if (aParams[i] == "")
                    continue;

                var param = this.GetParam(aParams[i]);
                urlParams.push(param);
            }
        }

        return urlParams;
    },

    //Obtiene un parámetro de URL
    GetURLParam: function(param, url) {
        param = param.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regexS = "[\\?&]" + param + "=([^&#]*)";
        var regex = new RegExp(regexS, "gi");
        var results;
        if (url)
            results = regex.exec(url);
        else
            results = regex.exec(window.location.href, name);
        if (results == null)
            return "";
        else
            return results[1];
    }
};

//Funciones para formatear cadenas, números, fechas.
MM.VCWUtils.ParseUtils = {
    //Clase estática que controla los campos numéricos que se insertan en la página.
    NumericFields: {

        Fields: new MM.VCWUtils.HashTable(),

        //Función que añade campos numéricos al array NumericFields. (Este array identifica los campos que deben tratarse como numéricos, para considerar ',' y '.' como separadores decimales según la configuración regional).
        Add: function(fields) {
            var aFields = fields.split(',');
            var lgt = aFields.length;
            for (var i = 0; i < lgt; i++) {
                var obj = $(aFields[i]);
                if (obj != undefined && obj.tagName.toUpperCase() == 'INPUT') {
                    //Añade el evento keypress
                    YAHOO.util.Event.addListener(obj, "keypress", numericKeyPress);

                    //Añade el objeto a la tabla hash
                    this.Fields.Set(aFields[i], obj);
                }
            }
        },

        //Determina si un objeto está marcado como numérico. Recibe el id del objeto como entrada
        IsNumeric: function(idObj) {
            for (var key in this.Fields.Items) {
                if (key == idObj)
                    return true;
            }

            return false;
        },

        Remove: function(fields) {
            var aFields = fields.split(',');
            var lgt = aFields.length;
            for (var i = 0; i < lgt; i++) {
                var id = aFields[i];
                var obj = this.Fields.Get(id);

                //Remueve el evento keypress
                YAHOO.util.Event.removeListener(obj, "keypress");

                //Remueve el objeto del array
                this.Fields.Remove(id);
                obj = null;
            }
        }
    },

    //Función utilizada para convertir String a Double (considerando las configuraciones regionales)
    GetDouble: function(sValue) {
        if (typeof (sValue) == 'number')
            return sValue;

        //Transformación de la cadena de acuerdo al lenguaje seleccionado por el usuario
        if (CurrentCulture == "es-es")
            sValue = sValue.replace(',', '.');

        if (!isFinite(sValue))
            value = NaN;
        else
            value = parseFloat(sValue);

        return value;
    },

    //Redondea un número a una cantidad de decimales. Devuelve un valor tipo Float
    RoundNumber: function(value, precision) {
        var value = MM.VCWUtils.ParseUtils.GetDouble(value);
        var precision = MM.VCWUtils.ParseUtils.GetDouble(precision);
        precision = (!precision ? 2 : precision);

        return Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision); 0
    },

    //Redondea un número a una cantidad de decimales. Devuelve un valor tipo String
    RoundNumberToString: function(value, precision) {
        var number = MM.VCWUtils.ParseUtils.RoundNumber(value, precision).toString();
        var x = number.indexOf('.');
        var dif = precision;

        if (x != -1)
            dif = precision - (number.length - x - 1);
        else
            number += ".";

        for (var i = 0; i < dif; i++)
            number += "0";

        return MM.VCWUtils.ParseUtils.GetString(number);
    },

    //Convierte una cadena con la forma yyyy-MM-dd hh:mm:ss en un objeto Date
    GetDate: function(sDate) {
        var year = sDate.substring(0, 4);
        var month = sDate.substring(5, 7);
        var day = sDate.substring(8, 10);
        var hour = sDate.substring(11, 13);
        var minute = sDate.substring(14, 16);
        var second = sDate.substring(17, 19);

        var date = new Date();
        date.setFullYear(year, month, day);
        date.setHours(hour, minute, second);

        return date;
    },

    //Devuelve una fecha a partir de los valores mes, dia, año
    GetDateFromInt: function(year, month, day) {
        var dt = new Date();
        dt.setDate(day);
        dt.setMonth(month - 1);
        dt.setYear(year);

        return dt;
    },

    // Devuelve un array con los valores YYYY - MM - DD (Este array puede convertirse luego a un objeto YUIDate)
    // sDate - String con la fecha
    // dayPosition - Posición del día en sDate (1, 2, o 3)
    // monthPosition - Posición del mes en sDate (1, 2, o 3)
    // yearPosition - Posición del año en sDate (1, 2, o 3)
    // Si la cadena no es una fecha con el formato especificado devuelve null
    DateToArray: function(sDate, dayPosition, monthPosition, yearPosition, fieldDelimiter) {
        //Obtener los separadores
        var f = sDate.split(fieldDelimiter);
        var fl = f.length;

        if (fl < 2)
            return null;

        var day = 0
        var month = 0;
        var year = 0;
        for (var i = 0; i < fl; i++) {
            if (i + 1 == dayPosition)
                day = parseInt(f[i], 10);

            if (i + 1 == monthPosition)
                month = parseInt(f[i], 10);

            if (i + 1 == yearPosition)
                year = parseInt(f[i], 10);
        }

        if (isNaN(day) || isNaN(month) || isNaN(year))
            return null;
        else
            return [year, month, day];
    },

    //Agrega una cantidad de caracteres a la izquierda de una cadena para asegurarse que tengan una longitud determinada.
    PadLeft: function(chr, value, lgt) {
        var valRtrn = value.toString();
        var valRtrnLgt = valRtrn.length;

        if (lgt < valRtrnLgt)
            return valRtrn;

        var ret = "";
        for (i = 0; i < lgt - valRtrnLgt; i++) {
            ret += chr;
        }

        return ret + valRtrn;
    },

    //Función utilizada para convertir Double a String (considerando las configuraciones regionales)
    GetString: function(value) {
        //    if (typeof (value) != 'number') //Si se habilita este código no funciona RoundNumberToString()
        //        return value;

        //Transformación de la cadena de acuerdo al lenguaje seleccionado por el usuario
        if (CurrentCulture == "es-es")
            value = value.toString().replace('.', ',');

        return value;
    },

    //Elimina los caracteres en blanco a la izquierda de una cadena.
    LTrim: function(s) {
        return s.replace(/^\s+/, "");
    },

    //Elimina los caracteres en blanco a la derecha de una cadena.
    RTrim: function(s) {
        return s.replace(/\s+$/, "");
    },

    //Elimina los caracteres en blanco a la izquierda y la derecha de una cadena.
    Trim: function(s) {
        return MM.VCWUtils.ParseUtils.RTrim(MM.VCWUtils.ParseUtils.LTrim(s));
    },

    //Obtiene el valor de un nodo Xml. Contempla el caso de FireFox que parte los nodos en 4 KB. (El segundo parámetro es opcional e indica el nombre del nodo que se desea buscar).
    GetXmlNodeInnerXml: function(node, nodeToSearch) {
        if (nodeToSearch != null) {
            var nodes = node.getElementsByTagName(nodeToSearch)
            if (nodes == null || nodes.length == 0)
                return null;

            node = nodes[0];
        }

        var value = "";
        var lgt = node.childNodes.length;
        for (var i = 0; i < lgt; i++)
            value += node.childNodes[i].nodeValue;

        return value;
    }
};

//**************************************************************************************************
//FormsManager
//**************************************************************************************************
MM.VCWUtils.FormsManager = {
    Const_OriginalAction: "originalAction",
    Const_MaintainFrames: "mf",
    Const_EncodeParams: "encodeParams",

    //Crea un formulario y lo devuelve
    CreateForm: function(doc, formId, method, action, elements) {
        //Crea el formulario
        var form = doc.createElement("form");
        form.id = formId;
        form.method = method;
        form.action = action;

        var values = new Array();
        var innerHTML = "";
        var lgt = elements.length;
        for (var i = 0; i < lgt; i++) {
            var element = elements[i];
            if (!element.Type)
                element.Type = "hidden";

            //Hay un bug en IE que no añade el atributo name a campos ocultos. De esta manera se soluciona.
            innerHTML += "<input name='" + element.Name + "' type='" + element.Type + "'/>";

            //Guarda el valor para asignarlo posteriormente (no lo asigna aquí para evitar problemas con caracteres especiales, tales como retornos de carro, etc.)
            values.push(element.Value);
        }

        form.innerHTML = innerHTML;

        //Copia los valores que se han guardado en el array
        for (var i = 0; i < lgt; i++)
            form.elements[i].value = values[i];

        return form;
    },

    //Función invocada cuando se produce un fallo al enviar el formulario.
    _validationFailed: function(form) {
        //Restaura el valor original del atributo action.
        MM.VCWUtils.FormsManager.RestoreAction(form);

        //Si no pasó la validación, verifica si está contenido en un IFrame para redimensionarlo
        MM.VCWUtils.Frames.DYNIFS.resizeIFrame();
    },

    //Crea una copia de un formulario con los mismos valores pero en campos ocultos.
    //(En caso de tratarse de un formulario externo con runat=server devuelve el mismo formulario)
    _copyForm: function(form, doc) {
        if (form.getAttribute("id") != "aspnetForm" && form.getAttribute("name") != "aspnetForm") {
            //Copia todos los valores de los input en campos ocultos.
            var elements = new Array();

            var lgt = form.elements.length;
            for (var i = 0; i < lgt; i++) {
                var elem = form.elements[i];
                var id = elem.id;
                var name = elem.name;
                var type = elem.type.toLowerCase();

                if (name == null || name == "" || type == "submit" || type == "button")
                    continue;

                var value = elem.value;

                //Comprueba si es un campo numérico.
                if (MM.VCWUtils.ParseUtils.NumericFields.IsNumeric(id))
                    value = MM.VCWUtils.ParseUtils.GetDouble(value);

                //Si es de tipo checkBox o RadioButton, solo se añade en el caso de que esté chequeado.
                if ((type != "radio" && type != "checkbox") || elem.checked)
                    elements.push({ "Name": name, "Type": "hidden", "Value": value });

                //Elimina la referencia
                elem = null;
            }

            var formId = form.id + "_" + MM.VCWUtils.GetUniqueValue();
            var formCopy = this.CreateForm(doc, formId, form.method, form.action, elements);

            //Añade los atributos del formulario original
            formCopy.setAttribute(MM.VCWUtils.FormsManager.Const_MaintainFrames, form.getAttribute(MM.VCWUtils.FormsManager.Const_MaintainFrames));
            formCopy.setAttribute(MM.VCWUtils.FormsManager.Const_OriginalAction, form.getAttribute(MM.VCWUtils.FormsManager.Const_OriginalAction));

            return formCopy;
        } else {
            for (var key in MM.VCWUtils.ParseUtils.NumericFields.Fields.Items) {
                var numericInput = MM.VCWUtils.ParseUtils.NumericFields.Fields.Items[key];
                numericInput.value = MM.VCWUtils.ParseUtils.GetDouble(numericInput.value);
            }

            return form;
        }
    },

    //Prepara un formulario antes del envío haciendole pasar por toda las validaciones. Si pasa las validaciones devuelve una copia del formulario (con los campos ocultos).
    //En caso de no pasar las validaciones devuelve null
    PrepareForm: function(form, url, doc) {
        //Establece el valor del atributo OriginalAction
        if (form.getAttribute(MM.VCWUtils.FormsManager.Const_OriginalAction) == undefined)
            form.setAttribute(MM.VCWUtils.FormsManager.Const_OriginalAction, form.action);

        //Obtiene todos los iframes del documento
        if (form.getAttribute(MM.VCWUtils.FormsManager.Const_MaintainFrames)) {
            var frames = document.getElementsByTagName("iframe");
            var lgt = frames.length;
            for (var i = 0; i < lgt; i++) {
                if (frames[i].id != undefined && frames[i].id != "") {
                    //Por cada FRAME crea un campo oculto
                    var hdn = document.createElement('input');
                    hdn.setAttribute("type", "hidden");
                    hdn.setAttribute("id", frames[i].id);
                    hdn.setAttribute("name", frames[i].id);
                    hdn.setAttribute("value", frames[i].contentWindow.location.href);
                    form.appendChild(hdn);
                }
            }
        }

        if (url != "")
            form.action = url;

        //Por compatibilidad con LiveValidation verifica si onsubmit tiene alguna función cargada.
        var submit = true;
        try {
            if (form.onsubmit != undefined)
                submit = form.onsubmit();
        } catch (ex) {
            this._validationFailed(form);
            return null;
        }

        if (submit) {
            //Antes de realizar el envío del formulario, verifica si debe ejecutar alguna función que preprocese los campos.
            try {
                var onBS = form.getAttribute("onBeforeSubmit");
                if (onBS != null)
                    eval(onBS + "()");
            }
            catch (ex) {
                this._validationFailed(form);
                throw new Error("Submit Error.");
            }

            //Cuando el método de envío es GET, se asegura que se envíen los parámetros definidos en action (para ello, los añade como campos ocultos).
            if (form.method.toLowerCase() == "get") {
                var urlParams = MM.VCWUtils.Url.GetUrlParams(form.action);
                var lgt = urlParams.length;
                var formLgt = form.length;
                var innerHTML = "";

                //Recorre los parámetros definidos en el action
                for (var i = 0; i < lgt; i++) {
                    //Recorre los campos del formulario
                    var exist = false;
                    for (var j = 0; j < formLgt; j++) {
                        if (urlParams[i].Name.toLowerCase() == form[j].name.toLowerCase()) {
                            exist = true;
                            break;
                        }
                    }

                    //Si no existe lo añade como campo oculto.
                    if (!exist)
                    //Lo añade de esta manera por un bug de IE que no añade el name a un input oculto por javascript.
                        innerHTML += "<input name='" + urlParams[i].Name + "' type='hidden' value='" + unescape(urlParams[i].Value) + "' />";
                }

                if (innerHTML.length > 0) {
                    var divUrlParams = document.createElement('div'); //DIV donde se insertarán los parámetros de la URL (como campos ocultos).
                    divUrlParams.innerHTML = innerHTML;
                    form.insertBefore(divUrlParams, form.childNodes[0]);
                    divUrlParams = null;
                }
            }

            //Ha pasado todas las validaciones, por lo tanto hace la copia del form.
            return MM.VCWUtils.FormsManager._copyForm(form, doc);
        }
        else
            this._validationFailed(form, divUrlParams);

    },

    //Encodea los parámetros de una url
    EncodeParams: function(url) {
        var urlMembers = url.split('?');
        var host = urlMembers[0];
        var aParams = null;
        if (urlMembers.length > 1)
            aParams = urlMembers[1].split('&');

        url = host + "?";
        var lgt = aParams.length;
        for (var i = 0; i < lgt; i++) {
            var aParam = aParams[i].split('=');
            var paramName = aParam[0];
            var paramValue;
            if (aParam.length > 1)
                paramValue = escape(aParam[1]); //Vuelve a encodear el parámetro
            url += paramName + "=" + paramValue + "&";
        }

        if (lgt > 0)
            url = url.substring(0, url.length - 1);

        return url;
    },

    //Envía un formulario. Esta función debe ser invocada solo desde el action de un formulario. De esta manera se asegura de volver a encodear los parámetros de la url
    //ya que los navegadores quitan el encodeo al llamar a una función.
    Submit_DefaultAction: function(type, formId, url, sendToFrame) {
        url = MM.VCWUtils.FormsManager.EncodeParams(url);

        if (type == "Ajax")
            MM.VCWUtils.FormsManager.SubmitFormAjax(formId, url);
        else
            MM.VCWUtils.FormsManager.SubmitForm(formId, url, sendToFrame);
    },

    //Envía un formulario de manera tradicional
    SubmitForm: function(formId, url, sendToFrame) {
        var form = $(formId);

        //Determina el documento donde creará la copia
        var doc = document;
        if (sendToFrame != undefined && sendToFrame != "")
            doc = MM.VCWUtils.Frames.GetIFrame(sendToFrame).document;

        //Obtiene el formulario que realmente enviará.
        var formToSend = MM.VCWUtils.FormsManager.PrepareForm(form, url, doc);
        if (formToSend) {
            //Se asegura que no se hará submit a una página que devuelve un xml.
            var actionToEval = formToSend.action.toLowerCase();
            if (actionToEval.indexOf("mmajax.aspx") != -1 || actionToEval.indexOf("xmlservices.aspx") != -1)
                ThrowJSError("Invalid submit action.");

            //Si el destino no es ni un frame se hace un submit común.
            if (sendToFrame == undefined || sendToFrame == "") {
                MM.VCWUtils.LoadingDialog.Instances["LD_Base"].Show();
                if (formId != "aspnetForm") {
                    $("DivExtended").appendChild(formToSend);
                    formToSend.submit();
                    MM.Ajax.JSDOM_Manager.RemoveDOMElement(formToSend);
                }
                else {
                    //Si se trata de un formuario externo se envía directamente ya que no se trata de una copia sino del original.
                    formToSend.submit();
                }
            }
            else {
                var objSendToFrame = MM.VCWUtils.Frames.GetIFrame(sendToFrame);
                if (objSendToFrame == undefined)
                    ThrowJSError("Invalid IFrame.");

                var divInFrame = objSendToFrame.document.createElement("div");
                divInFrame.style.display = "none";
                objSendToFrame.document.body.appendChild(divInFrame);

                //Crea un nuevo formulario en el div del iframe.
                divInFrame.appendChild(formToSend);
                formToSend.submit();
                //En este caso no es necesario eliminar formToSend ya que se carga en el iframe.

                //Restaura el action original.
                MM.VCWUtils.FormsManager.RestoreAction(form);
            }
        }
    },

    //Envia un formulario por Ajax
    SubmitFormAjax: function(formId, url) {
        var form = $(formId);

        //Obtiene el formulario que realmente enviará.
        var formToSend = MM.VCWUtils.FormsManager.PrepareForm(form, url, document);
        if (formToSend) {
            //Cuando se envía la petición por Ajax debe restaurar el valor original del atributo action. (Lo restaura antes de realizar el envío porque podría fallar la petición Ajax).
            MM.VCWUtils.FormsManager.RestoreAction(form);

            var callback = { "PreCallEvent": MM.VCWUtils.FormsManager.DisableFormElements, "PreCallParams": [form], "PostCallEvent": MM.VCWUtils.FormsManager.EnableFormElements, "PostCallParams": [form] };
            if (formToSend.method.toLowerCase() == "get") {
                //Añade todos los campos del formulario a la url para que se envien por get.
                url = url.split("?")[0]; //Se eliminan todos los parámetros (el método _prepareForm copió todos los parámetros de la url a campos ocultos del formulario)
                var lgt = formToSend.elements.length;
                for (var i = 0; i < lgt; i++) {
                    if (formToSend.elements[i].name != "")
                        url = MM.VCWUtils.Url.AddUrlParam(url, formToSend.elements[i].name + "=" + escape(formToSend.elements[i].value));
                }

                MM.Ajax.Html.Request(url, null, callback);
            }
            else {
                //Forma el array con los parámetros que se enviarán por post.
                var lgt = formToSend.elements.length;
                var postParams = new Array();
                for (var i = 0; i < lgt; i++) {
                    if (formToSend.elements[i].name != "")
                        postParams.push({ "Name": formToSend.elements[i].name, "Value": formToSend.elements[i].value });
                }

                MM.Ajax.Html.Request(url, postParams, callback);
            }
        }
    },

    //Restaura el valor original del atributo action de un formulario.
    RestoreAction: function(form) {
        form.setAttribute("action", form.getAttribute(MM.VCWUtils.FormsManager.Const_OriginalAction));
    },

    DisableFormElements: function(requestDetails, params) {
        var form = params[0];
        var lgt = form.elements.length;
        for (var i = 0; i < lgt; i++)
            form.elements[i].disabled = true;
    },

    EnableFormElements: function(requestDetails, params) {
        var form = params[0];
        var lgt = form.elements.length;
        for (var i = 0; i < lgt; i++)
            form.elements[i].disabled = false;
    },
    
    //Busca y obtiene un elemento de un formulario en función de su nombre.
    GetElement: function(form, nameElement) {
        var lgt = form.elements.length;
        for (var i = 0; i < lgt; i++) {
            if (form.elements[i].name == nameElement)
                return form.elements[i];
        }
        return null;
    }
}

//********************************************************************************************
//Objeto LoadingDialog
//********************************************************************************************
MM.VCWUtils.LoadingDialog = function(loadingDialogStyle, container) {
    var id;
    if (container == null)
        id = "LD_Base";
    else {
        id = "LD_" + MM.VCWUtils.LoadingDialog.Id.toString();
        MM.VCWUtils.LoadingDialog.Id++;
    }
    var idDots = id + "_Dots";
    var dots = 1;
    var loadingDialogDiv = null;
    var references = 0; //Número de referencias que mantienen activa la instancia actual.
    var timerStarted = false;

    //Añade instancia actual
    MM.VCWUtils.LoadingDialog.Instances[id] = this;

    MM.Ajax.JSDOM_Manager.AddRelation(container, [this]);

    this.destroy = function() {
        delete MM.VCWUtils.LoadingDialog.Instances[id];
    }

    this.Hide = function(url) {
        references--;
        if (references == 0)
            loadingDialogDiv.style.display = "none";
    }

    this.Show = function(url) {
        references++;

        if (loadingDialogDiv == null) {
            //Crea el objeto DIV
            loadingDialogDiv = document.createElement('div');
            var loadingDialogImage = loadingDialogStyle.ImageUrl;
            loadingDialogDiv.setAttribute("id", id);
            loadingDialogDiv.style.position = (document.compatMode == "CSS1Compat") ? "absolute" : "fixed"; //por compatibilidad con IE 6
            loadingDialogDiv.style.display = "none";
            loadingDialogDiv.style.zIndex = "9999991"; //Este valor está correlacionado con el div definido en MM.Ajax.Html.LockContainer

            if (container == null)
                $("DivExtended").appendChild(loadingDialogDiv);
            else
                container.appendChild(loadingDialogDiv);

            if (loadingDialogStyle.InnerHTML != "") {
                //Prepara HTML
                var dots = "<span id='" + idDots + "'></span>";
                var innerHTML = loadingDialogStyle.InnerHTML.replace("__SL_ANIMATED_DOTS__", dots); //SL_ANIMATED_DOTS es resuelto en el cliente
                loadingDialogDiv.innerHTML = innerHTML;
            } else if (loadingDialogImage != "")
                loadingDialogDiv.innerHTML = "<i" + "mg src='" + loadingDialogImage + "' alt='' />";
        }

        loadingDialogDiv.style.display = "";

        var loadDimen = GetObjectDimensions(loadingDialogDiv);
        var l = 0;
        var t = 0;
        var h;
        var w;
        if (container == null) {
            h = document.documentElement.clientHeight;
            w = document.documentElement.clientWidth;
        } else {
            var hw = GetObjectDimensions(container)
            h = hw[0];
            w = hw[1];
        }
        var hl = loadDimen[0];
        var wl = loadDimen[1];

        if (h > 0)
            t = (h / 2) + t - (hl / 2);

        if (w > 0)
            l = (w / 2) + l - (wl / 2);

        loadingDialogDiv.style.top = t + "px";
        loadingDialogDiv.style.left = l + "px";

        //ACTIVA ANIMACIÓN PUNTOS
        if (!timerStarted) {
            setTimeout("MM.VCWUtils.LoadingDialog.RefreshDots('" + id + "');", loadingDialogStyle.LoadDotsFrequency);
            timerStarted = true;
        }

        if (url != null && url != "")
            window.open(url, "_self", "", "");
    };

    this.RefreshDots = function() {
        var obj = $(idDots);
        if (obj && references > 0) {
            if (dots > loadingDialogStyle.MaxDots) {
                dots = 0;
                obj.innerHTML = "";
            }
            else
                obj.innerHTML = obj.innerHTML + ".";

            dots++;
            setTimeout("MM.VCWUtils.LoadingDialog.RefreshDots('" + id + "');", loadingDialogStyle.LoadDotsFrequency);
        }
        else
            timerStarted = false;
    }
}

MM.VCWUtils.LoadingDialog.Id = 0;
MM.VCWUtils.LoadingDialog.Instances = new Array();
MM.VCWUtils.LoadingDialog.RefreshDots = function(id) {
    if (MM.VCWUtils.LoadingDialog.Instances[id])
        MM.VCWUtils.LoadingDialog.Instances[id].RefreshDots();
};


/***************************************************************************************************************************************/
//ErrorHandler
/*****************************************************************************************************************************************/
MM.VCWUtils.ErrorHandler = {

    //Mensaje que se mostrará por defecto cuando no exista un FriendlyErrorMessage para la excepción actual.
    DefaultFriendlyErrorMessage: null,

    //Array que almacena todos los contenedores de error de la página. Estructrua:
    //ErrorContainers[errorCointainerId] = { "ErrorContainer": referenciaAlObjeto, "ErrorContainerMessages": referenciaAlObjeto, }
    ErrorContainers: null,

    //Url que invocará cuando se utilice el Handler ErrorHandler.Modal
    ModalUrl: null,

    //Añade un contenedor al array de contenedores
    AddErrorContainer: function(errorContainerId, errorContainer, errorContainerMessages) {
        //Comprueba si existe un contenedor con el mismo nombre
        if (MM.VCWUtils.ErrorHandler.ErrorContainers == null)
            MM.VCWUtils.ErrorHandler.ErrorContainers = new Array();
        else if (MM.VCWUtils.ErrorHandler.ErrorContainers[errorContainerId] != null)
            return;

        //Añade el nuevo contenedor al array
        var ecObj = $(errorContainer);
        var ecmObj = $(errorContainerMessages);
        if (ecObj == null || ecmObj == null)
            throw new Error("Invalid error container.");

        MM.VCWUtils.ErrorHandler.ErrorContainers[errorContainerId] = { "ErrorContainer": ecObj, "ErrorContainerMessages": ecmObj };
    },

    //Devuelve el contenedor de error relacionado con el id. Si no lo encuentra, devuelve el base.
    GetErrorContainer: function(errorContainerId) {
        if (MM.VCWUtils.ErrorHandler.ErrorContainers[errorContainerId])
            return MM.VCWUtils.ErrorHandler.ErrorContainers[errorContainerId].ErrorContainer;

        return MM.VCWUtils.ErrorHandler.ErrorContainers["Base"].ErrorContainer;
    },

    RemoveErrorContainer: function(errorContainerId) {
        delete MM.VCWUtils.ErrorHandler.ErrorContainers[errorContainerId];
    },

    //Muestra un contenedor de errores genérico
    ContainerHandler: function(errorContainerId, errorCode, errorMessage, stackTrace, friendlyErrorMessage) {
        var errorContainer = MM.VCWUtils.ErrorHandler.ErrorContainers[errorContainerId];

        if (errorContainer == null)
        //Utiliza el contenedor por defecto
            errorContainer = MM.VCWUtils.ErrorHandler.ErrorContainers["Base"];

        //Prepara el mensaje firendlyErrorMessage
        if (friendlyErrorMessage == null || friendlyErrorMessage == "")
            friendlyErrorMessage = MM.VCWUtils.ErrorHandler.DefaultFriendlyErrorMessage;

        if (errorContainer == undefined)
            throw new Error(friendlyErrorMessage);

        errorContainer.ErrorContainerMessages.innerHTML = friendlyErrorMessage;

        //Muestra el contenedor de error (verifica si debe mostrar el objeto padre)
        if (errorContainer.ErrorContainer.parentNode.style.display == "none")
            errorContainer.ErrorContainer.parentNode.style.display = "block";
        else
            errorContainer.ErrorContainer.style.display = "block";

        //Añade los detalles del error al objeto WarningsViewer
        if (!MM.AppInfo.ReleaseMode)
            AddError("URL - PENDIENTE", errorMessage, stackTrace);
    },

    ModalHandler: function(errorContainerId, errorCode, errorMessage, stackTrace, friendlyErrorMessage) {
        if (this.ModalUrl == null)
            this.ContainerHandler(errorContainerId, errorCode, errorMessage, stackTrace, friendlyErrorMessage);

        if (friendlyErrorMessage == null || friendlyErrorMessage == "")
            friendlyErrorMessage = MM.VCWUtils.ErrorHandler.DefaultFriendlyErrorMessage;

        var postParams = new Array();
        postParams.push({ "Name": "errorCode", "Value": errorCode });
        postParams.push({ "Name": "errorMessage", "Value": errorMessage });
        postParams.push({ "Name": "friendlyErrorMessage", "Value": friendlyErrorMessage });

        //Añade a ModalUrl el atributo ReqType
        var modalUrl = this.ModalUrl;
        modalUrl += "&ReqType=10";

        MM.Ajax.Html.Request(modalUrl, postParams, null);
    },

    //Invoca un handler de error específico
    Invoke: function(errorHandler, errorContainerId, errorCode, errorMessage, stackTrace, friendlyErrorMessage) {
        if (errorHandler == null || errorHandler == "" || !MM.VCWUtils.ErrorHandler[errorHandler])
            errorHandler = "Base";

        MM.VCWUtils.ErrorHandler[errorHandler](errorContainerId, errorCode, errorMessage, stackTrace, friendlyErrorMessage);
    }
}

//El handler de error por defecto será el ContainerHandler.
MM.VCWUtils.ErrorHandler.Base = MM.VCWUtils.ErrorHandler.ContainerHandler;

///////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////   MM.AJAX   /////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////

//Objeto que concentrará todas las funciones necesarias para manejar peticiones Ajax.
MM.Ajax = {};
//********************************************************************************************
//Objeto JSLoader
//********************************************************************************************
MM.Ajax.JSLoader = {
    HasBeenAdded: function(file, type) {
        var files = null;
        var attribute = null;
        if (type == "text/css") {
            files = MM.Ajax.JSLoader.Header.getElementsByTagName('link');
            attribute = "href";
        }
        else {
            files = MM.Ajax.JSLoader.MMScripts.getElementsByTagName('SCRIPT');
            attribute = "src";
        }

        var lgt = files.length;
        for (var i = 0; i < lgt; i++) {
            var attr = files[i].getAttribute(attribute);
            if (attr != null && attr.toLowerCase() == file)
                return true;
        }

        return false;
    },

    IsFlushing: false,
    Scripts: new Array(),

    JSLoad: function(file, innerHTML, currentLockedContainer) {
        file = file.toLowerCase();

        var added = false;
        var lgt = MM.Ajax.JSLoader.Scripts.length;
        for (var i = 0; i < lgt && !added; i++)
            if (MM.Ajax.JSLoader.Scripts[i].JSFile == file)
            added = true;

        if (!added)
            MM.Ajax.JSLoader.Scripts.push({ "JSFile": file, "Script": innerHTML, "LockedContainer": currentLockedContainer });
    },

    CSSLoad: function(file, innerHTML) {
        file = file.toLowerCase();
        if (!MM.Ajax.JSLoader.HasBeenAdded(file, "text/css")) {
            //Inserta el CSS
            var css = document.createElement('link');
            css.setAttribute('href', file);
            css.setAttribute('type', "text/css");
            css.setAttribute('rel', 'stylesheet');
            if (innerHTML != null)
                css.innerHTML = innerHTML;

            MM.Ajax.JSLoader.Header.appendChild(css);
        }
    },

    JSFlush: function() {
        if (!MM.Ajax.JSLoader.IsFlushing)
            MM.Ajax.JSLoader.LoadNextScript();
    },

    LoadNextScript: function() {
        MM.Ajax.JSLoader.IsFlushing = true;

        var js = null;
        var lgt = MM.Ajax.JSLoader.Scripts.length;
        for (var i = 0; i < lgt; i++) {
            js = MM.Ajax.JSLoader.Scripts[i];

            if (js.LockedContainer != null && js.LockedContainer.Aborted) {
                js = null;
                MM.Ajax.JSLoader.Scripts.splice(i, 1);
                i--;
                lgt--;
                continue;
            }

            if (js.Script == null) {
                if (MM.Ajax.JSLoader.HasBeenAdded(js.JSFile, "text/javascript")) {
                    js = null;
                    MM.Ajax.JSLoader.Scripts.splice(i, 1);
                    i--;
                    lgt--;
                    continue;
                }
                else {
                    script = document.createElement('script');
                    script.setAttribute('src', js.JSFile);
                    MM.Ajax.JSLoader.MMScripts.appendChild(script);
                    break;
                }
            }
            else {
                script = document.createElement('script');
                script.setAttribute('id', 'externalScript_' + MM.VCWUtils.GetUniqueValue());
                //En VC5 se añaden los SCRIPTS de google de manera especial.
                if (js.Script.indexOf('/script') != -1 || !MM.AppInfo.ReleaseMode)
                    script.text = js.Script;
                else {
                    script.text = "try { " + js.Script + "} catch (ex) ";
                    script.text += "{ ";
                    script.text += "MM.VCWUtils.ErrorHandler.Base(\"Base\", 1000, \"Error JavaScript: \" + ex.number + \" - \" + ex.name + \" - \" + ex.message, null, \"Error JavaScript: \" + ex.number + \" - \" + ex.name + \" - \" + ex.message);";
                    script.text += "MM.Ajax.JSLoader.JSLoaded('" + js.JSFile + "');";
                    script.text += "}";
                }

                MM.Ajax.JSLoader.MMScripts.appendChild(script);
                break;
            }
        }

        //js es distinto de null cuando se ha evaluado o se ha insertado el script. Es igual a null cuando todos los scripts ya han sido insertados.
        if (js == null)
        //Si ya se han descargado todos los script hay que ejecutar el CallBack
            MM.Ajax.JSLoader.IsFlushing = false;
    },

    Header: document.getElementsByTagName('head')[0],

    MMScripts: $('MMScripts'),

    JSLoaded: function(file) {
        file = file.toLowerCase();

        //Si el primer SCRIPT coincide
        if (MM.Ajax.JSLoader.Scripts.length == 0 || MM.Ajax.JSLoader.Scripts[0].JSFile == file) {
            //Eliminamos el elemento del array
            MM.Ajax.JSLoader.Scripts.splice(0, 1);

            if (MM.Ajax.JSLoader.IsFlushing)
                MM.Ajax.JSLoader.LoadNextScript();
        }
    }
}

/***************************************************************************************************************************************/
//JSDOM_Manager
/***************************************************************************************************************************************/
MM.Ajax.JSDOM_Manager = {
    id: 0,

    // Array asociativo que contendrá elementos con la siguiente estructura:{ “JSFunctions”: [ jsObjs ], “Listeners”: (booleano) }
    Objects: new Array(),

    AddRelation: function(domObj, jsObjs) {
        if (!domObj)
            return;

        var jsDomId = domObj.getAttribute("JSDOM");
        if (jsDomId != null) {
            lgt = jsObjs.length;
            for (var i = 0; i < lgt; i++)
                this.Objects[jsDomId].JSFunctions.push(jsObjs[i]);
        }
        else {
            jsDomId = "jsDom_" + this.id;
            domObj.setAttribute("JSDOM", jsDomId);
            this.Objects[jsDomId] = { "JSFunctions": jsObjs, "Listeners": false };
            this.id++;
        }
    },

    RemoveDOMElement: function(obj) {
        var JSDOMElements = new Array();
        var removeFromDom = function(obj) {
            //Se asegura que no sea un nodo de texto
            if (obj.getAttribute) {
                if (obj.getAttribute("JSDOM") != null)
                    JSDOMElements.push(obj);

                //Busca entre los objetos hijos
                var lgt = obj.childNodes.length;
                for (var i = 0; i < lgt; i++)
                    removeFromDom(obj.childNodes[i]);
            }
        }

        removeFromDom(obj);

        var lgt = JSDOMElements.length;
        for (var i = 0; i < lgt; i++) {
            var jsDomId = JSDOMElements[i].getAttribute("JSDOM");
            if (MM.Ajax.JSDOM_Manager.Objects[jsDomId] == null)
                continue;
            var jsObjs = MM.Ajax.JSDOM_Manager.Objects[jsDomId].JSFunctions;
            var jsObjsLgt = jsObjs.length;
            if (jsObjsLgt == undefined)
                ThrowJSError("Error: Invalid array.");

            for (var j = jsObjsLgt - 1; j >= 0; j--) {
                if (jsObjs[j]) {
                    if (!(jsObjs[j].destroy))
                        ThrowJSError("Error: Object '" + jsObjs[j] + "' does not implement a destructor.");
                    jsObjs[j].destroy();

                    //Elimina el elemento del array.
                    jsObjs.splice(0, 1);
                }
            }

            delete this.Objects[jsDomId];
        }

        //Remueve el control de la jerarquía de objetos. (Si es que tiene un ID).
        if (typeof (MM.Ajax.NodeSetsManager) != "undefined" && obj.id != null && obj.id != "")
            MM.Ajax.NodeSetsManager.RemoveObject(obj);

        try {
            //En algunos casos, (como en los TABS de YUI) el destructor elimina del DOM los elementos hijos.
            if (obj.parentNode != null)
                obj.parentNode.removeChild(obj);

            if (obj.outerHTML != null)
                obj.outerHTML = "";
            obj = null;
        }
        catch (ex) {
        }
    }
};

//Evalúa el nombre de un contenedor. Recibe un valor con la forma CONTAINER|0. Verifica si se trata de un TAB o un TEMPLATE y devuelve un objeto con información detallada.
MM.Ajax.EvalContainerId = function(containerId) {
    var details = {};
    var HMdefined = typeof (MM.Ajax.NodeSetsManager) != "undefined";

    //Evalúa el nombre del contenedor. Puede recibir: TAB|-1 CONTAINER|0
    var x = containerId.lastIndexOf('_');
    if (x != -1) {
        var idx = parseInt(containerId.substring(x + 1, containerId.length));
        if (!isNaN(idx)) {
            containerId = containerId.substring(0, x);

            //Verifica si se trata de un TAB
            var tabViewName = containerId.substring(3, x) + "_0"; //Para los tabs asume siempre la primera aparición de la misma sección.
            if (HMdefined)
                tabViewName = MM.Ajax.NodeSetsManager.EvalObjectId(tabViewName);

            if (MM.Controls != undefined && MM.Controls.TabsView != undefined) {
                var tabIndex = MM.Controls.TabsView.GetActiveTab(tabViewName);
                if (tabIndex != -1) {
                    //Si recibe TAB|-1 significa que se actualizará el tab actual.
                    if (idx != -1)
                        tabIndex = idx;

                    //Devuelve el nombre del contenedor que muestra la pestaña activa (para que no actualice todo el TAB) sino solo la pestaña activa.
                    details.TabIndex = tabIndex;
                    details.TabViewName = tabViewName;
                    details.ContainerId = tabViewName + "_" + tabIndex + "_TABCONTAINER";
                    return details;
                }
            }

            containerId += "_" + idx.toString();
        }
    }

    if (HMdefined)
        details.ContainerId = MM.Ajax.NodeSetsManager.EvalObjectId(containerId);
    else
        details.ContainerId = containerId;

    return details;
}

//Objeto que maneja los Timers de la aplicación.
MM.VCWUtils.TimersManager = {

    //Estados de un timer.
    _started: "STARTED",
    _stopped: "STOPPED",

    //Tabla Hash que guarda objetos compuestos por la siguiente estructura: { TimeOutId, IdTimer, Frequency, Status, CallBack, Parameters[]}
    _timers: new MM.VCWUtils.HashTable(),

    //Llama a la función Callback y vuelve a ejecutar el timer.
    _executeCallback: function(idTimer) {

        var timer = this._getTimer(idTimer);

        //Llama a la función callback
        timer.Callback(timer.Parameters);

        //Vuelve a ejecutar el timer si éste no está parado.
        if (timer.Status == this._started)
            this._startTimer(idTimer);
    },

    //Ejecuta el timer y actual el valor de timeOutId, cuya referencia nos servirá para poder realizar clearTimeOut.
    _startTimer: function(idTimer) {

        var timer = this._getTimer(idTimer);

        //Actualiza el valor de timeOutId (del objeto timer)
        timer.TimeOutId = setTimeout("MM.VCWUtils.TimersManager._executeCallback('" + timer.IdTimer + "')", timer.Frequency);
    },

    //Obtiene el timer correspondiente al id que le pasamos, comprobando si éste existe.
    _getTimer: function(idTimer) {
        var timer = this._timers.Get(idTimer);

        if (!timer)
            ThrowJSError("'" + idTimer + "' is not a valid timer.");

        return timer;
    },

    //Crea un timer
    // - idTimer: identificador del Timer.
    // - frequency: frecuencia con la que se va a ejecutar la función asociada al timer.
    // - callback: función asociada al timer que se invoca.
    // - parameters : opcinal, array con los parámetros que son pasados a la función callback.
    CreateTimer: function(idTimer, frequency, callback, parameters) {
        //Verifica si el timer existe.
        if (this._timers.HasItem(idTimer))
            ThrowJSError("The '" + idTimer + "' timer has already been added.");

        //Comprueba que se haya facilitado una función para el timer.
        if (!callback)
            ThrowJSError("You must define a valid callback function for the '" + idTimer + "' timer.");

        if (frequency == 0)
            ThrowJSError("You must define a valid frequency for the '" + idTimer + "' timer.");

        //Crea nuevo timer y lo añade a la tabla hash.
        var newTimer = { "TimeOutId": null, "IdTimer": idTimer, "Frequency": frequency, "Status": this._stopped, "Callback": callback, "Parameters": parameters };
        this._timers.Set(idTimer, newTimer);

        //Ejecuta el timer.
        this.StartTimer(idTimer);
    },

    //Obtiene el timer correspondiente al id que le pasamos.
    GetTimer: function(idTimer) {
        return this._timers.Get(idTimer);
    },

    //Inicia el Timer, estableciendo el estado a "STARTED".
    StartTimer: function(idTimer) {
        var timer = this._getTimer(idTimer);

        if (timer.Status == this._started)  //Si ya está iniciado, lanza una excepción.
            ThrowJSError("The '" + idTimer + "' timer has already been started.");

        timer.Status = this._started;

        this._startTimer(idTimer);
    },

    //Detiene el timer, estableciendo el estado a "STOPPED"
    StopTimer: function(idTimer) {
        var timer = this._getTimer(idTimer);

        //Si ya está detenido, lanza una excepción.
        if (timer.Status == this._stopped)//Si ya está detenido, lanza una excepción.
            ThrowJSError("The '" + idTimer + "' timer has already been stopped.");

        clearTimeout(timer.TimeOutId);

        timer.Status = this._stopped;
    },

    //Elimina el timer de la tabla hash, y corta las llamadas pendientes generadas por setTimeOut.
    RemoveTimer: function(idTimer) {
        var timer = this._getTimer(idTimer);

        clearTimeout(timer.TimeOutId);

        this._timers.Remove(idTimer);
    }
};

//Sobrecarga la función YAHOO.util.Dom.getXY ya que la versión 2.8 de YUI tiene un bug por el cual en IE 7 devuelve false cuando la posición del objeto es fixed.
//Este bug está resuelto en la versión 3.0
if (window["YAHOO"] != undefined && YAHOO.util != undefined && YAHOO.util.Dom != undefined) {
    YAHOO.util.Dom.getXY = function(obj) {
        var curleft = curtop = 0;
        if (obj) {
            if (obj.offsetParent) {
                curleft = obj.offsetLeft
                curtop = obj.offsetTop
                while (obj = obj.offsetParent) {
                    curleft += obj.offsetLeft
                    curtop += obj.offsetTop
                }
            }
        }

        return [curleft, curtop];
    }

    //Verifica si el objeto es visible (debe ser visible tanto él como sus nodos padres).
    YAHOO.util.Dom.isVisible = function(obj) {

        var parent = obj;
        while (parent != null) {
            //En el caso de los tabs-view, el div de las pestañas que no se muestran tienen asignada la clase css 'yui-hidden'
            if ((parent.style != null && parent.style.display != null && parent.style.display.toLowerCase() == 'none') || YAHOO.util.Dom.hasClass(parent, "yui-hidden"))
                return false;

            parent = parent.parentNode;
        }
        return true;
    }
}
if (window["MM"]!=undefined) 
 MM.Ajax.JSLoader.JSLoaded("js/visualchartwebclient/utils.js");