﻿if (window["MM"] == undefined)
    var MM = {};

if (MM.Ajax == undefined)
    MM.Ajax = {};

MM.Ajax.MMTable = function(tableId) {
    //Variables privadas
    var tableObj = $(tableId);
    var mmData = null;
    var oldCellStyles = new Array();
    var patternCfg = null;
    var sortStyle = null;
    var highlightStyle = null;
    var selectedStyle = null;

    if (tableObj == null)
        ThrowJSError("'" + tableId + "' is not a valid table.");

    //Añade la instancia creada a la colección MMTables
    MM.Ajax.MMTable.MMTables[tableId] = this;

    MM.Ajax.JSDOM_Manager.AddRelation(tableObj, [this]);

    this.destroy = function() {
        //Elimina los vínculos con secciones contextuales que pudieran existir
        var csLgth = contextSections.length;
        if (csLgth > 0) {
            for (var i = 0; i < csLgth; i++) {
                var contextSection = MM.Controls.ContextSectionsManager.ContextSections.Get(contextSections[i]);
                if (contextSection != null)
                    contextSection.RemoveLink(tableId);
            }
        }

        contextSections = null;

        //Deshabilita el streaming para este objeto.
        MM.Ajax.Data.RemoveSection(tableId);

        //Elimina las referencias a las filas de la tabla
        for (var elem in mmData.Items) {
            var mmDataValue = mmData.Get(elem);

            var lgt = patternCfg.Cells.length;
            for (var i = 0; i < lgt; i++) {
                mmDataValue.Cells[i].Td = null;
                mmDataValue.Cells[i] = null;
            }
        }

        //Destruye el objeto
        delete MM.Ajax.MMTable.MMTables[tableId];
        tableObj = null;
        eval("delete mmt_" + tableId);
    };

    //Métodos privados
    //Actualiza las referencias a las celdas de una tabla, siguiendo el orden de claves definido en la variable keys.
    //Función llamada cuando se crea el objeto mmData, cuando se añade una fila nueva a mmData ó cuando se ordenan los datos de la tabla.
    var updateCells = function(keys) {
        var currentRow = (!patternCfg.Vertical) ? patternCfg.DataStartAtRow : patternCfg.DataStartAtCell;

        var lgt = patternCfg.Cells.length;
        var cellsUpdatedPerRow; //Array que indica el número de celdas que se han actualizado para cada fila de la tabla.

        if (patternCfg.Vertical) {
            cellsUpdatedPerRow = new Array();
            for (var i = 0; i < patternCfg.Cols * patternCfg.RepeatPattern; i++)
                cellsUpdatedPerRow.push(0);

            if (patternCfg.DataStartAtRow > patternCfg.HasTitle) {//Si la cabecera está visible.
                //Incrementa el contador en función de la distribución de las celdas de la cabecera.
                for (var curRh = 0; curRh < patternCfg.RepeatPattern; curRh++)
                    for (var i = 0; i < lgt; i++) {//Para cada celda de la cabecera, obtiene la fila donde se pinta.
                    var posCellHeader = patternCfg.Cells[i].CellVerticalPosition + curRh * patternCfg.Cols;
                    cellsUpdatedPerRow[posCellHeader]++; //Incrementa el contador de esa fila.
                }
            }
        }

        //Actualiza las referencias de los objetos TD.
        var iPatternRow = 0; //Contador de repeticiones horizontales.

        //Verifica si se trata de una tabla Hash
        var hashTable = (keys.Items != null);

        for (var key in keys) {
            var mmDataValue = null;

            if (hashTable)
                mmDataValue = mmData.Get(key);
            else //Se trata de un Array
                mmDataValue = mmData.Get(keys[key].Key);

            //Recorre todas las celdas que corresponden a esa fila de datos.
            var cellIdx = 1;
            var changeRow = false;
            for (var i = 0; i < lgt; i++) {
                var patternCfgCell = patternCfg.Cells[i];

                //Obtiene el índice de fila y de columna.
                var rowIdx = patternCfgCell.Row + currentRow;
                var cellIdx = patternCfgCell.Cell;

                if (patternCfg.Vertical) {

                    //Obtiene el índice de la fila.
                    rowIdx = patternCfgCell.CellVerticalPosition + patternCfg.HasTitle;
                    if (patternCfg.RepeatPattern > 1) //Caso de que los patrones se repitan horizontalmente (atributo rh).
                        rowIdx += iPatternRow * patternCfg.Cols; //patternCfg.Cols se refiere al número de filas del patrón.

                    //Obtiene el indice de la columna.
                    cellIdx = cellsUpdatedPerRow[rowIdx - patternCfg.HasTitle]; //Resta el título, porque el array cellsUpdatedPerRow no guarda información de la celda del título.

                    cellsUpdatedPerRow[rowIdx - patternCfg.HasTitle]++; //Incrementa el contador de celdas actualizadas para esa fila.
                }
                else if (patternCfg.RepeatPattern > 1) //Caso de que los patrones se repitan horizontalmente (atributo rh).
                    cellIdx += iPatternRow * patternCfg.CellsPerRow[patternCfgCell.Row];

                //Verifica si debe actualizar la referencia de la fila (puede no cambiar el contenido pero haber sido desplazada).
                if (changeRow || mmDataValue.Cells[i].Td !== tableObj.rows[rowIdx].cells[cellIdx]) {
                    changeRow = true;
                    mmDataValue.Cells[i].Td = tableObj.rows[rowIdx].cells[cellIdx];
                }
            }

            iPatternRow++;

            if (iPatternRow == patternCfg.RepeatPattern) {
                iPatternRow = 0;
                currentRow += patternCfg.Rows;
            }

            if (changeRow)
                updateRow(mmDataValue, true);

        }
        cellsUpdatedPerRow = null;
    }

    //Método que resalta las celdas que han cambiado y almacena sus estilos antiguos. Donde:
    // - mmDataValue: Fila de datos.
    // - cells: Array que contiene las celdas que hay que resaltar.
    var highLightCells = function(mmDataValue, cells, style) {
        //Verifica si la celda no fue previamente añadida.
        var lgt = cells.length;
        if (oldCellStyles[mmDataValue.Key] != null) {
            //Recorre todas las celdas que debe añadir
            for (var i = 0; i < lgt; i++) {
                var add = true;
                //Recorre las celdas que actualmente tiene el objeto oldCellStyles para ese registro.
                var oldCellStylesValue = oldCellStyles[mmDataValue.Key];
                for (var p in oldCellStylesValue.Cells)
                    if (cells[i] == oldCellStylesValue.Cells[p])
                    add = false;

                if (add)
                    oldCellStyles[mmDataValue.Key].Cells.push(cells[i]);
            }
        }
        else
            oldCellStyles[mmDataValue.Key] = { "Key": mmDataValue.Key, "Cells": cells };

        //Resalta las celdas
        for (var i = 0; i < lgt; i++) {
            var column = mmDataValue.Cells[cells[i]].Td;
            ApplyStyleOverride(column, style);
        }
    }


    //Método encargado de actualizar una fila de la tabla. Donde:
    // - mmDataValue: Fila de datos que hay que actualizar
    var updateRow = function(mmDataValue, forceUpdate) {
        //Array que va a contener las celdas que han cambiado.
        var cells = new Array();
        var lgt = mmDataValue.Cells.length;
        for (var i = 0; i < lgt; i++) {
            var curCell = mmDataValue.Cells[i];

            //Añade la celda para que sea resaltada
            if (highlightStyle != null && ((curCell.Changed && highlightStyle.Cell == -1) || highlightStyle.Cell == -3))
                cells.push(i);

            //Si la celda ha cambiado se actualiza su innerHTML
            if (forceUpdate || curCell.Changed) {
                //Restaura el valor de Changed            
                curCell.Changed = false;

                var cellCfg = patternCfg.Cells[i];

                //Obtiene la celda de la tabla HTML
                curCell.Td.innerHTML = curCell.InnerHtml;
                if (mmDataValue.Selected) {
                    updateCellStyle(curCell.Td, cellCfg, curCell);
                    //Combina los estilos de la columna.
                    ApplyStyleOverride(curCell.Td, selectedStyle.Style);
                    if (selectedStyle.Css != "")
                        YAHOO.util.Dom.addClass(curCell.Td, selectedStyle.Css);
                }
                else
                    updateCellStyle(curCell.Td, cellCfg, curCell);
            }
        }

        //Evalúa si aplicará un estilo Neutral, Up o Down.
        var style = highlightStyle.Neutral;
        if (patternCfg.Cells[highlightStyle.CellToEval].DataType != "String") {
            var cellToEval = 0;
            //cellToEval será igual a la diferencia entre el valor actual y el anterior valor de cellToEval (en caso de que exista).
            if (mmDataValue.LastCellToEval != null) {
                cellToEval = mmDataValue.Cells[highlightStyle.CellToEval].Value - mmDataValue.LastCellToEval;
                mmDataValue.LastCellToEval = null;
            }

            if (cellToEval > 0)
                style = highlightStyle.Up;
            else if (cellToEval < 0)
                style = highlightStyle.Down;
        }

        //Resalta las celdas que han cambiado
        if (cells.length > 0 && highlightStyle != null) {
            if (highlightStyle.Cell == -1 || highlightStyle.Cell == -3)
                highLightCells(mmDataValue, cells, style);
            else if (highlightStyle.Cell == -2)
                highLightCells(mmDataValue, [patternCfg.Cells.length - 1], style);
            else
                highLightCells(mmDataValue, [highlightStyle.Cell], style);
        }

        cells = null;
    }

    //Método encargado de actualizar las filas de la tabla.
    var updateRows = function() {
        for (var key in mmData.Items)
            updateRow(mmData.Get(key), false);
    }

    //Actualiza el estilo de una celda según el estilo definido en el objeto MMData. Luego aplica el estilo que le corresponde a la columna (si es que esta estuviese ordenada).
    var updateCellStyle = function(td, cellCfg, mmDataValueCell) {
        //Aplica el estilo que corresponde a la celda
        ApplyStyle(td, mmDataValueCell.Style);
        ApplyCss(td, mmDataValueCell.Css);

        //Verifica si la columna a la que pertenece está ordenada
        if (sortStyle != null && cellCfg.CurrentOrder != null) {
            if (cellCfg.CurrentOrder == "ASC") {
                if (sortStyle.ColumnAsc.Style != "")
                    ApplyStyleOverride(td, sortStyle.ColumnAsc.Style);
                if (sortStyle.ColumnAsc.Css != "")
                    ApplyCss(td, sortStyle.ColumnAsc.Css);
            }
            else {
                if (sortStyle.ColumnDesc.Style != "")
                    ApplyStyleOverride(td, sortStyle.ColumnDesc.Style);
                if (sortStyle.ColumnDesc.Css != "")
                    ApplyCss(td, sortStyle.ColumnDesc.Css);
            }
        }
    }

    //Actualiza el estilo de todas las filas de un patrón.
    var updateCellsPatternStyle = function(mmDataValue, select) {

        var lgt = mmDataValue.Cells.length;
        for (var i = 0; i < lgt; i++) {

            var td = mmDataValue.Cells[i].Td;
            var style = null;
            var css = null;

            if (!select) {
                style = mmDataValue.Cells[i].Style;
                css = mmDataValue.Cells[i].Css;
                ApplyStyle(td, style);
                ApplyCss(td, css);

                if (patternCfg.Cells[i].CurrentOrder == "ASC") {
                    if (sortStyle.ColumnAsc.Style != "")
                        ApplyStyleOverride(td, sortStyle.ColumnAsc.Style);
                    if (sortStyle.ColumnAsc.Css != "")
                        ApplyStyleOverride(td, sortStyle.ColumnAsc.Style);
                }
                if (patternCfg.Cells[i].CurrentOrder == "DESC") {
                    if (sortStyle.ColumnDesc.Style != "")
                        ApplyStyleOverride(td, sortStyle.ColumnDesc.Style);
                    if (sortStyle.ColumnDesc.Css != "")
                        ApplyStyleOverride(td, sortStyle.ColumnDesc.Style);
                }
            }
            else {
                style = selectedStyle.Style;
                css = selectedStyle.Css;
                //Combina los estilos de la columna.
                ApplyStyleOverride(td, style);
                YAHOO.util.Dom.addClass(td, css);
            }
        }
    }

    //Métodos públicos
    this.GetMMData = function() {
        return mmData;
    }

    //A partir de un objeto TD, devuelve el conjunto de datos que está mostrando.
    this.GetValues = function(td) {
        var tr = td.parentNode;
        for (var key in mmData.Items) {
            mmDataValue = mmData.Get(key);

            if (mmDataValue.Cells.length == 0)
                continue;

            var row = mmDataValue.Cells[0].Td.parentNode;
            if (row === tr)
                return mmDataValue;
        }

        return null;
    }

    this.GetPatternCfg = function() {
        return patternCfg;
    }

    this.SetHighlightStyle = function(aHighlightStyle) {
        //TODO: Añadir validación (que CellToEval sea una celda válida).
        highlightStyle = aHighlightStyle;
    }
    this.SetPatternCfg = function(aPatternCfg) {
        patternCfg = aPatternCfg;
    }
    this.SetSelectedStyle = function(aSelectedStyle) {
        selectedStyle = aSelectedStyle;
    }
    this.SetSortStyle = function(aSortStyle) {
        sortStyle = aSortStyle;
    }

    //Método encargado de restaurar los estilos antiguos de los valores que han cambiado.
    this.RestoreStyle = function() {
        if (oldCellStyles == null)
            return;

        for (var key in oldCellStyles) {
            var value = oldCellStyles[key];
            var lgt = value.Cells.length;
            for (var i = 0; i < lgt; i++) {
                var cell = value.Cells[i];
                var cellCfg = patternCfg.Cells[cell];
                var mmDataValue = mmData.Get(key);
                var td = mmDataValue.Cells[cell].Td;

                if (mmDataValue.Selected == true) {
                    updateCellStyle(td, cellCfg, mmDataValue.Cells[cell]);

                    //Combina los estilos de la columna.
                    ApplyStyleOverride(td, selectedStyle.Style);
                    if (selectedStyle.Css != "")
                        YAHOO.util.Dom.removeClass(td, selectedStyle.Css);
                }
                else
                    updateCellStyle(td, cellCfg, mmDataValue.Cells[cell]);

                td = null;
            }

            delete oldCellStyles[key];
        }
    }

    this.UpdateTable = function(aData) {
        //Indica la razón por la que deberían actualizarse todas las referencias de las celdas. Posibles valores: -1: No es necesario actualizar, 0: Primera vez que se carga la tabla, 1: Se han añadido y eliminado keys.
        var updateCellsRef = -1;

        //Si es la primera vez que se invoca
        if (mmData == null) {
            //Si la colección de datos proporcionada es inválida, no actualiza datos.
            if (aData == null) {
                if (!MM.AppInfo.ReleaseMode) //Solo en modo depuración lanza un error
                    ThrowJSError("You must provide a valid dataCollection object.");
                else
                    return;
            }

            mmData = aData;
            updateCellsRef = 0;
        }

        //Si la colección de datos a actualizar es nula o está vacía, no hace nada.
        if (aData == null || aData.Length() == 0)
            return;

        //Verifica que la cantidad de filas de ambos conjuntos de datos sean iguales. Si la colección de datos proporcionada es inválida, no actualiza datos.
        if (mmData != null && mmData.Length() != aData.Length()) {
            if (!MM.AppInfo.ReleaseMode) //Solo en modo depuración lanza un error
                ThrowJSError("The dataCollection object has an invalid number of records. MMData: " + mmDataLength.toString() + " - aData: " + aDataLength.toString());
            else
                return;
        }

        //Comprueba si la cabecera de la tabla está visible para actualizarla.
        if (patternCfg.DataStartAtRow > patternCfg.HasTitle)
            this.updateTableHeader();

        if (aData != mmData) {//Cuando mmData se actualiza por primera vez, aData es igual a mmData.
            //Remueve las keys de mmData que no están en aData.
            for (var key in mmData.Items) {
                if (!aData.HasItem(key)) {
                    mmData.Remove(key);
                    //var oldCellStylesKey = oldCellStyles[key];
                    if (oldCellStyles[key])
                        delete oldCellStyles[key];
                    updateCellsRef = 1;
                }
            }

            //Recorre las keys de aData.
            var lgtPattern = patternCfg.Cells.length;
            for (var key in aData.Items) {

                if (mmData.HasItem(key)) {//Si la key está en mmData, actualiza los valores si han cambiado.
                    var mmDataValue = mmData.Get(key);
                    for (var i = 0; i < lgtPattern; i++) {
                        if (aData == null || (aData.HasItem(key) && aData.Get(key).Cells[i].Value != mmDataValue.Cells[i].Value)) {

                            if (aData != null) {
                                //Almacena el valor anterior de la columna a sustituir en la propiedad LastCellToEval. (Utilizado para calcular si el valor está subiendo o bajando).
                                if (highlightStyle != null && highlightStyle.CellToEval == i)
                                    mmDataValue.LastCellToEval = mmDataValue.Cells[i].Value;

                                var tdDataValue = mmDataValue.Cells[i].Td;
                                mmDataValue.Cells[i] = aData.Get(key).Cells[i];
                                aData.Get(key).Cells[i].Td = tdDataValue;
                            }

                            mmDataValue.Cells[i].Changed = true;
                        }
                    }
                    mmDataValue = null;
                }
                else {//Si no está la key, la añade a mmData.
                    updateCellsRef = 1;
                    mmDataNewValue = aData.Get(key);
                    for (var i = 0; i < lgtPattern; i++)
                        mmDataNewValue.Cells[i].Changed = true;
                    mmData.Set(key, mmDataNewValue);
                }
            }
        }

        //Actualiza las referencias de la tabla.
        if (updateCellsRef == 0 || (updateCellsRef == 1 && !patternCfg.CurrentOrder)) //Primera vez que se carga la tabla o al añadir/eliminar registros sin ordenación. Asume que viene ordenado del servidor.
            updateCells(aData.Items);
        else if (updateCellsRef == 1 && patternCfg.CurrentOrder) //Cuando se añaden o eliminan KEYS y la tabla tiene ordenación.
            this.Sort(null);
        else //Simplemente actualiza los valores de las filas/celdas que han cambiado.
            updateRows();

        //if (highLightChanges && highlightStyle != null) { //TODO: ¿Qué hacer una vez eliminado highLightChanges?
        if (highlightStyle != null) {
            if (highlightStyle.FadeIn < 400)
                highlightStyle.FadeIn = 400;

            setTimeout("MM.Ajax.MMTable.RestoreStyle('" + tableId + "')", highlightStyle.FadeIn);
        }
    }

    //Actualiza el encabezado de la tabla.
    this.updateTableHeader = function() {
        var lgt = patternCfg.Cells.length;
        for (var i = 0; i < lgt; i++) {
            var cellCfg = patternCfg.Cells[i];
            var td;
            var idxRow;
            var idxCol;

            //Obtiene la celda del encabezado de la tabla html.
            if (!patternCfg.Vertical) {
                idxRow = patternCfg.HasTitle + cellCfg.Row;
                idxCol = cellCfg.Cell;
            }
            else {
                idxRow = patternCfg.HasTitle + cellCfg.CellVerticalPosition;
                idxCol = cellCfg.Row;
            }

            td = tableObj.rows[idxRow].cells[idxCol];

            //Restaura los estilos originales de las columnas por las que se ordenó anteriormente.
            if (cellCfg.OldStyle != null || cellCfg.OldCss != null || cellCfg.OldInnerHtml != null) {
                ApplyStyle(td, cellCfg.OldStyle);
                ApplyCss(td, cellCfg.OldCss);

                td.innerHTML = cellCfg.OldInnerHtml;

                cellCfg.OldStyle = null;
                cellCfg.OldCss = null;
                cellCfg.OldInnerHtml = null;
            }

            //Aplica el estilo al encabezado de la columna por la que se está ordenando.
            if (sortStyle != null && cellCfg.CurrentOrder != null) {
                //Almacena el estilo anterior de la celda.
                cellCfg.OldStyle = td.style.cssText;
                cellCfg.OldCss = td.className;
                cellCfg.OldInnerHtml = td.innerHTML;

                if (cellCfg.CurrentOrder == "ASC") {
                    if (sortStyle.HeaderAsc.Style != "")
                        ApplyStyleOverride(td, sortStyle.HeaderAsc.Style);
                    if (sortStyle.HeaderAsc.Css != "")
                        ApplyCss(td, sortStyle.HeaderAsc.Css);
                    if (sortStyle.HeaderAsc.InnerHtml != null)
                        td.innerHTML = sortStyle.HeaderAsc.InnerHtml[i];
                }
                else {
                    if (sortStyle.HeaderDesc.Style != "")
                        ApplyStyleOverride(td, sortStyle.HeaderDesc.Style);
                    if (sortStyle.HeaderDesc.Css != "")
                        ApplyCss(td, sortStyle.HeaderDesc.Css);
                    if (sortStyle.HeaderDesc.InnerHtml != null)
                        td.innerHTML = sortStyle.HeaderDesc.InnerHtml[i];
                }
            }

            td = null;
        }
    }

    //Método que ordena los datos.
    this.Sort = function(column) {
        var maintainOrder = false;
        //La columna viene a NULL cuando es llamada desde UpdateCells al insertar una nueva fila en la tabla que tiene ordenación.
        if (!column) {
            column = patternCfg.CurrentOrder.Cell;
            maintainOrder = true; //Se debe mantener el orden de la columna, no se invierte la dirección.
        }

        //Copia en un array el key de cada fila y el valor de la celda que se debe ordenar
        var dataArray = new Array();
        for (var key in mmData.Items) {
            var mmDataValue = mmData.Get(key);
            dataArray.push({ "Key": mmDataValue.Key, "CellValue": mmDataValue.Cells[column].Value });
        }

        //Ordena en función del tipo de dato
        switch (patternCfg.Cells[column].DataType) {
            case "Float":
                dataArray.sort(function(a, b) {
                    if (isNaN(a.CellValue))
                        return -1;
                    if (isNaN(b.CellValue))
                        return 1;

                    if (parseFloat(a.CellValue) < parseFloat(b.CellValue)) return -1;
                    if (parseFloat(a.CellValue) > parseFloat(b.CellValue)) return 1;
                    return 0;
                });
                break;
            default:
                dataArray.sort(function(a, b) {
                    if (a.CellValue < b.CellValue) return -1;
                    if (a.CellValue > b.CellValue) return 1;
                    return 0;
                });
        }

        var newCurrentOrder = "ASC";
        if ((patternCfg.CurrentOrder.Order == "ASC" && !maintainOrder) || (patternCfg.CurrentOrder.Order == "DESC" && maintainOrder)) {

            dataArray = dataArray.reverse();
            newCurrentOrder = "DESC";
        }
        patternCfg.CurrentOrder.Order = newCurrentOrder;
        patternCfg.CurrentOrder.Cell = column;

        updateCells(dataArray, true);
    }

    //Selecciona una fila de una tabla.
    //Key que identifica la fila como un registro único.
    this.SelectRow = function(key) {
        var mmDataValue = mmData.Get(key);
        if (mmDataValue == null || selectedStyle == null)
            return;

        //Actualiza los estilos de la fila en función de si está seleccionada o no.
        if (selectedStyle.MultipleSelection || !mmDataValue.Selected) { //Hace esta comprobación para que cuando multipleSelection sea false, no deseleccione las filas seleccionadas.
            mmDataValue.Selected = (mmDataValue.Selected != null && mmDataValue.Selected) ? false : true;
            updateCellsPatternStyle(mmDataValue, mmDataValue.Selected); //Actualiza del estilo de la fila.
        }

        if (!selectedStyle.MultipleSelection)
            for (_key in mmData.Items) {
            var _mmDataValue = mmData.Get(_key);
            if (_key != key && _mmDataValue.Selected) {
                _mmDataValue.Selected = false;
                updateCellsPatternStyle(_mmDataValue, false); //Actualiza el estilo de la fila.
            }
        }
    }

    //------------------- Funciones añadidas para dar soporte a las secciones contextuales -------------------
    //Evalúa la expresión (pueden ser valores como '-3', '-2', '-1', 2, '0;1-5;7') y devuelve un array con los índices de los elementos a los que corresponde.
    var evalRange = function(expression, lgt) {
        var objRange = new Array();

        if (expression == -3)
            for (var i = 0; i < lgt; i++)
            objRange.push(i);
        else if (expression == -2)
            objRange.push(lgt - 1);
        else {
            var aExpressions = expression.split(";");
            var lgt = aExpressions.length;
            for (var i = 0; i < lgt; i++) {
                var v = aExpressions[i];
                var idx = v.indexOf("-");
                if (idx != -1)
                    for (var j = v.substring(0, idx); j <= v.substring(idx + 1); j++)
                    objRange.push(parseInt(j));
                else
                    objRange.push(parseInt(v));
            }
        }

        return objRange;
    };

    var contextSections = new Array(); //Nombre de la sección contextual a la que está vinculada la tabla.

    //Vincula la tabla a una sección contextual.
    //eventDetails array con la forma: [{"Rows:" rows, "Cells": cells, "Event": event}]
    this.AddContextSection = function(contextSectionId, eventDetails) {
        contextSections.push(contextSectionId);

        //Prepara el objeto que se pasará al control ContextSectionsManager.
        var linkToObjects = new Array(); //Array con la forma [{ "Event": event, "Objects": aObjects}]
        var linkDetails = { "Id": tableId, "DataSource": this, "Objects": linkToObjects }

        var ldLgth = eventDetails.length;
        for (var x = 0; x < ldLgth; x++) {
            var eventDetail = eventDetails[x];
            var rows = eventDetail.Rows;
            var cells = eventDetail.Cells;
            var event = eventDetail.Event;

            //Crea los objetos que contienen el rango de celdas sobre los que se aplica la sección contextual.
            var lgtRows = (tableObj.rows.length - patternCfg.DataStartAtRow) * patternCfg.Rows;  //Tiene en cuenta la primera fila de datos y los patrones.
            var objRangeRow = evalRange(rows, lgtRows);
            var objRangeCol = evalRange(cells, patternCfg.Cells.length);

            //Crea e inicializa la colección de objetos que se vincularán a la sección contextual.
            var aObjects = new Array();
            var lgtRangeRow = objRangeRow.length;
            var lgtRangeCol = objRangeCol.length;
            for (var i = 0; i < lgtRangeRow; i++) {
                var r = patternCfg.DataStartAtRow + objRangeRow[i];
                for (var j = 0; j < lgtRangeCol; j++) {

                    var c = patternCfg.Cells[objRangeCol[j]].Cell;
                    r += patternCfg.Cells[c].Row;

                    var td = tableObj.rows[r].cells[c];
                    if (td)
                        aObjects.push(td);

                    td = null;
                }
            }

            linkToObjects.push({ "Event": event, "Objects": aObjects });
        }

        MM.Controls.ContextSectionsManager.Link(contextSectionId, linkDetails);
    };
    //--------------------------------------------------------------------------------------------------------

};

MM.Ajax.MMTable.MMTables = new Array();

MM.Ajax.MMTable.RestoreStyle = function(tableId) {
    var instance = MM.Ajax.MMTable.MMTables[tableId];
    if (instance != null)
        instance.RestoreStyle();
}

//Crea un objeto del tipo MMData a partir de un array de datos aDataArray array con todos los datos para formar el objeto aData.
MM.Ajax.MMTable.GetMMData = function(aDataArray) {
    if (aDataArray == null)
        return null;

    //Objeto aData a actualizar dentro del MM.Ajax.MMTables.
    var aData = new MM.VCWUtils.HashTable();
    var lgt = aDataArray.length;
    for (var j = 0; j < lgt; j++) {
        //Obtiene los datos del array
        var key = aDataArray[j][0];
        var selected = aDataArray[j][1];

        //Obtiene las celdas con datos.
        var cells = new Array();
        var cellsLgt = aDataArray[j][2].length;
        for (var k = 0; k < cellsLgt; k++) {
            var cellsData = aDataArray[j][2][k];
            cells[k] = { "Value": cellsData[0], "InnerHtml": cellsData[1], "Style": cellsData[2], "Css": cellsData[3], "Changed": false, "Td": null };
        }
        //Añade el registro al objeto aData.
        aData.Set(key, { "Key": key, "Selected": selected, "Cells": cells });
    }

    return aData;
}

//Handler que controla la actualización de porciones de la página.
MM.Ajax.Html = {
    //Número de peticiones realizadas al servidor
    reqId: 0,

    AjaxStyle: null,

    //Array del tipo: { "ReqId": x , "Aborted": false, "ContainersId": Array de containerId }
    LockedRequests: new Array(),

    LoadFile: function(nodes, js, currentLockContainer) {
        var lgt = nodes.length;
        for (i = 0; i < lgt; i++) {
            var currentFile = nodes[i];
            var src = currentFile.getAttribute('src');
            var value = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(currentFile);

            if (js)
                MM.Ajax.JSLoader.JSLoad(src, (value != "") ? value : null, currentLockContainer);
            else
                MM.Ajax.JSLoader.CSSLoad(src, (value != "") ? value : null);
        }
    },

    //Aborta las peticiones pendientes de descargar, cuyos contenedores esten dentro de containerId.
    //La búsqueda se realiza de hijo a padre.
    AbortContainers: function(containerId) {
        var lgt = MM.Ajax.Html.LockedRequests.length;
        for (var i = 0; i < lgt; i++) {

            var lockedRequest = MM.Ajax.Html.LockedRequests[i];
            var lgtLockedContainers = lockedRequest.ContainersId.length;
            for (var j = 0; j < lgtLockedContainers && !lockedRequest.Aborted; j++) {

                var currentContainer = $(lockedRequest.ContainersId[j]);
                while (currentContainer != null) {
                    if (currentContainer.getAttribute != undefined && currentContainer.getAttribute("id") == containerId) {
                        lockedRequest.Aborted = true;
                        break;
                    }

                    currentContainer = currentContainer.parentNode;
                }
            }
        }
    },

    //Determina si una petición contiene al menos un contenedor abortado.
    IsAborted: function(reqId, answer) {
        var lgt = MM.Ajax.Html.LockedRequests.length;
        for (var i = 0; i < lgt; i++) {
            var lockedContainer = MM.Ajax.Html.LockedRequests[i];
            if (lockedContainer.ReqId == reqId) {
                if (answer != null)
                    answer.FirstLockedContainer = lockedContainer;

                if (lockedContainer.Aborted)
                    return true;
            }
        }
    },

    //Petición realizada con éxito. Se recibe el xml al que se accede desde o.responseXML
    HandleSuccess: function(o) {
        //Obtiene del documento xml el contenido html con el que se actualizará el contenedor
        var xmlDoc = o.responseXML;
        var reqId = xmlDoc.firstChild.getAttribute('reqId');

        //Si bien, se abortan "Contenedores", cuando una petición tiene un contenedor abortado, se aborta toda la petición.
        //Busca todos los contenedores que forman parte de la misma petición y verifica si están abortados.
        var answer = { "FirstLockedContainer": null };

        //Desbloquea todos los contenedores de la petición que ha sido abortada.
        if (MM.Ajax.Html.IsAborted(reqId, answer)) {
            MM.Ajax.Html.UnlockContainers(reqId);
            return;
        }

        //Verifica si hay una redirección
        if (MM.Ajax.Html.EvalRedirect(xmlDoc))
            return;

        //--- Carga los archivos css externos definidos en el xml.
        var nodeFiles = xmlDoc.getElementsByTagName('files');
        var nodesCss = nodeFiles[0].getElementsByTagName('css');
        MM.Ajax.Html.LoadFile(nodesCss, false);

        var nodesHtmlContent = xmlDoc.getElementsByTagName('htmlContent');
        if (nodesHtmlContent == null || nodesHtmlContent.length == 0)
            ThrowJSError('Invalid request.');
        var nodeHtmlContent = nodesHtmlContent[0];

        if (!nodeHtmlContent)
            ThrowJSError('Invalid request.');

        var isModal = (nodeHtmlContent.getAttribute('modal') == "true");

        //Debe bloquear toda la página con el objeto LockContainer.
        if (isModal)
            MM.Ajax.Html.LockContainer("pageMainDiv", true);

        //Actualiza el HTML de los contenedores.
        var lgt = nodeHtmlContent.childNodes.length;
        for (n = 0; n < lgt; n++) {

            var nodeContainer = nodeHtmlContent.childNodes[n];
            var contentToUpdate = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(nodeContainer);

            //Lee el containerId del xml
            var containerId = nodeContainer.getAttribute('id');
            var container = MM.Ajax.Html.GetContainer(containerId, isModal);

            //Si el contenedor no existe, es porque probablemente ya fue removido.
            if (!container)
                continue;

            if (!isModal) {
                //Remueve todos los hijos de container
                var containerChildren = container.childNodes.length;
                for (var cc = containerChildren - 1; cc >= 0; cc--)
                    MM.Ajax.JSDOM_Manager.RemoveDOMElement(container.childNodes[cc]);
            }

            container.innerHTML = contentToUpdate;

            if (isModal) {
                //Situa el container en el centro de la pantalla añadiendole el padding superior necesario.
                var dim = GetObjectDimensions(container)[0] / 2;

                //Si el tamaño de la pantalla es menor que el de la página que se va a cargar pone un padding fijo. Sino asigna el padding necesario para que quede centrada en la pantalla.
                //TODO: REVISAR PADDING FIJO (posibilidad de definirlo en el skin o en el css.)
                var heightScreen = YAHOO.util.Dom.getDocumentScrollTop() + (document.documentElement.clientHeight / 2);
                if (heightScreen < dim)
                    container.style.paddingTop = "10px";
                else
                    container.style.paddingTop = (heightScreen - dim).toString() + "px";
            }
        }

        //--- Carga los archivos JS definidos en el xml.
        var nodesJS = nodeFiles[0].getElementsByTagName('script');

        //Crea 2 arrays con los scripts a ejecutar
        var jsLoad = new Array(); //javascripts que se asocian al primer contenedor de la petición.
        var jsLoadComplete = new Array(); //javascript que se cargarán al final.
        for (var i = 0; i < nodesJS.length; i++) {
            var js = nodesJS[i];
            if (js.getAttribute('olc') == "true")
                jsLoadComplete.push(js);
            else
                jsLoad.push(js);
        }

        //Asocia los SCRIPTS (no marcados como onLoadComplete) al primer contenedor de la petición.
        MM.Ajax.Html.LoadFile(jsLoad, true, answer.FirstLockedContainer);

        //Añade un script que desbloqueará los contenedores de la petición.
        var tempFile = "tmp_" + MM.VCWUtils.GetUniqueValue();
        //Ejecuta PostCallEvent (en caso de que exista)
        var script = "MM.Ajax.Html.PostCallEvent(" + reqId.toString() + ");";
        //Desbloquea contenedores
        script += "MM.Ajax.Html.UnlockContainers(" + reqId.toString() + ");";
        //Invoca el objeto MM.AdvManager
        var lockedRequest = MM.Ajax.Html.GetLockedRequest(reqId);
        if (lockedRequest != null)
            script += "MM.AdvManager.Refresh(\"" + lockedRequest.RequestDetails.Url + "\");";
        script += "MM.Ajax.JSLoader.JSLoaded('" + tempFile + "');";
        MM.Ajax.JSLoader.JSLoad(tempFile, script, answer.FirstLockedContainer);

        //Carga los javascript marcados como onLoadComplete
        MM.Ajax.Html.LoadFile(jsLoadComplete, true, null);

        MM.Ajax.JSLoader.JSFlush();
    },

    //Obtiene un contenedor. Si la petición es modal, obtiene el contenedor genérico donde se pinta el contenido de páginas modal.
    GetContainer: function(containerId, isModal) {
        if (isModal) {
            //Verifica si ya existe el contenedor genérico de páginas modal.
            var mainDiv = $('divModalContainer');
            if (!mainDiv) {
                //Si no existe, lo crea
                mainDiv = document.createElement("div");
                mainDiv.setAttribute("id", "divModalContainer");

                $("divModalLockContainer").appendChild(mainDiv);
            }

            return mainDiv;
        }

        var container = null;
        if (containerId != null && containerId != "")
            container = $(containerId);

        return container;
    },

    PostCallEvent: function(reqId) {
        var callback;
        var lgt = MM.Ajax.Html.LockedRequests.length;
        //Busca la funcion a ejecutar en LockedRequests
        var requestDetails;
        for (var i = 0; i < lgt; i++) {
            if (MM.Ajax.Html.LockedRequests[i].ReqId == reqId) {
                callback = MM.Ajax.Html.LockedRequests[i].Callback;
                requestDetails = MM.Ajax.Html.LockedRequests[i].RequestDetails;
                break;
            }
        }

        if (callback) {
            var postCallEvent = callback.PostCallEvent;
            var postCallParams = callback.PostCallParams;

            if (postCallEvent)
                postCallEvent(requestDetails, postCallParams);
        }
    },

    //Si se ha producido un error al realizar la conexión.
    HandleFailure: function(o) {
        var failure = MM.Ajax.Html.DecodeFailure(o);

        if (failure == null)
            return;

        //Identifica la petición.
        var lgtRequests = MM.Ajax.Html.LockedRequests.length;
        var lockedRequest = null;
        for (var i = 0; i < lgtRequests; i++) {
            lockedRequest = MM.Ajax.Html.LockedRequests[i];
            if (lockedRequest.ReqId == failure.ReqId)
                break;
        }

        //Ejecuta PostCallEvent (en caso de que exista)
        if (MM.Ajax.Html.PostCallEvent)
            MM.Ajax.Html.PostCallEvent(failure.ReqId.toString());

        //Desbloquea las secciones que dejaban inhabilitadas los contenedores a actualizar por ajax.
        MM.Ajax.Html.UnlockContainers(failure.ReqId);

        //Realiza la llamada a la función que muestra el contenedor de error (solo si la petición no ha sido abortada).
        if (!MM.Ajax.Html.IsAborted(failure.ReqId)) {
            if (failure.ErrorContainerId != "Base")
                MM.VCWUtils.ErrorHandler.Invoke(failure.ErrorHandler, failure.ErrorContainerId, failure.ErrorCode, failure.ErrorMessage, failure.StackTrace, failure.FriendlyErrorMessage);
            else {
                //Obtiene el contenedor para el handler definido. Si no se ha definido uno, tomará el contenedor base.
                var errorContainer = MM.VCWUtils.ErrorHandler.GetErrorContainer(failure.ErrorContainerId);
                if (errorContainer != null && lockedRequest != null) {
                    var lgtContainers = lockedRequest.ContainersId.length;
                    //Recorre los contenedores que corresponden a la petición. Añade a cada contenedor una copia del contenedor de error.
                    for (var i = 0; i < lgtContainers; i++) {
                        var container = $(lockedRequest.ContainersId[i]);
                        var id = "errorContainer_" + container.getAttribute('id');

                        //Verifica si no existe ese contenedor de error.
                        if (MM.VCWUtils.ErrorHandler.ErrorContainers[id] == null) {
                            //Si no existe, lo crea.
                            var divError = document.createElement("div");
                            divError.setAttribute("id", id);
                            ApplyStyle(divError, GetStyle(errorContainer));
                            container.insertBefore(divError, container.firstChild);

                            //Asocia el DIV a un destructor (para que al ser removido lo quite del objeto ErrorHandler).
                            var destroyer = function(id) { this.destroy = function() { MM.VCWUtils.ErrorHandler.RemoveErrorContainer(id, id, id); } };
                            MM.Ajax.JSDOM_Manager.AddRelation(divError, [new destroyer(id)]);

                            MM.VCWUtils.ErrorHandler.AddErrorContainer(id, id, id);
                            MM.VCWUtils.ErrorHandler.Invoke(failure.ErrorHandler, id, failure.ErrorCode, failure.ErrorMessage, failure.StackTrace, failure.FriendlyErrorMessage);
                        }
                    }
                }
            }
        }
    },

    //Decodifica el xml que contiene un fallo y devuelve un objeto con información sobre el error.
    DecodeFailure: function(o) {
        var errorCode = 1000;
        var errorMessage = "Invalid service.";
        var stackTrace = null;
        var errorHandler = "Base";
        var friendlyErrorMessage = null;
        var reqId = -1;
        var errorContainerId;

        //Verifica que se trate de un xml.
        if (o.getResponseHeader != undefined) {
            var contentType = o.getResponseHeader["Content-Type"];
            if (contentType != undefined && contentType.toLowerCase().indexOf("text/xml") != -1) {
                var xmlDoc = o.responseXML;

                //Verifica si hay una redirección
                if (MM.Ajax.Html.EvalRedirect(xmlDoc))
                    return null;

                var nodeError = xmlDoc.getElementsByTagName('errorMessage');
                var errorMessage = nodeError[0].firstChild.nodeValue;

                errorCode = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(xmlDoc, 'errorCode');
                errorMessage = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(xmlDoc, 'errorMessage');
                stackTrace = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(xmlDoc, 'stackTrace');
                errorHandler = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(xmlDoc, 'errorHandler');
                errorContainerId = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(xmlDoc, 'errorContainerId');
                friendlyErrorMessage = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(xmlDoc, 'friendlyErrorMessage');
                reqId = xmlDoc.firstChild.getAttribute('reqId');
            }
        }

        //Si errorContainerId está vacío envía el valor de ErrorHandler.
        if (errorContainerId == null || errorContainerId == "")
            errorContainerId = errorHandler;

        return { "ErrorHandler": errorHandler, "ErrorContainerId": errorContainerId, "ErrorCode": errorCode, "ErrorMessage": errorMessage, "StackTrace": stackTrace, "FriendlyErrorMessage": friendlyErrorMessage, "ReqId": reqId };
    },

    //Evalúa si el xml recibido indica que debe realizarse una redirección. Si se realiza una redirección devuelve true.
    EvalRedirect: function(xmlDoc) {
        var redirectTo = xmlDoc.getElementsByTagName("redirectTo");
        if (redirectTo != null && redirectTo.length > 0) {
            var redirectToNode = redirectTo[0];
            var url = MM.VCWUtils.ParseUtils.GetXmlNodeInnerXml(redirectToNode);
            var onBefore = redirectToNode.getAttribute("onBefore");
            if (onBefore != null && onBefore != "")
                eval(onBefore);

            //Verifica si debe enviar parámetros por POST
            var postParams = redirectToNode.getAttribute("postParams");
            if (postParams != null && postParams != "") {
                //Crea un div oculto
                var div = document.createElement("div");
                div.style.display = "none";

                //Prepara los elementos que deben enviarse por POST.
                var parameters = postParams.split('&');
                var elements = new Array();
                var lgt = parameters.length;
                for (var i = 0; i < lgt; i++) {
                    var param = MM.VCWUtils.Url.GetParam(parameters[i]);
                    param.Type = "hidden";
                    param.Value = decodeURIComponent(param.Value); //Desencodea los parámetros.
                    elements.push(param);
                }

                //Crea un formulario y realiza un submit
                var form = MM.VCWUtils.FormsManager.CreateForm(document, "", "POST", url, elements);

                //Añade el formulario a div
                div.appendChild(form);

                //Añade el div a DivExtended
                $("DivExtended").appendChild(div);

                //Envía el formulario
                form.submit();

                return true;
            }
            else {
                document.location.href = url;

                return true;
            }
        }

        return false;
    },

    //Evalúa si el parámetro actual debe ser considerado como parte de los parámetros de la petición.
    EvalParam: function(paramObj) {
        paramObj.Name = paramObj.Name.toLowerCase();
        if (paramObj.Name == "reqid" || paramObj.Name == "reqtype" || paramObj.Name == "u" || paramObj.Name == "sendermodal")
            return false;

        if (paramObj.Name == "containerid")
            paramObj.Containers = unescape(paramObj.Value);

        return true;
    },

    //CallBack genérico, utilizado para todas las peticiones que no definan uno propio.
    GenericCallBack: null,

    // Función que realiza la petición ajax en base a los parámetros recibidos
    //     url : url que se desea cargar. No tiene aún el parámetro containerId ni sectionId
    //     postParams: Array con los parámetros que se enviarán por post (sin encodear).
    //     callback: objeto que contendrá¡:
    //           PreCallEvent : Función que se disparará antes de enviar la petición.
    //           PreCallParams : Array de parámetros
    //           PostCallEvent : Función que se disparará cuando venga la respuesta (exitosa o fallida).
    //           PostCallParams : Array de parámetros
    //           La estructura de ambas funciones será:
    //                 RequestDetails : Contiene información sobre la petición.
    //                 Params : Array de parámetros.
    Request: function(url, postParams, callback) {
        //Si en la petición no se envía un callback, utiliza el genérico.
        if (typeof (callback) == "undefined")
            callback = this.GenericCallBack;

        var containers = null;

        //Prepara la URL
        var urlSplit = url.split("?");
        url = urlSplit[0] + "?";
        if (urlSplit.length == 1)
            return;

        var urlParams = urlSplit[1].split("&");
        var currentParam;
        var reqType = 0;
        for (var i = 0; i < urlParams.length; i++) {
            var paramObj = MM.VCWUtils.Url.GetParam(urlParams[i]);
            paramObj.Containers = null;
            if (!MM.Ajax.Html.EvalParam(paramObj)) {
                //Verifica si la petición corresponde a una página de error.
                if (paramObj.Name == "reqtype" && paramObj.Value == "10")
                    reqType = 10;

                //Si la página desde donde se realiza la petición es modal, se debe cerrar.
                if (paramObj.Name == "sendermodal" && paramObj.Value == "true")
                    MM.Ajax.Html.CloseModal();

                continue;
            }

            if (paramObj.Containers != null) {
                //Más adelante se añade este parámetro a la url.
                containers = paramObj.Containers;
                continue;
            }

            url += paramObj.Name + "=" + paramObj.Value + "&";
        }

        //Evalúa los parámetros recibidos por POST.
        var postParamsString = "";
        if (postParams != null) {
            var lgt = postParams.length;
            for (var i = 0; i < lgt; i++) {
                var paramObj = postParams[i];
                if (!MM.Ajax.Html.EvalParam(paramObj))
                    continue;

                //Si se definió containers en un parámetro enviado por POST.
                if (paramObj.Containers != null) {
                    if (containers == null) {
                        //Más adelante se añade este parámetro a la url.
                        containers = paramObj.Containers;
                        continue;
                    }
                    else
                    //Si se definió containers en la URL, preserva ese valor
                        continue;
                }
                postParamsString += paramObj.Name + "=" + encodeURIComponent(paramObj.Value) + "&";
            }

            if (lgt > 0)
                postParamsString = postParamsString.substring(0, postParamsString.length - 1);
        }

        if (containers == null || containers == "" || containers == "REDIRECT")
            containers = 'pageMainDiv';

        var reqId = MM.Ajax.Html.reqId;
        MM.Ajax.Html.reqId++;

        //Bloquea los contenedores que se actualizarán
        var aContainers = containers.split(";");
        var containers = "";
        var nodeSets = "";
        lgt = aContainers.length;
        var aLockedContainersId = new Array();
        for (var i = 0; i < lgt; i++) {
            var details = MM.Ajax.EvalContainerId(aContainers[i]);

            //Si es un TAB lo activa
            if (details.TabViewName != null)
                MM.Controls.TabsView.SetActiveTab(details.TabViewName, details.TabIndex, true, true);

            var containerId = details.ContainerId;

            //Llama al método que verifica si el contenedor a bloquear reemplaza a contenedores que están esperando respuestas anteriores.
            //En ese caso aborta sus respectivas peticiones (solo si la página actual no es modal).
            if (containerId != 'pageMainDiv')
                MM.Ajax.Html.AbortContainers(containerId);

            containers += containerId + ";";

            //Obtiene los prefijos que debe pasar al servidor
            var nodeSet = MM.Ajax.NodeSetsManager.GetNodeSet(containerId);
            if (nodeSet != "")
                nodeSets += nodeSet + ";";

            aLockedContainersId.push(containerId);
            MM.Ajax.Html.LockContainer(containerId, false);
        }

        var lockedRequest = { "ReqId": reqId, "Aborted": false, "ContainersId": aLockedContainersId, "Callback": callback };
        MM.Ajax.Html.LockedRequests.push(lockedRequest);

        if (containers.length > 0)
            containers = containers.substring(0, containers.length - 1);

        if (nodeSets.length > 0)
            nodeSets = nodeSets.substring(0, nodeSets.length - 1);

        if (url.substring(url.length - 1, url.length) != "&")
            url += "&";

        url += "ContainerId=" + containers + "&nodeSets=" + nodeSets + "&ReqType=" + reqType + "&ReqId=" + reqId.toString() + "&u=" + MM.VCWUtils.GetUniqueValue();
        lockedRequest.RequestDetails = { "Url": url };

        if (callback) {
            //Ejecuta PreCallEvent (en caso de que exista)
            if (callback.PreCallEvent) {
                callback.PreCallEvent(lockedRequest.RequestDetails, callback.PreCallParams);
                //Actualiza la url por si la función la cambió.
                url = lockedRequest.RequestDetails.Url;
            }
        }

        //Si el protocolo actual es HTTPS y se está enviando una petición HTTP lanza un error.
        if (document.location.href.substring(0, 6).toLowerCase() == "https:" && url.substring(0, 5).toLowerCase() == "http:")
            ThrowJSError("Invalid protocol. Change the current protocol to HTTP and try again.");

        //Petición Ajax para recibir el xml. Se decide si es get o post en funcion de si la variable post ha sido o no definida
        if (postParamsString != "")
            YAHOO.util.Connect.asyncRequest('POST', url, { success: MM.Ajax.Html.HandleSuccess, failure: MM.Ajax.Html.HandleFailure }, postParamsString);
        else
            YAHOO.util.Connect.asyncRequest('GET', url, { success: MM.Ajax.Html.HandleSuccess, failure: MM.Ajax.Html.HandleFailure });
    },

    CloseModal: function() {
        var divModalLockContainer = $("divModalLockContainer");
        if (divModalLockContainer) {
            MM.Ajax.JSDOM_Manager.RemoveDOMElement(divModalLockContainer);
            MM.Ajax.Data.Start();

            //Vuelve a mostrar los applets de la página.
            if (MM.Tools != null && MM.Tools.JSAppletsManager != null)
                MM.Tools.JSAppletsManager.ShowApplets(null, "MODAL_PAGE");
        }
    },

    //Obtiene el objeto LockedRequest que pertenece a una petición.
    GetLockedRequest: function(reqId) {
        var lgtRequests = MM.Ajax.Html.LockedRequests.length;
        for (var i = 0; i < lgtRequests; i++) {
            if (MM.Ajax.Html.LockedRequests[i].ReqId == reqId)
                return MM.Ajax.Html.LockedRequests[i];
        }

        return null;
    },

    //Desbloquea visualmente uno o varios contenedor destruyendo los div que los dejaban inhablitados.
    UnlockContainers: function(reqId) {
        var lgtRequests = MM.Ajax.Html.LockedRequests.length;
        for (var i = 0; i < lgtRequests; i++) {
            if (MM.Ajax.Html.LockedRequests[i].ReqId == reqId) {
                var containersId = MM.Ajax.Html.LockedRequests[i].ContainersId;
                MM.Ajax.Html.LockedRequests.splice(i, 1);
                i--;
                lgtRequests--;

                //Destruye el div que dejaba inhabilitado el contenedor a actualizar
                var lgt = containersId.length;
                for (var j = 0; j < lgt; j++) {
                    var containerId = containersId[j];
                    var divLock = $('divLock_' + containerId);
                    if (divLock)
                        MM.Ajax.JSDOM_Manager.RemoveDOMElement(divLock);

                    MM.VCWUtils.LoadingDialog.Instances["LD_Base"].Hide();
                }
            }
        }
    },

    //Bloquea visualmente un contenedor
    LockContainer: function(containerId, isModal) {
        //Si se está intentando bloquear el objeto divModalLockContainer y ya existe, no hace nada.
        if (isModal && $('divModalLockContainer'))
            return;

        if (containerId == null)
            return;

        //Si no se ha definido un objeto AjaxStyle no bloquea los contenedores.
        if (MM.Ajax.Html.AjaxStyle == null)
            return;

        //Obtiene el container donde se insertará el nuevo div
        var container = $(containerId);
        if (!container)
            ThrowJSError("'" + containerId + "' is not a valid container.");

        //Crear un div que almacenará el div que bloquea la sección antigua
        var div = document.createElement("div");
        var mainDiv;

        //Aplica la configuración de colores definidas en MMAjaxStyle.
        if (isModal) {
            div.innerHTML = MM.Ajax.Html.AjaxStyle.ModalLockContainer;
            mainDiv = div.firstChild;
            MM.Ajax.Data.Stop();
        }
        else {
            div.innerHTML = MM.Ajax.Html.AjaxStyle.LockContainer.replace("__ID__", containerId);
            mainDiv = div.firstChild;
            mainDiv.setAttribute("id", "divLock_" + containerId);
        }

        //Obtiene las dimensiones del contenedor y se la aplica al div
        var dimensions = GetObjectDimensions(container);
        //Si la ventana es modal, se asegura que las dimensiones del contenedor a bloquear sea igual o superior a las de la ventana actual.
        if (isModal) {
            var viewportHeight = YAHOO.util.Dom.getViewportHeight();
            var viewportWidth = YAHOO.util.Dom.getViewportWidth();
            if (dimensions[0] < viewportHeight || dimensions[1] < viewportWidth)
                dimensions = [viewportHeight, viewportWidth];
        }
        mainDiv.style.width = dimensions[1] + "px";
        mainDiv.style.height = dimensions[0] + "px";

        //Obtiene la posición del contenedor y se la aplica al div
        var pos = YAHOO.util.Dom.getXY(container);
        mainDiv.style.left = pos[0] + "px";
        mainDiv.style.top = pos[1] + "px";
        mainDiv.style.position = 'absolute';

        if (isModal) {
            //Oculta los applets de la página.
            if (MM.Tools != null && MM.Tools.JSAppletsManager != null)
                MM.Tools.JSAppletsManager.HideApplets(null, "MODAL_PAGE");

            //Debe establecer zIndex superior al LoadingDialog. Si se carga una página modal, no se anulan las peticiones pendientes. En este caso divLoadingDialog debe quedar por debajo.
            mainDiv.style.zIndex = "9999992";
        }
        else
            mainDiv.style.zIndex = "9999990"; //Este valor está correlacionado con el div definido en el objeto LoadingContainer.Show();

        //Elimina la referencia al elemento div
        MM.Ajax.JSDOM_Manager.RemoveDOMElement(div);

        //Añade el div a DivExtended
        $("DivExtended").appendChild(mainDiv);

        if (!isModal)
            MM.VCWUtils.LoadingDialog.Instances["LD_Base"].Show();
    }
};

//Handler que controla la actualización de datos de una sección por streaming.
MM.Ajax.Data = {
    //Array que contiene los grupos de parámetros.
    ParamsGroup: new Array(),
    //Array que contiene las secciones que deben actualizarse dinámicamente. Identifica las secciones por ContainerId
    SectionsToUpdate: new Array(),
    //Peticiones enviadas al servidor.
    ReqId: 0,
    //Diferencia de la hora del cliente y del servidor.
    ServerTimeDif: null,
    //Indica si la actualización de datos ha sido iniciada.
    IsStarted: false,
    Frequency: 0,
    BaseUrl: null,
    //Añade una lista de secciones.
    //- parameters: Parámetros con que los que se obtuvo las secciones
    //- sections: Array de secciones a añadir
    AddSections: function(parameters, sections) {
        //Verifica si los parámetros con los que se cargaron las secciones ya están añadidos en el array ParamsGroup
        var idxParamsGroup = -1;
        var firstNull = -1;
        for (var i = 0; i < MM.Ajax.Data.ParamsGroup.length; i++) {
            var paramGroup = MM.Ajax.Data.ParamsGroup[i];
            if (paramGroup == null) {
                if (firstNull == -1)
                    firstNull = i;

                continue;
            }

            if (paramGroup.Params == parameters) {
                idxParamsGroup = i;
                //Aumenta el valor de ref ya que hay varias secciones con los mismos parámetros.
                paramGroup.Ref = paramGroup.Ref + sections.length;

                break;
            }
        }

        //Añade los parámetros en el array ParamsGroup
        if (idxParamsGroup == -1) {
            if (firstNull == -1)
                firstNull = MM.Ajax.Data.ParamsGroup.length;

            MM.Ajax.Data.ParamsGroup[firstNull] = { Ref: sections.length, Params: parameters };
            idxParamsGroup = firstNull;
        }

        //Añade las secciones
        for (var i = 0; i < sections.length; i++) {
            var section = sections[i];
            var sectionId = section[2].toUpperCase();

            //Comprueba si la sección ya fue añadida al array
            if (MM.Ajax.Data.SectionsToUpdate[sectionId])
                ThrowJSError("Section " + sectionId + " has already been added.");

            MM.Ajax.Data.SectionsToUpdate[sectionId] = { "IdxParamsGroup": idxParamsGroup, "Page": section[0].toUpperCase(), "IsRemote": section[1], "SectionId": sectionId, "StartsAt": MM.VCWUtils.ParseUtils.GetDate(section[3]), "EndsAt": MM.VCWUtils.ParseUtils.GetDate(section[4]), "ReqId": this.ReqId, "Frequency": section[5] };
        }
    },

    RemoveSection: function(sectionId) {
        var section = MM.Ajax.Data.SectionsToUpdate[sectionId];
        if (section != undefined) {
            //Antes de eliminar la sección, busca el grupo de parámetros al cual está asociada.
            var idxParamsGroup = section.IdxParamsGroup;
            var paramGroup = MM.Ajax.Data.ParamsGroup[idxParamsGroup];
            if (paramGroup.Ref == 1)
                delete MM.Ajax.Data.ParamsGroup[idxParamsGroup];
            else
                paramGroup.Ref--;

            delete MM.Ajax.Data.SectionsToUpdate[sectionId];
        }
    },

    HandleSuccess: function(o) {
        var xmlNode = o.responseXML.firstChild;
        if (xmlNode != null && xmlNode.tagName == "xmlServices") {
            //Si reqType=0 || 10, se trata de una petición html o una página de error, por lo que debe resolverse en MM.Ajax.Html. Este caso puede producirse si falla la petición y el servidor redirige a otra página.
            if (xmlNode.getAttribute('reqType') == null || xmlNode.getAttribute('reqType') == "0" || xmlNode.getAttribute('reqType') == "10") {
                MM.Ajax.Html.HandleSuccess(o);
                MM.Ajax.Data.Stop();
                return;
            }

            //Calcula la diferencia horaria entre el cliente y el servidor
            var dateClient = new Date();
            MM.Ajax.Data.ServerTimeDif = dateClient - MM.VCWUtils.ParseUtils.GetDate(o.responseXML.firstChild.getAttribute('currentTime'));

            //Comprueba que exista un hijo.
            xmlNode = xmlNode.firstChild;

            //Verifica si debe ejecutar scripts.
            if (xmlNode != null && xmlNode.tagName == "scripts") {
                //--- Carga los archivos JS definidos en el xml.
                var nodesJS = xmlNode.getElementsByTagName('script');
                MM.Ajax.Html.LoadFile(nodesJS, true);
                MM.Ajax.JSLoader.JSFlush();

                xmlNode = xmlNode.nextSibling;
            }

            //Actualiza las secciones indicadas.
            if (xmlNode != null && xmlNode.tagName == "sections") {
                var lgt = xmlNode.childNodes.length;
                for (var i = 0; i < lgt; i++) {
                    var section = xmlNode.childNodes[i];
                    if (section.tagName == "section") {
                        var sectionId = section.getAttribute('sId').toUpperCase();

                        //Verifica que la sección recibida exista
                        var objSection = $(sectionId);
                        if (!objSection)
                            continue;

                        //Verifica que la sección recibida esté en la colección sectionsToUpdate y que ReqID sea superior al número de petición en que se añadió la sección.
                        var sectionToUpdate = MM.Ajax.Data.SectionsToUpdate[sectionId];
                        var reqId = o.responseXML.firstChild.getAttribute('reqId');
                        if (sectionToUpdate != undefined && sectionToUpdate.ReqId < reqId) {
                            var sLgt = section.childNodes.length;
                            for (var j = 0; j < sLgt; j++) {
                                var data = section.childNodes[j];
                                //Verifica si el hijo es de tipo data.
                                if (data.tagName == "data" && data.nodeValue != "") {
                                    //Obtiene el contenido del tag. (Recorre un bucle porque FireFox corta el nodo en 4096 byts).
                                    var dataText = "";
                                    var dLgt = data.childNodes.length;
                                    for (var k = 0; k < dLgt; k++)
                                        dataText += data.childNodes[k].nodeValue;

                                    var mmTableInstance = MM.Ajax.MMTable.MMTables[sectionId];
                                    if (mmTableInstance != null) {
                                        //Genera el array con los datos de la estructura obtenida.
                                        var aDataArray = eval(dataText);
                                        //Obtiene el objeto a aData.
                                        var aData = MM.Ajax.MMTable.GetMMData(aDataArray);
                                        //Objeto MMTable que se actualizará en función del id.
                                        MM.Ajax.MMTable.MMTables[sectionId].UpdateTable(aData, true);

                                        //Actualiza la hora de la última actualización de la sección.
                                        sectionToUpdate.LastDate = new Date();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        //Llamada del timer para volver a ejecutar el método request
        if (MM.Ajax.Data.Frequency > 0)
            setTimeout("MM.Ajax.Data.Request()", MM.Ajax.Data.Frequency);
    },
    HandleFailure: function(o) {
        var failure = MM.Ajax.Html.DecodeFailure(o);
        if (failure == null)
            return;

        if (failure.ErrorCode >= 1500 && failure.ErrorCode <= 1550) //Los errores que indican que el servidor está saturado detienen el Ajax.
            MM.Ajax.Data.Stop();
        else if (failure.ErrorCode >= 2000 & failure.ErrorCode < 3000) //Los errores de seguridad no reintentan
            MM.VCWUtils.ErrorHandler.Invoke(failure.ErrorHandler, failure.ErrorContainerId, failure.ErrorCode, failure.ErrorMessage, failure.StackTrace, failure.FriendlyErrorMessage);
        else //Reintenta la petición
            setTimeout("MM.Ajax.Data.Request()", MM.Ajax.Data.Frequency);
    },
    Request: function() {
        if (MM.Ajax.Data.IsStarted) {
            var sections = "";
            var params = "";

            //Almacena en este array las secciones que tengan la misma página y el mismo IdxParamsGroup
            var aSections = new Array();
            for (var sectionId in MM.Ajax.Data.SectionsToUpdate) {
                //Comprueba si la sección o bien alguno de sus padres están ocultos
                var objSection = $(sectionId);
                var visible = YAHOO.util.Dom.isVisible(objSection);

                //Si la sección está oculta no se añade a la lista de secciones a actualizar.
                if (!visible)
                    continue;

                var sectionToUpdate = MM.Ajax.Data.SectionsToUpdate[sectionId];
                var date = new Date();
                var serverTime = (date - MM.Ajax.Data.ServerTimeDif);
                var parentNodeId = objSection.parentNode.id;
                objSection = null; //Libera la referencia al objeto                    

                //Comprueba si ha pasado el tiempo suficiente como para actualizar la seccion
                if (sectionToUpdate.LastDate && (date - sectionToUpdate.LastDate) < sectionToUpdate.Frequency)
                    continue;

                //Verifica si el mercado está abierto
                if (this.ServerTimeDif != null && serverTime < sectionToUpdate.StartsAt && serverTime > sectionToUpdate.EndsAt)
                    continue;

                var aSectionsItem = aSections[sectionToUpdate.Page + "_" + sectionToUpdate.IdxParamsGroup];
                if (aSectionsItem == undefined)
                    aSections[sectionToUpdate.Page + "_" + sectionToUpdate.IdxParamsGroup] = { Page: sectionToUpdate.Page, IsRemote: sectionToUpdate.IsRemote, IdxParamsGroup: sectionToUpdate.IdxParamsGroup, Containers: parentNodeId + ",", Sections: sectionId + ",", DataLengths: MM.Ajax.MMTable.MMTables[sectionId].GetMMData().Length() + "," };
                else {
                    aSectionsItem.Containers += parentNodeId + ",";
                    aSectionsItem.Sections += sectionId + ",";
                    aSectionsItem.DataLengths += MM.Ajax.MMTable.MMTables[sectionId].GetMMData().Length() + ",";
                }
            }

            //Recorre el array aSections
            for (var pageGroup in aSections) {
                var section = aSections[pageGroup];
                sections += section.Page + "," + section.IsRemote + "," + section.IdxParamsGroup.toString() + "," + section.Containers + section.Sections + section.DataLengths.substring(0, section.DataLengths.length - 1) + ";";
            }

            if (sections.length > 0) {
                sections = sections.substring(0, sections.length - 1);

                //Prepara las agrupaciones de parámetros
                var lgt = MM.Ajax.Data.ParamsGroup.length;
                for (var i = 0; i < lgt; i++) {
                    var paramGroup = MM.Ajax.Data.ParamsGroup[i];
                    if (paramGroup == null)
                        params += "&Param_" + i.toString() + "=''";
                    else
                        params += "&Param_" + i.toString() + "=" + encodeURIComponent(paramGroup.Params);
                }

                MM.Ajax.Data.ReqId++;
                var parameters = "Sections=" + sections + params + "&ReqId=" + MM.Ajax.Data.ReqId + "&u=" + MM.VCWUtils.GetUniqueValue();

                //Si el protocolo actual es HTTPS y se está enviando una petición HTTP lanza un error.
                if (document.location.href.substring(0, 6).toLowerCase() == "https:" && parameters.substring(0, 5).toLowerCase() == "http:")
                    ThrowJSError("Invalid protocol. Change the current protocol to HTTP and try again.");

                YAHOO.util.Connect.asyncRequest("POST", MM.Ajax.Data.BaseUrl, { success: MM.Ajax.Data.HandleSuccess, failure: MM.Ajax.Data.HandleFailure }, parameters);
            }
            else if (MM.Ajax.Data.Frequency > 0)
                setTimeout("MM.Ajax.Data.Request()", MM.Ajax.Data.Frequency);
        }
    },
    Start: function() {
        if (!MM.Ajax.Data.IsStarted) {
            MM.Ajax.Data.IsStarted = true;
            MM.Ajax.Data.Request();
        }
    },
    Stop: function() {
        MM.Ajax.Data.IsStarted = false;
    }
}

//Objeto para controlar la jerarquía de contenedores y objetos que se insertan en una página vía Ajax.
//Como el contenido de las páginas cambia con peticiones asíncronas, y el servidor desconoce donde se insertará el nuevo contenido,
//este control se asegura de identificar de manera única un objeto, en función de sus nodos padres (de ahí el nombre NodeSetsManager).
//Así, un objeto O, que se inserte en el documento se identificará de manera única por sus nodos padres. Ejemplo:
// A
//  B
//   C
//    D
//      O --> Los nodos padres A,B,C,D identifican de manera única a este objeto O.
// Este mismo objeto O, podría insertarse otra vez en la página, pero en otro nodo. Ejemplo:
// A
//  B
//   C
//    E
//      O --> Los nodos padres A,B,C,E identifican de manera única a este objeto O.
// De esta manera, es posible insertar varias veces el mismo objeto en la misma página. Combinando los ejemplos anteriores, es posible tener los dos objetos O:
// A
//  B
//   C
//    D
//      O --> Objeto
//    E
//      O --> Objeto
// Para evitar conflictos de nombres, por cada conjunto de padres único, se genera un prefijo que se envía al servidor, el cual es utilizado a la hora de generar los nombres de las secciones.
MM.Ajax.NodeSetsManager = {
    nodeSets: new Array(), //Array que almacena todos los NodeSets que contiene el documento.
    lastNodeSet: 0,

    //Busca un objeto con nombre objectId que esté visible y lo devuelve.
    //Se utiliza este método porque el mismo objeto podría estar presente en la misma página varias veces (solo que en distintos nodos). Esta función busca el primer objeto visible.
    GetVisibleObject: function(objectId) {
        var obj = $(objectId);
        if (obj == null) {
            var lgt = this.nodeSets.length;
            for (var i = 0; i < lgt; i++) {
                obj = $(this.nodeSets[i] + "_" + objectId);
                if (obj != null)
                    break;
            }
        }

        return obj;
    },

    //Obtiene el nombre del conjunto de nodos que identifica un determinado objeto.
    GetNodeSet: function(objectId) {
        var obj = $(objectId);

        //Si el objeto no existe, busca uno concatenando los NodeSets generados hasta el momento.
        if (obj == null) {
            var visibleObject = this.GetVisibleObject(objectId);
            if (visibleObject == null)
                return null;

            obj = visibleObject.Object;
        }

        var nodeSet = obj.getAttribute("nodeSet");
        if (nodeSet == null || nodeSet == "") {
            //Si no ha encontrado ningún conjunto de nodos padres con los mismos elementos, significa que se trata de un nuevo elemento, y lo añade a la jerarquía de objetos.
            nodeSet = "UI_" + this.lastNodeSet++;
            obj.setAttribute("nodeSet", nodeSet);
            this.nodeSets.push(nodeSet);
        }

        return nodeSet;
    },

    //Evalúa el nombre de un objeto (que se recibe sin NodeSet) y determina que prefijo le corresponde.
    EvalObjectId: function(objectId) {
        var visibleObject = this.GetVisibleObject(objectId);
        if (visibleObject != null)
            return visibleObject.id;

        return objectId;
    },

    //Cada vez que se remueve un objeto, verifica si contiene NodeSets.
    RemoveObject: function(obj) {
        //Verifica si el objeto que se está removiendo contiene la definición de un NodeSet.
        var nodeSet = obj.getAttribute("nodeSet");
        if (nodeSet != null && nodeSet != "") {
            var lgt = this.nodeSets.length;
            for (var i = lgt - 1; i >= 0; i--) {
                if (this.nodeSets[i] == nodeSet) {
                    this.nodeSets.splice(i, 1);
                    break;
                }
            }
        }
    }
}
if (window["MM"]!=undefined) 
 MM.Ajax.JSLoader.JSLoaded("js/jsobjects/ajaxhandler.js");