htmlGrid.js

/**
 * Aquí están contenidas todas las funciones que se encargan de darle interacción a la grid de Fw7, todas las funciones 
 * que usen la grid, requieren que ya esté inicializada
 * @module HtmlGrid
 * @author Cristian Camilo Cantillo
 * @tutorial gridConf
 * @tutorial gridEvent
 */

var _$ = jQuery.noConflict();
var dProgress = null;
var STATUS_LOCK = "1", STATUS_UNLOCK = "0";
var maxRowsValidate = 50;

/**
*Se encarga de inicializar el objeto que manejara las funciones de la grid
*@param {string} id id de la tabla HTML
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@returns {object} Objeto configurado con las opciones y listo para usarse
*@example initFromTable("gPruebas", gPruebasConf);
*/
function initFromTable(id, conf) {
    var grid = null;
    var tabla = _$(id);

    if (tabla != null && tabla.length > 0) {
        var contTabla = tabla.parents('.data-table-init')[0];
        var header = tabla.find('thead')[0];
        var body = tabla.find('tbody')[0];
        var tablaLimpia = null;
        var inWorker = false;

        if (_$(id + 'L_').length > 0)
            tablaLimpia = _$(id + 'L_')[0];

        grid = {
            'tabla': tabla[0], 'header': header, 'body': body, 'contenedor': contTabla
            , 'colsHidden': {}, 'colsFormat': {}, 'pintarFilaOculta': true, 'editOnClick': false
            , 'valPopup': false, 'addEventOpt': false, rowsBuffer: '', rowsBufferNum: 0, 'tablaL': tablaLimpia
            , 'iconosOpt': '', 'colOpt': -1, liteValidate:false, pagination: false
        };

        if (grid.tablaL != null) {
            if (_$(grid.tablaL).find('tbody')[0].rows.length > maxRowsValidate) {
                inWorker = true;
                initWorker();
                convertirHTML(grid, conf);
            } else {
                grid.body.innerHTML = _$(grid.tablaL).find('tbody').html();
            }
        }

        if (getRowsNum(grid) > 0) {
            if (!inWorker) {
                _addRowIdAndValueFromTable(grid, conf);
                _validateAttrGrid(grid);
            }
            
            if (getRowsNum(grid) < maxRowsValidate)
                _addAllDataCollapsableTitle(grid);

            if (!inWorker)
                _addSelectRow(grid);
        }

        if (conf != null) {
            hideColumnsConf(grid, conf);
            addMaxLength(conf);
        }
    }
    
    return grid;
}

function _selectRow(row) {
    if (!_$(row).hasClass('table-active')) {
        _$(row).addClass('table-active');
        var grid = getGridFromRow(row);
        var rId = getRowId(row);
        _onRowSelected(grid, rId);
    }
}

/**
*Selecciona la fila indicada en el parámetro row
*@param {HTMLTableRowElement} row debe ser el tr de la fila a seleccionar
*@example var rId = getSelectedRowId(gPruebas);
var row = getRow(gPruebas, rId);
*selectRow(row);
*/
function selectRow(row) {
    _selectRow(row);
    verModalOnClick(row);
}

/**
*Quita la selección de la fila indicada en el parámetro row
*@param {HTMLTableRowElement} row debe ser el tr de la fila a seleccionar
*@example 
var rId = getSelectedRowId(gPruebas);
var row = getRow(gPruebas, rId);
*unSelectRow(row);
*/
function unSelectRow(row) {
    if (_$(row).hasClass('table-active'))
        _$(row).removeClass('table-active');
}

/**
*Devuelve el rowId de la fila que esté seleccionada
*@param {object} grid Objeto de la grid ya inicializada
*@returns {string} rowId único que identifica a cada fila
*@example var rId = getSelectedRowId(gPruebas);
*/
function getSelectedRowId(grid) {
    var row = _$(grid.body).find("tr[class='table-active']");
    if (row.length > 0)
        return getRowId(row[0]);

    return '';
}

/**
*Devuelve el índice de la fila que esté seleccionada
*@param {object} grid Objeto de la grid ya inicializada
*@returns {string} rowId único que identifica a cada fila
*@example var rIdx = getSelectedRowIndex(gPruebas);
*/
function getSelectedRowIndex(grid) {
    var row = _$(grid.body).find("tr[class='table-active']");
    if (row.length > 0)
        return getRowIndex(row);

    return -1;
}

/**
*Devuelve el objeto de la grid a partir de una fila (row)
*@param {HTMLTableRowElement} row algún tr de la tabla
*@returns {object} Objeto de la grid ya inicializada
*/
function getGridFromRow(row) {
    return getGridFrom(row);
}

/**
*Devuelve el objeto de la grid a partir de una celda (cell)
*@param {HTMLTableCellElement} cell algún td de la tabla
*@returns {object} Objeto de la grid ya inicializada
*/
function getGridFromCell(cell) {
    return getGridFrom(cell);
}

function getGridFrom(obj) {
    var tb = _$(obj).parents('table')[0];
    return objGrids[tb.id].grid;
}

/**
*Devuelve un nuevo y único rowId
*@returns {string} El nuevo rowId generado
*@see <a href="https://www.w3resource.com/javascript-exercises/javascript-math-exercise-23.php" target="_blank">Código de Referencia</a>
*/
function getNewRowId() {
    var dt = new Date().getTime();
    var rId = 'xxxxx4xxyxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (dt + Math.random() * 16) % 16 | 0;
        dt = Math.floor(dt / 16);
        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });

    return rId;
}

/**
*Se encarga de recorrer todas las filas de la grid
*@param {object} grid Objeto de la grid ya inicializada
*@param {function} fn Función que se encargará de procesar el ciclo por cada fila,
* la función que se envía como parámetro recibe el rowId de cada fila
*@example foreachRow(gPruebas, function(rId){
    var row = getRow(gPruebas, rId);
    unSelectRow(row);
});
*/
function foreachRow(grid, fn) {
    var nRows = getRowsNum(grid);
    var rows = grid.body.rows;
    
    for (var i = 0; i < nRows; i++) {
        if (rows[i] != null)
            fn.apply(this, [getRowId(rows[i])]);
    }
}

/**
*Se encarga recorrer todas las celdas de una fila
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId de la fila que se desea recorrer
*@param {function} fn Función que se encargará de procesar el ciclo por cada celda,
* la función que se envía como parámetro recibe el objeto cell (td) y el ínidice de cada celda
*@example var rId = getSelectedRowId(gPruebas);
foreachCell(gPruebas,rId, function(cell, cInd){
    var val = getCellValue(gPruebas, rId, cInd);
    console.log(val);
});
*/
function foreachCell(grid, rId, fn) {
    var row = getRow(grid, rId);
    var nCells = row.cells.length;

    for (var i = 0; i < nCells; i++) {
        fn.apply(this, [row.cells[i], i]);
    }
}

/**
*Devuelve el número de filas de una grid
*@param {object} Objeto de la grid ya inicializada
*@returns {integer} Número equivalente a la cantidad de filas
*/
function getRowsNum(grid) {
    return grid.body.rows.length;
}

/**
*Devuelve el número de columnas que tiene una grid
*@param {object} Objeto de la grid ya inicializada
*@returns {integer} Número equivalente a la cantidad de columnas
*/
function getCellsNum(grid) {
    return grid.header.rows[0].cells.length;
}

/**
*Devuelve el objeto row (tr) de la grid según el rowId
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@returns {HTMLTableRowElement} row de la fila indicada
*/
function getRow(grid, rId) {
    var filtro = "tr[data-rid='{0}']".format(rId);
    var row = _$(grid.body).find(filtro);

    if (row.length > 0)
        return row[0];

    return null;
}

/**
*Devuelve el objeto row (tr) de la grid según el índice de la fila
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} index índice que identifica a cada fila (0,1,2)
*@returns {HTMLTableRowElement} row de la fila indicada
*/
function getRowFromIndex(grid, index) {
    var row = _$(grid.body.rows).eq(index);

    if (row.length > 0)
        return row[0];

    return null;
}

function getHeaderCell(grid, cInd) {
    var row = _$(grid.header.rows[0]).children().eq(cInd);

    if (row.length > 0)
        return row[0];

    return null;
}

/**
*Obtiene el Label de una columna especificada
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} cInd Índice o posición del header
*@returns {string} El label del header
*/
function getHeaderLabel(grid, cInd) {
    var row = getHeaderCell(grid, cInd);
    if (row != null)
        return row.innerHTML;

    return '';
}

/**
*Cambiar el rowId de una fila
*@param {HTMLTableRowElement} row algún tr de la tabla
*@param {string} rId rowId único que identifica a cada fila
*/
function setRowId(row, rId) {
    row.dataset.rid = rId;
}

/**
*Obtener el rowId de una fila
*@param {HTMLTableRowElement} row algún tr de la tabla
*@returns {string} rowId único que identifica a cada fila
*/
function getRowId(row) {
    return row.dataset.rid;
}

/**
*Obtener el Indice de una fila
*@param {HTMLTableRowElement} row algún tr de la tabla
*@returns {integer} ïndice o posición de esa fila en la tabla
*/
function getRowIndex(row) {
    return _$(row).index();
}

/**
*Obtener el Indice de una fila por el rowId
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@returns {integer} ïndice o posición de esa fila en la tabla
*/
function getRowIndexFromId(grid, rId) {
    var row = getRow(grid, rId);
    return _$(row).index();
}

/**
*Obtener el rowId de una fila por el índice
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} index ïndice o posición de esa fila en la tabla
*@returns {string} rowId único que identifica a cada fila
*/
function getRowIdFromIndex(grid, index) {
    var row = getRowFromIndex(grid, index);
    return row.dataset.rid;
}

/**
*Obtener el rowId de una fila por una celda especificada
*@param {HTMLTableCellElement} cell algún td de la fila
*@returns {string} rowId único que identifica a cada fila
*/
function getRowIdFromCell(cell) {
    var row = _$(cell).parent('tr')[0];
    return getRowId(row);
}

/**
*Obtiene el valor de una celda específica
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {integer} cInd Índice de la columna específica
*@example var rId = getSelectedRowId(gPruebas);
var val = getCellValue(gPruebas, rId, gPruebasConf.Nombre.idx);
console.log(val);
*/
function getCellValue(grid, rId, cInd) {
    var row = getRow(grid, rId);
    return getRowCellValue(row, cInd);
}

/**
*Obtiene el valor de una celda específica
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} rIdx índice de la fila
*@param {integer} cInd Índice de la columna específica
*@example var val = getCellValue(gPruebas, 1, gPruebasConf.Nombre.idx);
console.log(val);
*/
function getCell2Value(grid, rIdx, cInd) {
    var row = getRowFromIndex(grid, rIdx);
    return getRowCellValue(row, cInd);
}

function getRowCellValue(row, cInd) {
    var cell = getCell(row, cInd);
    if (cell == null)
        return '';

    return cell.dataset.value;
}

/**
*Cambia el valor de una celda específica
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {integer} cInd Índice de la columna específica
*@param {string} value Valor que se desea asignar a la celda
*@example var rId = getSelectedRowId(gPruebas);
setCellValue(gPruebas, rId, gPruebasConf.Nombre.idx, 'Ricardo Jorge');
*/
function setCellValue(grid, rId, cInd, value) {
    var row = getRow(grid, rId);
    var cell = getCell(row, cInd);
    setCell2Value(cell, value, grid, cInd);
}

function setCell2Value(cell, value, gridN, cIndN) {
    var cInd = (cIndN != null)? cIndN : getCellIndex(cell);
    var grid = (gridN != null) ? gridN : getGridFromCell(cell);

    if (grid != null && grid.colsFormat[cInd] != null) {
        cell.innerHTML = _aplNF(grid, value, cInd);
        cell.dataset.value = _aplNFb(grid, cell.innerHTML, cInd);
    } else {
        cell.dataset.value = value;
        cell.innerHTML = value;
    }
}

function setCell2ValueStr(cell, value, grid, cInd) {
    var lblCol = getHeaderLabel(grid, cInd);
    var hide = (isColumHidden(grid, cInd)) ? 'style="display: none;"' : '';

    if (grid != null && grid.colsFormat[cInd] != null) {
        var valHTML = _aplNF(grid, value, cInd);
        cell = cell.format(value, lblCol, valHTML, hide);
    } else {
        cell = cell.format(value, lblCol, value, hide);
    }

    return cell;
}

/**
*Obtener el objeto td especificado
*@param {HTMLTableRowElement} row tr de la tabla
*@param {integer} cInd Índice de la columna específica
*@returns {HTMLTableCellElement} cell (td) de la fila y columna indicada
*/
function getCell(row, cInd) {
    var cell = _$(row).children().eq(cInd);
    if (cell.length > 0)
        return cell[0];

    return null;
}

/**
*Obtener el índice de una celda especificada
*@param {HTMLTableCellElement} cell (td) de una fila
*@returns {integer} Índice de la celda
*/
function getCellIndex(cell) {
    return _$(cell).index();
}

/**
*Oculta una columna especificada
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} cInd Índice de la columna
*@example hideColumn(gPruebas, gPruebasConf.Nombre.idx);
*/
function hideColumn(grid, cInd) {
    hideShowColumn(grid, cInd, false);
}

/**
*Oculta varias columna especificadas
*@param {object} grid Objeto de la grid ya inicializada
*@param {Array} arrCInd Arreglo con los Índices de las columnas
*@example hideColumns(gPruebas, [gPruebasConf.Nombre.idx, gPruebasConf.Edad.idx]);
*/
function hideColumns(grid, arrCInd) {
    for (var i = 0; i < arrCInd.length; i++) {
        hideShowColumn(grid, arrCInd[i], false);
    }
}

/**
*Oculta las columna según la propiedad ver de la configuración {@tutorial gridConf}
*@param {object} grid Objeto de la grid ya inicializada
*@param {object} conf Objeto de configuración de la grid
*@example hideColumnsConf(gPruebas, gPruebasConf);
*/
function hideColumnsConf(grid, conf) {
    for (var c in conf) {
        if (conf[c].ver != null && !conf[c].ver)
            hideColumn(grid, conf[c].idx);
    }
}

/**
*Muestra una columna especificada
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} cInd Índice de la columna
*@example showColumn(gPruebas, gPruebasConf.Nombre.idx);
*/
function showColumn(grid, cInd) {
    hideShowColumn(grid, cInd, true);
}

/**
*Muestra varias columna especificadas
*@param {object} grid Objeto de la grid ya inicializada
*@param {Array} arrCInd Arreglo con los Índices de las columnas
*@example showColumns(gPruebas, [gPruebasConf.Nombre.idx, gPruebasConf.Edad.idx]);
*/
function showColumns(grid, arrCInd) {
    for (var i = 0; i < arrCInd.length; i++) {
        hideShowColumn(grid, arrCInd[i], true);
    }
}

function hideShowColumn(grid, cInd, view) {
    foreachRow(grid, function (rId) {
        var row = getRow(grid, rId);
        var cell = getCell(row, cInd);

        if (view) 
            _$(cell).css('display', '');
        else 
            _$(cell).css('display', 'none');
    });

    var hCell = getHeaderCell(grid, cInd);

    if (view) {
        _$(hCell).css('display', '');
        delete grid.colsHidden[_$(hCell).index()];
    } else {
        grid.colsHidden[_$(hCell).index()] = _$(hCell).index();
        _$(hCell).css('display', 'none');
    }
}

/**
*Marca si al encontrar un error en una celda oculta, se pinte toda la fila de rojo
*@param {object} grid Objeto de la grid ya inicializada
*@param {boolean} validar true si desea que se marque toda la fila, false lo contrario
*@example pintarFilasOcultas(gPruebas, false);
*/
function pintarFilasOcultas(grid, validar) {
    grid.pintarFilaOculta = validar;
}

/**
*Marca si se desea que al hacer click en una celda se abra el modal automáticamente para editar
*@param {object} grid Objeto de la grid ya inicializada
*@param {boolean} edit true si desea que se abra el modal, false lo contrario
*@example editOnClick(gPruebas, true);
*/
function editOnClick(grid, edit) {
    grid.editOnClick = edit;
}

/**
*Habilitar lo lógica de editar y eliminar automáticamente
*@param {object} grid Objeto de la grid ya inicializada
*@param {boolean} enabled true si desea que habilitar, false lo contrario
*@param {string} iconosOpt Íconos de agregado y eliminado
*@param {integer} colOpt Índice de la columna donde van a estar los íconos
*@example var optAdd = "<i class='material-icons pointer iPopup' data-tipo='add'>add_circle_outline</i>";
var optDel = "<i class='material-icons pointer iPopup' data-tipo='delete'>remove_circle_outline</i>";
var iOpts = optAdd + optDel;
addEventOpt(gPruebas, true, iOpts, gPruebasConf.Opc.idx);
*/
function addEventOpt(grid, enabled, iconosOpt, colOpt) {
    grid.addEventOpt = enabled;

    if (iconosOpt != null)
        grid.iconosOpt = iconosOpt;

    if (colOpt != null)
        grid.colOpt = colOpt;
}

/**
*Marca si se desea marcar con error los campos en el modal cuando se tenga error en la grid
*@param {object} grid Objeto de la grid ya inicializada
*@param {boolean} validar true si desea que se marquen los campos, false lo contrario
*@example validarCamposPopup(gPruebas, true);
*/
function validarCamposPopup(grid, validar) {
    grid.valPopup = validar;
}

/**
*Agrega una fila a la grid
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId (opcional) rowId único que identifica a cada fila, si no se envía se asigna uno automáticamente
*@param {Array} values Contiene los valores a agregar en la fila, si el arreglo es vacio, crea la fila sin datos
*@param {boolean} noCallEvt (opcional) Se envia true si al agregar la fila no se quiere disparar el evento onRowAdded 
*@example addRow(gPruebas, '', []);
*/
function addRow(grid, rId, values, noCallEvt) {
    //Se esta creando por HTML y no por DOM
    return addRowStr(grid, rId, values, noCallEvt);
}

function addRowStr(grid, rId, values, noCallEvt) {
    if (rId == null || rId == '')
        rId = getNewRowId();

    var newRow = _getNewRowStr(grid, values, rId);
    _$(grid.body).append(newRow);

    if (noCallEvt == null || !noCallEvt)
        _onRowAdded(grid, rId);

    newRow = getRow(grid, rId);
    addEventsOpt(grid, newRow);

    _addEventRow(newRow, 'click', function (e) {
        _removeSelectedAll(grid);
        selectRow(this);
    });

    return rId;
}

function addEventsOpt(grid, row) {
    //Eventos para opciones de agregar y eliminar
    if (grid.addEventOpt) {
        if (grid.colOpt != null && grid.iconosOpt != null) {
            var rId = getRowId(row);
            var valOpt = getCellValue(grid, rId, grid.colOpt);
            if (valOpt == '')
                setCellValue(grid, rId, grid.colOpt, grid.iconosOpt);
        }

        var opts = _$(row).find(".iPopup");
        if (opts.length > 0) {
            opts.unbind();
            opts.on('click', function (e) {
                e.stopPropagation();
                var row = _$(this).parents('tr')[0];

                _removeSelectedAll(grid);
                _selectRow(row);
                verModalF7(e);
            });
        }
    }
}

/**
*Cambia los valores de una fila completa
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {Array} values tiene los valores de cada columna
*@example var rId = getSelectedRowId(gPruebas);
setDataRow(gPruebas, rId, ['Juan Carlos', 'Gonzales', '28']);
*/
function setDataRow(grid, rId, values) {
    if (rId != '') {
        foreachCell(grid, rId, function (cell, cInd) {
            if (values[cInd] != null)
                setCell2Value(cell, values[cInd], grid, cInd);
            else
                setCell2Value(cell, '', grid, cInd);
        });

        var row = getRow(grid, rId);
        addEventsOpt(grid, row);
        _onRowEdit(grid, rId);
    }
}

/**
*Limpia una fila
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@example var rId = getSelectedRowId(gPruebas);
clearRow(gPruebas, rId);
*/
function clearRow(grid, rId) {
    if (rId != '') {
        var row = getRow(grid, rId);

        foreachCell(grid, rId, function (cell, cInd) {
            setCell2Value(cell, '', grid, cInd);
        });
    }
}

/**
*Indica si una fila tiene datos, ignorando las opciones de agregar y eliminar
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@returns {boolean} true si tiene, false si no tiene
*@example var rId = getSelectedRowId(gPruebas);
if(dataInRow(gPruebas, rId)){
    console.log('Tiene datos');
}
*/
function dataInRow(grid, rId) {
    if (rId != '') {
        var row = getRow(grid, rId);
        var iData = false;

        foreachCell(grid, rId, function (cell, cInd) {
            var val = getCellValue(grid, rId, cInd);

            if (!iData && val != '' && _$(cell).find('i').length == 0)
                iData = true;
        });
    }

    return iData;
}

/**
*Elimina una fila especificada
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@example var rId = getRowIdFromIndex(gPruebas, 3);
removeRow(gPruebas, rId);
*/
function removeRow(grid, rId) {
    _onBeforeRowDelete(grid, rId);
    __removeRow(grid, rId);
    _onRowDelete(grid);
}

function __removeRow(grid, rId) {
    var row = getRow(grid, rId);
    _$(grid.body)[0].removeChild(row);
}

/**
*Obtiene el XML de una grid
*@param {object} grid Objeto de la grid ya inicializada
*@param {Array} cIndExcluir Índices de las columnas que no se desean grabar
*@param {function} fnVal (opcional) Función para validar si una fila se graba o no,
* la función que se envía como parámetro recibe el rowId y debe retornar un booleano
*@example var xmlGrid = getXml(gPruebas, [gPruebasConf.Opc.idx], function(rId){
    var nombre = getCellValue(gPruebas, rId, gPruebasConf.Nombre.idx);
    if(nombre == 'Andrés')
        return false;
    
    return true;
});
*/
function getXml(grid, cIndExcluir, fnVal) {
    var fRow = '<r id="{0}">{1}</r>';
    var fCell = '<c>{0}</c>';
    var arrRows = [];
    var idx = 0;

    foreachRow(grid, function (rId) { 
        var arrCells = [];

        if (fnVal != null) {
            var rowValid = fnVal.apply(grid, [rId]);
            if (!rowValid)
                return;
        }

        foreachCell(grid, rId, function (cell, cInd) {
            if (cIndExcluir.indexOf(cInd) == -1) {
                var val = cell.dataset.value;
                arrCells.push(fCell.format(val));
            }
        });

        arrRows.push(fRow.format(idx, arrCells.join('')));
        idx++;
    });

    return arrRows.join('');
}

/**
*Obtiene el XML de una grid en otro hilo
*@param {object} grid Objeto de la grid ya inicializada
*@param {Array} cIndExcluir Índices de las columnas que no se desean grabar
*@param {function} fnCb (opcional) Función de callback cuando el proceso ha finalzado, se entrega el XML
*@example getXmlWorker(gPruebas, [gPruebasConf.Opc.idx], function(xmlGrid){
    console.log(xmlGrid);
});
*/
function getXmlWorker(grid, cIndExcluir, fnCb) {
    worker = new Worker('../javascript/htmlGridWorker.js?v=5');
    worker.addEventListener('message', function (e) {
        var obj = e.data;
        if (obj.nFilas != null && obj.xml != null) {
            worker.terminate();
            fnCb.apply(this, [obj.xml]);
        }
    });

    var html = grid.body.innerHTML;
    worker.postMessage({ 'html': html, 'tipo': 'xml', 'colsEx': cIndExcluir });
}

/**
*Carga filas en la grid mediante una cadena XML
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} xml datos a parsear
*@example var xmlSample = "<rows><row id="0"><cell>Pepito</cell><cell>26</cell></row><row id="1"><cell>Jon Doe</cell><cell>43</cell></row></rows>";
parseXml(gPruebas, xmlSample);
*/
function parseXml(grid, xml) {
    if (xml != '') {
        try {
            var iXml = StringtoXML(xml);
            var rows = iXml.childNodes[0];
            var nRows = rows.childNodes.length;
            var nCells = rows.childNodes[0].childNodes.length;
            
            for (var i = 0; i < nRows; i++) {
                var valuesGrid = [];
                var row = rows.childNodes[i];

                for (var j = 0; j < nCells; j++) {
                    var cell = row.childNodes[j];
                    var val = cell.innerHTML;
                    valuesGrid.push(val);
                }

                addRow(grid, '', valuesGrid);
            }

            _onParseXml(grid, nRows);
        } catch (E) {
            console.error("Error al parsear XML", E);
        }
    }
}

/**
*Limpia la grid, dejándola sin filas
*@param {object} grid Objeto de la grid ya inicializada
*@example clearAll(gPruebas);
*/
function clearAll(grid) {
    grid.body.innerHTML = '';
    grid.rowsBuffer = '';
}

/**
*Indica si una columna está o no oculta
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} cInd Índice de la columna
*@returns {boolean} true si está oculta, false si no lo está
*/
function isColumHidden(grid, cInd) {
    if (grid.colsHidden[cInd] != null)
        return true;

    return false;
}

function validateGridWorker(grid, conf, fnCb, fnValRow, fnBeforeValRow) {
    worker = new Worker('../javascript/htmlGridWorker.js?v=1');

    worker.addEventListener('message', function (e) {
        var obj = e.data;

        if (obj.arrDatos != null) {
            worker.terminate();

            var colsV = getColsValidate(conf);
            var colsE = getColsEnlazadas(conf);
            var errors = { totalError: 0, numFilasV: 0 };

            var promesas = [];

            for (var x = 0; x < obj.arrDatos.length; x++) {
                promesas.push(validateRowPromesa(
                    grid, conf, obj.arrDatos[x].rId, fnBeforeValRow, colsV, colsE, errors)
                    .then(function (rId) {
                        if (fnValRow != null)
                            fnValRow.apply(grid, [rId, errors]);

                        if (!grid.pintarFilaOculta) {
                            for (var c in colsE) {
                                var row = getRow(grid, rId);
                                var cell = getCell(row, c);
                                var cellE = getCell(row, colsE[c]);

                                if (_$(cellE).hasClass('row-error'))
                                    _$(cell).addClass('row-error');
                            }
                        }
                    }, function (err) {
                        console.log(err);
                    })
                );
            }

            Promise.all(promesas)
                .then((r) => {
                    if (fnCb != null)
                        fnCb.apply(grid, [errors]);
                });
        }
    });

    var confC = getConfToWorker(conf);
    var idG = grid.tabla.id;
    var html = _$(grid.tabla).find('tbody').html();

    worker.postMessage({ 'html': html, 'id': idG, 'conf': confC, 'tipo': 'arrDatos' });
}

/**
*Valida la grid, según la configuración indicada.
*Se valida requerido, mínimo, máximo, expresión regular y valor de columna enlazada {@tutorial gridConf}
*@param {object} grid Objeto de la grid ya inicializada
*@param {object} conf Objeto de configuración de la grid
*@param {function} fnCb Función que se encargará de Obtener el resultado de la validación
* ésta función recibe un objeto que indica la cantidad de errores totales y los errores por columnas
*@param {function} fnValRow Función que se invoca después de pasar la validación por cada fila
* ésta función recibe el rowId
*@param {function} fnBeforeValRow Función que se invoca antes de pasar la validación por cada fila
* ésta función recibe el rowId
*@returns {object} Si no se envía función de callback, retorna el objeto con los errores 
*@example validateGrid(gPruebas, gPruebasConf, function(errors){
    if(errors.totalError > 0){
        console.log('Grid con errores');
        return;
    }
}, null, function(rId){
    var nombre = getCellValue(gPruebas, rId, gPruebasConf.Nombre.idx);
    if(nombre == 'Harri')
        gPruebasConf.Edad.req = false;
    else
        gPruebasConf.Edad.req = true;
});
*/
function validateGrid(grid, conf, fnCb, fnValRow, fnBeforeValRow) {
    var colsV = getColsValidate(conf);
    var colsE = getColsEnlazadas(conf);
    var errors = { totalError: 0, gridId: grid.tabla.id};

    //Validación rápida grids pesadas
    if (grid.liteValidate) {
        errors = liteValidateGrid(grid, colsV, errors);

        if (fnCb != null) {
            fnCb.apply(grid, [errors]);
            return;
        }else {
            return errors;
        }
    }

    foreachRow(grid, function (rId) {
        validateRow(grid, conf, rId, fnBeforeValRow, colsV, colsE, errors);

        if (fnValRow != null)
            fnValRow.apply(grid, [rId, errors]);

        //No pintar la fila completa, pintar la celda enlazada
        if (!grid.pintarFilaOculta) {
            for (var c in colsE) {
                var row = getRow(grid, rId);
                var cell = getCell(row, c);
                var cellE = getCell(row, colsE[c]);

                if (_$(cellE).hasClass('row-error'))
                    _$(cell).addClass('row-error');
            }
        }
    });

    if (fnCb != null)
        fnCb.apply(grid, [errors]);
    else
        return errors;
}

function validateRow(grid, conf, rId, fnBeforeValRow, colsV, colsE, errors) {
    var row = getRow(grid, rId);
    _$(row).removeClass('row-error');
    var errorRow = false;

    if (fnBeforeValRow != null) {
        fnBeforeValRow.apply(grid, [rId, errors]);
        colsV = getColsValidate(conf);
    }

    foreachCell(grid, rId, function (cell, cInd) {
        _$(cell).removeClass('row-error');

        if (colsV[cInd] != null) {
            var objCol = colsV[cInd];
            var val = cell.dataset.value;
            var labelCol = getHeaderLabel(grid, cInd);
            var errorCelda = false;

            if (objCol.req && val == '')
                errorCelda = true;

            if (objCol.min != null && val.length < objCol.min)
                errorCelda = true;

            if (objCol.max != null && val.length > objCol.max)
                errorCelda = true;

            if (objCol.regExp != null && val != '' && !objCol.regExp.test(val))
                errorCelda = true;

            if (objCol.colEnlazada != null && objCol.noValM == null && val != '') {
                var valE = getCellValue(grid, rId, objCol.colEnlazada);
                if (valE == '')
                    errorCelda = true;
            }

            if (errorCelda) {
                if (grid.pintarFilaOculta && grid.colsHidden[_$(cell).index()] != null) {
                    _$(row).addClass('row-error');
                    removeAllCellError(grid, rId);
                    errorRow = true;
                } else if (!errorRow) {
                    _$(cell).addClass('row-error');
                }

                if (errors[labelCol] == null)
                    errors[labelCol] = 0;

                errors[labelCol]++;
                errors.totalError++;
            } else {
                _$(cell).removeClass('row-error');
            }
        }
    });
}

/**
*Marca una celda con error
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {integer} cInd Índice de la columna
*/
function addCellError(grid, rId, cInd) {
    addRemoveCellError(grid, rId, cInd, true);
}

/**
*Quita el error a una celda
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {integer} cInd Índice de la columna
*/
function removeCellError(grid, rId, cInd) {
    addRemoveCellError(grid, rId, cInd, false);
}

function addRemoveCellError(grid, rId, cInd, add) {
    var row = getRow(grid, rId);
    var cell = getCell(row, cInd);

    if(add)
        _$(cell).addClass('row-error');
    else
        _$(cell).removeClass('row-error');
}

/**
*Quita el error a todas las celda
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*/
function removeAllCellError(grid, rId) {
    foreachCell(grid, rId, function (cell, cInd) {
        _$(cell).removeClass('row-error');
    });

    //v2
    //_$('tr[data-rid="{0}"] td'.format(rId)).removeClass('row-error');
}

function getColsValidate(conf) {
    var colsV = {};

    for (var k in conf) {
        if (conf[k].req || conf[k].min || conf[k].max || conf[k].regExp || conf[k].colEnlazada)
            colsV[conf[k].idx] = conf[k];
    }

    return colsV;
}

function getColsEnlazadas(conf) {
    var colsE = {};

    for (var k in conf) {
        if (conf[k].colEnlazada)
            colsE[conf[k].idx] = conf[k].colEnlazada;
    }

    return colsE;
}

function getObjColIdx(idx, gConf) {
    for (var c in gConf) {
        if (gConf[c].idx == idx) {
            return gConf[c];
        }
    }

    return null;
}

/**
*Busca dentro de la grid un valor especificado
*@param {object} grid Objeto de la grid ya inicializada
*@param {integer} cInd Índice de la columna
*@param {string} val valor a buscar
*@param {boolean} first true si desea devolver solo la primera fila que encuentra
*@returns {Array} contiene objetos con el rowId y los índices de las celdas que coinciden
*@example var arrRIds = find(gPruebas, gPruebasConf.Apellido.idx, 'Perez', true);
if(arrRIds.length > 0){
    var nombre = getCellValue(gPruebas, arrRIds[0].rId, gPruebasConf.Nombre.idx);
    console.log(nombre);
}
*/
function find(grid, cInd, val, first) {
    var rFiles = [];

    if (grid.body != null) {
        var cell = _$(grid.body).find("td[data-value='{0}']".format(val));
        if (cell.length > 0) {
            for (var i = 0; i < cell.length; i++) {
                if (cInd == null || cInd == getCellIndex(cell[i])) {
                    var row = _$(cell[i]).parent('tr')[0];
                    rFiles.push({ 'rId': getRowId(row), 'cInd': getCellIndex(cell[i]) });

                    if (first)
                        return rFiles;
                }
            }
        }
    }

    return rFiles;
}

/**
*Busca dentro de la grid combinaciones de valores
*@param {object} grid Objeto de la grid ya inicializada
*@param {Array} arrCInd Índices de las columnas donde se va a buscar
*@param {Array} arrVal valores a buscar, deben corresponder a las columnas y estar en el mismo orden
*@param {boolean} first true si desea devolver solo la primera fila que encuentra
*@returns {Array} contiene objetos con el rowId y los índices de las celdas que coinciden
*@example var arrRIds = findMulti(gPruebas, [gPruebasConf.Nombre.idx, gPruebasConf.Apellido.idx], ['Juan', 'Perez'], true);
if(arrRIds.length > 0){
    var edad = getCellValue(gPruebas, arrRIds[0].rId, gPruebasConf.Edad.idx);
    console.log(edad);
}
*/
function findMulti(grid, arrCInd, arrVal, first) {
    var rFiles = [];

    if (grid.body != null) {
        var cell = _$(grid.body).find("td[data-value='{0}']".format(arrVal[0]));
        if (cell.length > 0) {
            //Buscar sobre las filas encontradas con el primer valor los demás valores
            for (var i = 0; i < cell.length; i++) {
                if (arrCInd[0] == getCellIndex(cell[i])) {
                    var arrValFind = [];
                    arrValFind.push(cell[i].dataset.value);
                    var row = _$(cell[i]).parent('tr')[0];

                    for (var c = 1; c < arrVal.length; c++) {
                        var cellA = _$(row).find("td[data-value='{0}']".format(arrVal[c]));

                        if (cellA.length > 0) {
                            for (var x = 0; x < cellA.length; x++) {
                                if(arrCInd[c] == getCellIndex(cellA[x]))
                                    arrValFind.push(cellA[0].dataset.value);
                            }
                        }                            
                    }

                    if (arrValFind.join('') == arrVal.join(''))
                        rFiles.push({ 'rId': getRowId(row), 'cInd': getCellIndex(cell[i]) });

                    if (first && rFiles.length > 0)
                        return rFiles;
                }
            }
        }
    }

    return rFiles;
}

/**
*Agrea formato numérico a columnas en la grid
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} mask Máscara usada para el formateo
*@param {integer} cInd Índice de la columna
*@param {string} p_sep Separador de miles
*@param {string} d_sep Separador de decimales
*@example setNumberFormat(gPruebas, '$ 000,000', gPruebasConf.Salario.idx, '.', ',');
*/
function setNumberFormat(grid, mask, cInd, p_sep, d_sep) {
    var nmask = mask.replace(/[^0\,\.]*/g, "");
    var pfix = nmask.indexOf(".");

    if (pfix > -1)
        pfix = nmask.length - pfix - 1;
    var dfix = nmask.indexOf(",");

    if (dfix > -1)
        dfix = nmask.length - pfix - 2 - dfix;

    if (typeof p_sep != "string")
        p_sep = '.';
    if (typeof d_sep != "string")
        d_sep = ',';

    var pref = mask.split(nmask)[0];
    var postf = mask.split(nmask)[1];
    grid.colsFormat[cInd] = [
        pfix,
        dfix,
        pref,
        postf,
        p_sep,
        d_sep
    ];
}


//Funciones Internas
function _aplNFb(grid, data, ind) {
    var a = grid.colsFormat[ind];

    if (!a)
        return data;

    var ndata = parseFloat(data.toString().replace(/[^0-9]*/g, ""));

    if (data.toString().substr(0, 1) == "-")
        ndata = ndata * -1;

    if (a[0] > 0)
        ndata = ndata / Math.pow(10, a[0]);
    return ndata;
}

function _aplNF(grid, data, ind) {
    if (isNaN(parseFloat(data)))
        data = 0;

    var a = grid.colsFormat[ind];
		
    if (!a)
        return data;
		
    var c = (parseFloat(data) < 0 ? "-" : "")+a[2];
    data=Math.abs(Math.round(parseFloat(data)*Math.pow(10, a[0] > 0 ? a[0] : 0))).toString();
    data=(data.length
        < a[0]
        ? Math.pow(10, a[0]+1-data.length).toString().substr(1, a[0]+1)+data.toString()
        : data).split("").reverse();
    data[a[0]]=(data[a[0]]||"0")+a[4];
			
    if (a[1] > 0)
        for (var j = (a[0] > 0 ? 0 : 1)+a[0]+a[1]; j < data.length; j+=a[1])data[j]+=a[5];
    return c+data.reverse().join("")+a[3];
}

function _hideColumnsRow(grid, rId) {
    foreachCell(grid, rId, function (cell, cInd) {
        if (isColumHidden(grid, cInd))
            hideColumn(grid, cInd);
    });
}

function _addRowIdAndValueFromTable(grid, conf) {
    var nRows = getRowsNum(grid);
    var rows = grid.body.rows;
    var nCells = rows[0].cells.length;

    for (var i = 0; i < nRows; i++) {
        setRowId(rows[i], getNewRowId());

        if (nRows <= maxRowsValidate && conf != null)
            _normalizeGrid(grid, conf, nCells, i);

        for (var j = 0; j < nCells; j++) {
            var cell = getCell(rows[i], j);
            var valHtml = cell.innerHTML;

            if (valHtml.indexOf('&nbsp;') != -1) {
                valHtml = valHtml.replace(/&nbsp;/g, '');
                cell.innerHTML = valHtml;
            }

            cell.dataset.value = valHtml;
        }
    }
}

function _normalizeGrid(grid, conf, nCells, idxRow, rId) {
    var colsHeader = grid.header.rows[0].cells.length;

    if (nCells < colsHeader) {
        var numColsF = colsHeader - nCells;

        for (var i = 0; i < numColsF; i++) {
            var colIdx = nCells;
            var objCol = getObjColIdx(colIdx, conf);
            var label = getHeaderLabel(grid, colIdx);
            var vDef = (objCol != null && objCol.vDef != null) ? objCol.vDef : '';

            var td = '<td data-value="{0}" data-collapsible-title="{1}">{0}</td>'.format(vDef, label);
            _$(grid.body.rows[idxRow]).append(td);

            if (objCol != null && objCol.eventOpc != null) {
                grid.addEventOpt = true;
                addEventsOpt(grid, grid.body.rows[idxRow]);
                grid.addEventOpt = false;
            }
        }
    }
}

function _addRowIdFromTable(grid) {
    var nRows = getRowsNum(grid);
    var rows = grid.body.rows;

    for (var i = 0; i < nRows; i++) {
        setRowId(rows[i], (getNewRowId() + i));
    }
}

function _addAllDataCollapsableTitle(grid) {
    foreachRow(grid, function (rId) {
        foreachCell(grid, rId, function (cell) {
            _addDataCollapsableTitle(grid, cell);
        });
    });
}

function _addRowCellDataCollapsableTitle(grid, rId) {
    foreachCell(grid, rId, function (cell) {
        _addDataCollapsableTitle(grid, cell);
    });
}

function _addDataCollapsableTitle(grid, cell) {
    var cInd = getCellIndex(cell);
    var hCell = getHeaderCell(grid, cInd);
    
    if (hCell != null)
        cell.dataset.collapsibleTitle = hCell.innerHTML;
}

function _addValueFromTable(grid) {
    foreachRow(grid, function (rId) {
        foreachCell(grid, rId, function (cell) {
            var valHtml = cell.innerHTML;
            if (valHtml.indexOf('&nbsp;') != -1) {
                valHtml = valHtml.replace(/&nbsp;/g, '');
                cell.innerHTML = valHtml;
            }

            var val = cell.dataset.value;
            cell.dataset.value = valHtml;
        });
    });
}

function _addEventRows(grid, evento, fn) {
    var nRows = getRowsNum(grid);
    var rows = grid.body.rows;

    for (var i = 0; i < nRows; i++) {
        _addEventRow(rows[i], evento, fn);
    }
}

function _addEventRow(row, evento, fn) {
    _$(row).on(evento, fn);
}

function _removeSelectedAll(grid) {
    _$(grid.body).find('.table-active').removeClass('table-active')
}

function _getNewRow(grid, values) { 
    var row = document.createElement('tr');
    var nCells = getCellsNum(grid);

    for (var i = 0; i < nCells; i++) {
        var cell = document.createElement('td');
        var val = (values[i] != null) ? values[i] : '';
        setCell2Value(cell, val, grid, i);
        _$(row).append(cell);
    }

    return row;
}

function _getNewRowStr(grid, values, rId) {
    var row = '<tr data-rid="{0}">{1}</tr>';
    var arrCell = [];
    var nCells = getCellsNum(grid);

    for (var i = 0; i < nCells; i++) {
        var cell = '<td data-value="{0}" data-collapsible-title="{1}" {3}>{2}</td>';
        var val = (values[i] != null) ? values[i] : '';

        try {
            if (val != null || typeof val == 'string')
                val = val.replace(/"/g, "'");
        } catch (E) {}

        cell = setCell2ValueStr(cell, val, grid, i);
        arrCell.push(cell);
    }

    return row.format(rId, arrCell.join(''));
}

//Eventos
/**
*Agrega un evento específico a la grid
*@param {string} event Nombre del evento que se desea agrear a la grid {@tutorial gridEvent}
*@param {object} grid Objeto de la grid ya inicializada
*@param {function} fnCb Función que manejará el evento,
* todos los eventos retornan el rowId, excepto onRowDelete
*@example addEventGrid('onRowAdded', gPruebas, function(rId){
    setCellValue(gPruebas, rId, gPruebasConf.Estatus.idx, 'Nuevo');
});
*/
function addEventGrid(event, grid, fnCb) {
    if (grid[event] == null)
        grid[event] = [];

    grid[event].push(fnCb);
}

function _onRowAdded(grid, rId) {
    _callEvent(grid, rId, 'onRowAdded');
}

function _onRowEdit(grid, rId) {
    _callEvent(grid, rId, 'onRowEdit');
}

function _onRowDelete(grid) {
    _callEvent(grid, null, 'onRowDelete');
}

function _onBeforeRowDelete(grid, rId) {
    _callEvent(grid, rId, 'onBeforeRowDelete');
}

function _onBeforeOpenPopup(grid, rId) {
    _callEvent(grid, rId, 'onBeforeOpenPopup');
}

function _onOpenPopup(grid, rId) {
    _callEvent(grid, rId, 'onOpenPopup');
}

function _onRowSelected(grid, rId) {
    _callEvent(grid, rId, 'onRowSelected');
}

function _onParseXml(grid, nRows) {
    if (grid['onParseXml'] != null)
        grid['onParseXml'].apply(grid, [nRows]);
}

function _callEvent(grid, rId, event) {
    if (grid[event] != null && grid[event].length > 0) {
        try {
            for (var i = 0; i < grid[event].length; i++) {
                if (rId != null)
                    grid[event][i].apply(grid, [rId]);
                else
                    grid[event][i].apply(grid);
            }
        } catch (E) {
            console.error('Error en el evento ' + event, grid.tabla.id, E);
        }
    }
}

function _addSelectRow(grid) {
    _$(grid.body.rows).on('click', function (e) {
        _removeSelectedAll(grid);
        selectRow(this);
    });
}

function StringtoXML(text) {
    if (window.ActiveXObject) {
        var doc = new ActiveXObject('Microsoft.XMLDOM');
        doc.async = 'false';
        doc.loadXML(text);
    } else {
        var parser = new DOMParser();
        var doc = parser.parseFromString(text, 'text/xml');
    }
    return doc;
}

//Popups
/**
*Guarda los datos del Popup en la grid a partir del event
*@param {event} e Evento que disparan las opciones de agregar, editar y eliminar
*/
function guardarDatosPopup(e) {
    var dvPadre = _$(e.target).parents('.popup');

    if (_$(dvPadre).hasClass('noGrid'))
        return;

    guardarPopup(dvPadre);
}

/**
*Guarda los datos del Popup en la grid a partir del div popup
*@param {object} dvPadre Objeto jQuery con el div popup
*@example guardarPopup(_$('#_gPruebas'));
*/
function guardarPopup(dvPadre) {
    if (dvPadre.length > 0) {
        var idGrid = dvPadre[0].id.replace('_', '');
        var rId = dvPadre[0].dataset.rid;
        var grid = objGrids[idGrid].grid;
        var confGrid = objGrids[idGrid].conf;

        var datos = getDatosPopup(confGrid);

        if ((rId == null || rId == '') && (getRowsNum(grid) == 1)) {
            rId = getRowIdFromIndex(grid, 0);
            if (dataInRow(grid, rId))
                rId = null;
        }

        if (rId != null && rId != '') {
            setDataRow(grid, rId, datos);
        } else if (agregoDatosPopup(confGrid)) {
            addRow(grid, '', datos);
        }

        if (!_$(dvPadre).hasClass('noClosePopup')) {
            app.popup.close('#_' + idGrid);
            dvPadre[0].dataset.rid = '';
        }
    }
}

/**
*Obtiene el arreglo con los datos ingresados en el popup
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@param {boolean} forceValidate Indica si se desea forzar la validación de los campos en el modal
*@example var data = getDatosPopup(gDatosConf, false);
*/
function getDatosPopup(conf, forceValidate) {
    var datos = [];

    for (var e in conf) {
        var idE = conf[e].id;

        if (idE == null) {
            continue;
            console.log('idConf Null ' + JSON.stringify(conf[e]));
        }

        if (idE != '') {
            if (isFieldType(idE.toUpperCase(), ['R', '_R', 'C', '_C'])) {
                datos[conf[e].idx] = _$('input[name="' + idE + '"]:checked').val();
            } else {
                if (_$('#' + idE).prop('multiple') != null && _$('#' + idE).prop('multiple'))
                    datos[conf[e].idx] = _$('#' + idE).val().join(',');
                else
                    datos[conf[e].idx] = _$('#' + idE).val();
            }
        } else if (conf[e].vDef) {
            datos[conf[e].idx] = conf[e].vDef;
        }

        if (forceValidate != null && forceValidate) {
            if (_$('#' + idE).val() == '')
                app.input.validate('#' + idE);
        }
    }

    return datos;
}

/**
*Indica si en el popup ingresaron datos
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@returns {boolean} true si se agregó algún valor, false si no
*@example if(!agregoDatosPopup(gPruebasConf)){
    console.log('No agregó ningún valor en el popup');
}
*/
function agregoDatosPopup(conf) {
    var datos = [];

    for (var e in conf) {
        var idE = conf[e].id;

        if (idE != '')
            datos[conf[e].idx] = _$('#' + idE).val();
    }

    if (datos.join('') == '')
        return false;

    return true;
}

/**
*Obtiene los datos de una fila específica
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@returns {Array} Datos de la fila
*@example var rId = getSelectedRowId(gPruebas);
var data = getDatosCellGrid(gPruebas, rId, gPruebasConf);
console.log(data);
*/
function getDatosCellGrid(grid, rId, conf) {
    var datos = [];

    for (var e in conf) {
        var val = getCellValue(grid, rId, conf[e].idx);
        var idE = conf[e].id;

        if (idE != '')
            datos[conf[e].idx] = val;
    }

    return datos;
}

function reverseValueXML(val) {
    return val.replace(/&amp;/g, '&');
}

/**
*Carga los datos de una fila de la grid en el popup
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@example var rId = getSelectedRowId(gPruebas);
cargarDatosPopup(gPruebas, rId, gPruebasConf);
*/
function cargarDatosPopup(grid, rId, conf) {
    for (var e in conf) {
        var valCell = getCellValue(grid, rId, conf[e].idx);
        valCell = reverseValueXML(valCell);
        var idE = conf[e].id;

        if (conf[e].vDef != null && valCell == '')
            valCell = conf[e].vDef;

        if (idE != null && idE != '') {
            if (isFieldType(idE.toUpperCase(), ['R', '_R', 'C', '_C'])) {
                _$('input[name="' + idE + '"]').prop('checked', false);
                _$('input[name="' + idE + '"][value="' + valCell + '"]').prop('checked', true);
            } else if (isFieldType(idE.toUpperCase(), ['S', '_S'])) {
                if (_$('#' + idE).prop('multiple') != null && _$('#' + idE).prop('multiple')) {
                    if (valCell != '')
                        _$('#' + idE).val(valCell.split(','));
                    else
                        _$('#' + idE).val([]);
                } else {
                    _$('#' + idE).val(valCell);
                }

                var s = app.smartSelect.get('.' + idE);
                if (s != null) {
                    var txt = _$('#' + idE).find("option:selected").text();
                    s.setValue(txt);
                }
            } else {
                _$('#' + idE).val(valCell);
            }
        }
    }
}

/**
*Limpia el popup
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@example limpiarPopup(gPruebasConf);
*/
function limpiarPopup(conf) {
    for (var e in conf) {
        var idE = conf[e].id;

        if (idE == '')
            continue;

        if (isFieldType(idE.toUpperCase(), ['R', '_R', 'C', '_C'])) {
            _$('input[name="' + idE + '"]').prop('checked', false);
        } else if (isFieldType(idE.toUpperCase(), ['S', '_S'])) {
            var s = app.smartSelect.get('.' + idE);
            if (s != null)
                s.setValue('');

            _$('#' + idE).val('');
            _$('#' + idE).find('option').prop('selected', false);
        }
        else {
            _$('#' + idE).val('');
        }
    }
}

/**
*Traslada los errores de una fila al popup
*@param {object} grid Objeto de la grid ya inicializada
*@param {string} rId rowId único que identifica a cada fila
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@example var rId = getSelectedRowId(gPruebas);
addErrorPopup(gPruebas, rId, gPruebasConf);
*/
function addErrorPopup(grid, rId, conf) {
    cleanErrorPopup(conf);
    
    if (grid.valPopup != null && grid.valPopup) {
        var row = getRow(grid, rId);
        var cellError = _$(row).find('.row-error');

        for (var r = 0; r < cellError.length; r++) {
            var cInd = getCellIndex(cellError[r]);
            var obj = getObjColIdx(cInd, conf);

            if (obj != null) {
                var dvPadre = getDvPadrePopup(obj.id);
                if(dvPadre != null)
                    _$(dvPadre).addClass('has-error');
            }
        }
    }
}

/**
*Limpia los errores marcados en el popup
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@example cleanErrorPopup(gPruebasConf);
*/
function cleanErrorPopup(conf) {
    for (var e in conf) {
        var idE = conf[e].id;

        if (idE == null) {
            continue;
            console.log('idConf Null ' + JSON.stringify(conf[e]));
        }

        if (idE != '') {
            var dvPadre = getDvPadrePopup(idE);
            if (dvPadre != null)
                _$(dvPadre).removeClass('has-error');
        }
    }
}

/**
*Obtiene el Div padre de un campo en el modal
*@param {string} id id de la grid
*@returns {object} Div jQuery que contiene el campo
*@example var dvPadre = getDvPadrePopup("t2");
*/
function getDvPadrePopup(id) {
    if (id == '')
        return null;

    var dvPadre = _$('#' + id).parents('.col-100')[0];

    if (isFieldType(id.toUpperCase(), ['R', '_R', 'C', '_C'])) {
        dvPadre = _$('input[name="' + id + '"]').parents('.col-100')[0];

        if (dvPadre == null)
            dvPadre = _$('input[name="' + id + '"]').parents('.list')[0];

        return dvPadre;
    }
    
    if (dvPadre == null)
        dvPadre = _$('#' + id).parents('.list')[0];

    return dvPadre;
}

/**
*Abre el popup de la grid y cargar los valores, se puede hacer solo con el event, o enviando el row y la acción
*@param {event} e (opcional) evento que generan las opciones de agregar, editar y eliminar
*@param {HTMLTableRowElement} row (opcional) tr de la tabla
*@param {string} tipo (opcional) puede ser add, edit, delete
*@example var rId = getSelectedRowId(gPruebas);
var row = getRow(gPruebas, rId);
verModalF7(null, row, 'edit');
*/
function verModalF7(e, row, tipo) {
    var tipo = (tipo == null) ? _$(e.target)[0].dataset.tipo : tipo;
    var tb = (row == null) ? _$(e.target).parents('table')[0] : _$(row).parents('table')[0];
    var rId = (row == null) ? getRowIdFromCell(_$(e.target).parents('td')[0]) : getRowId(row);
    var grid = objGrids[tb.id].grid;
    var conf = objGrids[tb.id].conf;

    if (_isRowLocked(row))
        return;

    if (tipo == 'add') {
        limpiarPopup(conf);
        verOcultarCamposPopup(grid, conf);
        _onBeforeOpenPopup(grid, '');
        app.popup.open('#_' + tb.id);
        _onOpenPopup(grid, '');
    } else if (tipo == 'edit') {
        _$('#_' + tb.id)[0].dataset.rid = rId;
        verOcultarCamposPopup(grid, conf);
        addErrorPopup(grid, rId, conf);
        _onBeforeOpenPopup(grid, rId);
        app.popup.open('#_' + tb.id);

        if (getRowsNum(grid) > maxRowsValidate) {
            setTimeout(function () {
                cargarDatosPopup(grid, rId, conf);
                _onOpenPopup(grid, rId);
            }, 200);
        } else {
            cargarDatosPopup(grid, rId, conf);
            _onOpenPopup(grid, rId);
        }
    } else if (tipo == 'delete') {
        if (dataInRow(grid, rId)) {
            app.dialog.confirm('Está seguro de eliminar la fila?', function () {
                _removeRow(grid, rId);
            });
        } else {
            if (getRowsNum(grid) > 1)
                _removeRow(grid, rId);
        }
    }
}

function verModal2F7(rId, tipo, idPopup, grid, conf) {
    if (tipo == 'add') {
        limpiarPopup(conf);
        verOcultarCamposPopup(conf);
        app.popup.open('#' + idPopup);
        _onOpenPopup(grid, '');
    } else if (tipo == 'edit') {
        _$('#' + idPopup)[0].dataset.rid = rId;
        verOcultarCamposPopup(conf);
        addErrorPopup(grid, rId, conf);
        cargarDatosPopup(grid, rId, conf);
        app.popup.open('#' + idPopup);
        _onOpenPopup(grid, rId);
    } else if (tipo == 'delete') {
        if (dataInRow(grid, rId)) {
            app.dialog.confirm('Está seguro de eliminar la fila?', function () {
                grid.deleteRow(rId);
            });
        } else {
            if (grid.getRowsNum() > 1)
                grid.deleteRow(rId);
        }
    }
}

function verModalOnClick(row) {
    try {
        var grid = getGridFromRow(row);
        if (grid != null && grid.editOnClick)
            verModalF7(null, row, 'edit');
    } catch (E) {
        console.error(E);
    }
}

/**
*Muestra u oculta campos en el popup basado en la configuración
*@param {object} grid Objeto de la grid ya inicializada
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@example verOcultarCamposPopup(gPruebas, gPruebasConf);
*/
function verOcultarCamposPopup(grid, conf) {
    for (var c in conf) {
        if (conf[c].verModal != null) {
            var dvPadre = getDvPadrePopup(conf[c].id);

            if (dvPadre != null) {
                if (conf[c].verModal)
                    _$(dvPadre).removeClass('nb-hidden');
				else if(!conf[c].verModal)
                    _$(dvPadre).addClass('nb-hidden');
            }
        }
    }
}

/**
*Muestra todos los campos del popup
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@example verTodosCamposPopup(gPruebasConf);
*/
function verTodosCamposPopup(conf) {
    verOcultarTodosCamposPopup(conf, true);
}

/**
*Oculta todos los campos del popup
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@example ocultarTodosCamposPopup(gPruebasConf);
*/
function ocultarTodosCamposPopup(conf) {
    verOcultarTodosCamposPopup(conf, false);
}

function verOcultarTodosCamposPopup(conf, ver) {
    for (var c in conf) {
        if (conf[c].verModal != null) {
            if (conf[c].id == null) {
                continue;
                console.log('idConf Null ' + JSON.stringify(conf[e]));
            }

            var dvPadre = getDvPadrePopup(conf[c].id);

            if (dvPadre != null) {
                if (ver)
                    _$(dvPadre).removeClass('nb-hidden');
                else
                    _$(dvPadre).addClass('nb-hidden');
            }
        }
    }
}

/**
*Muestra u oculta en el popup los campos indicados
*@param {Array} arrIds Id de los campos que se desean mostrar u ocultar
*@param {boolean} ver true para mostrar, false para ocultar
*@example verOcultarPorCamposPopup([gPruebasConf.Apellido.id, gPruebasConf.Estado.id], false);
*/
function verOcultarPorCamposPopup(arrIds, ver) {
    for (var i = 0; i < arrIds.length; i++) {
        var dvPadre = getDvPadrePopup(arrIds[i]);

        if (dvPadre != null) {
            if (ver)
                _$(dvPadre).removeClass('nb-hidden');
            else
                _$(dvPadre).addClass('nb-hidden');
        }
    }
}

function _removeRow(grid, rId) {
    removeRow(grid, rId);

    if (getRowsNum(grid) < 1)
        addRow(grid, '', []);
}

function addMaxLength(conf) {
    for (var c in conf) {
        if (conf[c].max != null) {
            var idE = conf[c].id;

            if (idE != null && idE != '')
                _$('#' + idE).prop('maxlength', conf[c].max);
        }
    }
}

function addRowsBuffer(grid, rId, values) {
    if (rId == null || rId == '')
        rId = getNewRowId();

    var newRow = _getNewRowStr(grid, values, rId);
    if (grid.rowsBufferNum >= maxRowsValidate) {
        var idxTr = newRow.indexOf('>');
        newRow = newRow.substring(0, idxTr) + ' style="display:none"; ' + newRow.substring(idxTr);
    }

    grid.rowsBufferNum += 1;
    grid.rowsBuffer += newRow;
}

function parseRowsBuffer(grid) {
    if (grid.rowsBuffer != null && grid.rowsBuffer != '') {
        _$(grid.body).append(grid.rowsBuffer);
        _addSelectRow(grid);

        if (grid.rowsBufferNum > maxRowsValidate)
            _enabledPagination(grid, grid.rowsBufferNum);

        grid.rowsBuffer = '';
        grid.rowsBufferNum = 0;
    }
}

var worker = null;
function initWorker(grid) {
    worker = new Worker('../javascript/htmlGridWorker.js?v=2');

    worker.addEventListener('message', function (e) {
        var obj = e.data;

        if (obj.nFilas != null && obj.nFilas > 0 && obj.html != '') {
            if (dProgress.setText != null)
                dProgress.setText('Cargando los datos');

            setTimeout(function () {
                var idGrid = obj.id;
                var grid = objGrids[idGrid].grid;
                grid.body.innerHTML = obj.html;
                _addSelectRow(grid);

                worker.terminate();
                app.dialog.close();
            }, 100);
        }

        if (obj.avance != null) {
            var prog = obj.avance / obj.filas * 100;

            if (dProgress.setProgress != null) {
                dProgress.setProgress(prog);
                dProgress.setText('Procesando Filas {0}'.format(obj.avance));
            }
        }
    }, false);
}

function convertirHTML(grid, conf) {
    if (grid.tablaL != null) {
        var idG = grid.tabla.id;
        var html = _$(grid.tablaL).find('tbody').html();
        var nFilas = _$(grid.tablaL).find('tbody')[0].rows.length;

        _enabledPagination(grid, nFilas);
        dProgress = app.dialog.progress('Cargando {0} Filas'.format(nFilas), 0);

        var confC = getConfToWorker(conf);

        worker.postMessage({ 'html': html, 'id': idG, 'conf': confC, 'maxRows': maxRowsValidate });
        clearAll(grid);
    }
}

function getConfToWorker(confC) {
    var confC = confC;

    for (var c in confC) {
        if (confC[c].onkeyp != null)
            confC[c].onkeyp = null;
    }

    return confC;
}

function _validateAttrGrid(grid) {
    var rows = getRowsNum(grid);
    var cells = grid.body.rows[0].cells.length;
    var numCells = rows * cells;
    var arrCellsAttr = _$(grid.body).find('td[data-value]');

    if (numCells > arrCellsAttr.length) {
        console.log('Faltan atributos {0} {1}'.format(numCells, arrCellsAttr.length));
        _addRowIdAndValueFromTable(grid);
    }
}

/**
*Devuelve el objeto de la grid ya inicializada
*@param {string} idGrid id de la tabla HTML
*@returns {object} Objeto de la grid ya inicializada
*@example var gPruebas = getGridForId("gPruebas");
*/
function getGridForId(idGrid) {
    var grid = objGrids[idGrid].grid;
    return grid;
}

/**
*Devuelve el objeto de configuración de la grid
*@param {string} idGrid id de la tabla HTML
*@returns {object} Objeto de la configuración
*@example var gPruebasConf = getConfForId("gPruebas");
*/
function getConfForId(idGrid) {
    var confGrid = objGrids[idGrid].conf;
    return confGrid;
}

String.prototype.format = function () {
    var txt = this;
    for (var i = 0; i < arguments.length; i++) {
        var exp = new RegExp('\\{' + (i) + '\\}', 'gm');
        txt = txt.replace(exp, arguments[i]);
    }
    return txt;
}

function validateRowPromesa(grid, conf, rId, fnBeforeValRow, colsV, colsE, errors) {
    return new Promise(function (ok, err) {
        try {
            validateRow(grid, conf, rId, fnBeforeValRow, colsV, colsE, errors);
            ok(rId);
        } catch (E) {
            err(E);
        }
    });
}

function getGridArrDatosWorker(grid, conf, fnCb) {
    var workerG = new Worker('../javascript/htmlGridWorker.js?v=1');

    workerG.addEventListener('message', function (e) {
        var obj = e.data;

        if (obj.arrDatos != null) {
            workerG.terminate();
            fnCb.apply(grid, [obj, grid, conf]);
        }
    });

    var confC = getConfToWorker(conf);
    var idG = grid.tabla.id;
    var html = _$(grid.tabla).find('tbody').html();

    workerG.postMessage({ 'html': html, 'id': idG, 'conf': confC, 'tipo': 'arrDatos' });
}

function copiarContenidoModal() {
    if (_$('.modal-in').length > 0) {
        var idGrid = _$('.modal-in')[0].id.replace('_', '');
        var conf = getConfForId(idGrid);
        var grid = getGridForId(idGrid);

        copiarContenido(grid, conf);
    }
}

function copiarContenido(grid, conf) {
    if (conf != null && grid != null) {
        var datos = getDatosPopup(conf);

        foreachRow(grid, function (rId) {
            foreachCell(grid, rId, function (cell, cInd) {
                var obj = getObjColIdx(cInd, conf);
                var val = getCellValue(grid, rId, cInd);

                if (obj != null && obj.copiar != null && obj.copiar) {
                    setCellValue(grid, rId, cInd, datos[cInd]);
                }
            });
        });
    }
}

function cargarCamposCopiar() {
    if (_$('.modal-in').length > 0) {
        _$('#_sCopy').find('option').prop('selected', false);

        if (_$('#_sCopy')[0].options.length == 1) {
            var idGrid = _$('.modal-in')[0].id.replace('_', '');
            var conf = getConfForId(idGrid);
            var grid = getGridForId(idGrid);
            cargarComboCamposCopiar(grid, conf);
        }

        var sCopy = app.smartSelect.get('._sCopy');
        var evtClose = false;

        if (sCopy.eventsListeners.close != null && sCopy.eventsListeners.close.length > 0) {
            for (var e = 0; e < sCopy.eventsListeners.close.length; e++) {
                if (sCopy.eventsListeners.close[e].toString().indexOf('jsonConf') != -1) {
                    evtClose = true;
                    break;
                }
            }
        }

        if (!evtClose) {
            sCopy.on('close', function () {
                var jsonConf = _$('#_sCopy')[0].dataset.conf;
                var confN = JSON.parse(jsonConf);
                var arrVal = _$('#_sCopy').val();

                for (var c in confN) {
                    if (confN[c].copiar != null)
                        confN[c].copiar = null;
                }

                for (var i = 0; i < arrVal.length; i++) {
                    if (arrVal[i] != '') {
                        var obj = getObjColIdx(arrVal[i] * 1, confN);
                        if (obj != null) {
                            obj.copiar = true;

                            if (obj.colEnlazada != null) {
                                var objE = getObjColIdx(obj.colEnlazada, confN);
                                objE.copiar = true;
                            }
                        }
                    }
                }

                copiarContenido(grid, confN);
            });
        }

        sCopy.open();
    }
}

function cargarComboCamposCopiar(grid, conf) {
    _$('#_sCopy')[0].options.length = 1;

    var colE = -1;
    for (var c in conf) {
        if (conf[c].copiar != null) {
            if (conf[c].colEnlazada != null)
                colE = conf[c].colEnlazada;

            var lblCampo = getHeaderLabel(grid, conf[c].idx);
            var option = new Option(lblCampo, conf[c].idx);

            if (colE != -1 && conf[c].idx == colE)
                continue;

            _$('#_sCopy').append(option);
        }
    }

    _$('#_sCopy')[0].dataset.conf = JSON.stringify(conf);
}


function getJsonCells(grid, conf) {
    var objCells = [];

    foreachRow(grid, function (rId) {
        objCells.push(getJsonCell(grid, conf, rId));
    });

    return objCells;
}

/**
*Obtiene un JSON con los headers de las columnas y sus valores de una fila especificada
*@param {object} grid Objeto de la grid ya inicializada
*@param {object} conf Objeto de configuración de la grid, {@tutorial gridConf}
*@param {string} rId rowId único que identifica a cada fila
*@returns {object} Objeto con los datos de la grid
*@example var rId = getSelectedRowId(gPruebas);
var dataRow = getJsonCell(gPruebas, gPruebasConf, rId);
console.log(dataRow);
//{'Nombre': 'Pepito', 'Apellido': 'Perez', 'Edad': '25'}
*/
function getJsonCell(grid, conf, rId) {
    var objColumns = {};

    foreachCell(grid, rId, function (cell, cInd) {
        var objConfCell = getObjColIdx(cInd, conf);

        if (objConfCell != undefined) {
            if (objConfCell.colMap != undefined) {
                var val = cell.dataset.value;
                var header = objConfCell.colMap;
                objColumns[header] = val;
            }
        }
    });

    return objColumns;
}

/**
*Bloquea o desbloquea una fila
*@param {HTMLTableRowElement} row tr de la tabla
*@param {boolean} bloq true para bloquear, false para desbloquear
*@example var rId = getSelectedRowId(gPruebas);
var row = getRow(gPruebas, rId);
lockRow(row, true);
*/
function lockRow(row, bloq) {
    if (bloq)
        row.dataset.lock = STATUS_LOCK;
    else 
        row.dataset.lock = STATUS_UNLOCK;
}

function _isRowLocked(row) {
    if (row == undefined)
        return false;

    var status = row.dataset.lock;
    if (status == STATUS_LOCK)
        return true;
    else
        return false;
}

/**
*Habilita o deshabilita la validación rápida, solo valida requeridos
*@param {object} grid Objeto de la grid ya inicializada
*@param {boolean} enabled true para habilitar, false para deshabilitar
*@example enabledLiteValidation(gPruebas, true);
*/
function enabledLiteValidation(grid, enabled) {
    grid.liteValidate = enabled;
}

function liteValidateGrid(grid, colsV, errors) {
    _$(grid.body).find('.row-error').removeClass('row-error')

    _$(grid.body).find('td[data-value=""]').each(function () {
        var idxTd = _$(this).index();

        if (colsV[idxTd] != null && colsV[idxTd].req) {
            var labelCol = getHeaderLabel(grid, idxTd);
            if (errors[labelCol] == null)
                errors[labelCol] = 0;

            errors[labelCol]++;
            errors.totalError++;
            _$(this).addClass('row-error');
        }
    });

    return errors;
}

function _enabledPagination(grid, numRows) {
    grid.pagination = true;

    var numPagin = [10, 20, 30, 40, 50];
    var numPaginR = numPagin.reverse();
    var numPaginDef = 10;

    for (var i = 0; i < numPaginR.length; i++) {
        if (numRows / numPaginR[i] >= 3) {
            numPaginDef = numPaginR[i];
            break;
        }
    }

    //HTML para controles
    var cardTable = _$(grid.tabla).parents('.card');

    if (cardTable.find('.data-table-footer').length == 0)
        cardTable.append(getHTMLPagination());

    cardTable.find('.rows-page').val(numPaginDef);
    cardTable.find('.rows-page').on('change', function () {
        refreshNumPage(grid);
        cardTable.find('input[type="number"]').trigger('change');
    });

    cardTable.find('input[type="number"]').val(1);
    cardTable.find('input[type="number"]').on('change', function () {
        var page = parseInt(this.value);
        var numPages = calcNumPages(grid);

        if (page <= numPages)
            showGridPage(grid, page);
        else
            this.value = numPages;
    });

    refreshNumPage(grid);
    showGridPage(grid, 1);
}

function calcNumPages(grid) {
    var rowsPage = _$(grid.tabla).parents('.card').find('.rows-page').val();
    var numRows = getRowsNum(grid);

    return Math.ceil(numRows / rowsPage);
}

function refreshNumPage(grid) {
    var rowsPages = calcNumPages(grid);
    _$(grid.tabla).parents('.card').find('.curr-page span').html(rowsPages);
    _$(grid.tabla).parents('.card').find('input[type="number"]').attr('max', rowsPages);
}

function showGridPage(grid, page) {
    var cardTable = _$(grid.tabla).parents('.card');
    var rowsPage = cardTable.find('.rows-page').val() * 1;
    var rows = _$(grid.body).find('tr');
    rows.hide();

    var start = (page - 1) * rowsPage;
    var end = start + rowsPage;

    rows.slice(start, end).show();
}

function getHTMLPagination() {
    var htmlPag = '<div class="card-footer data-table-footer"><div class="data-table-rows-select">Filas por página:<div class="input input-dropdown input-with-value"><select class="input-with-value rows-page"><option value="10">10</option><option value="20">20</option><option value="30">30</option><option value="40">40</option><option value="50">50</option></select></div><div class="curr-page"><div>Página <input type="number" min="1" style="display:inline;width:50px" /> de <span></span></div></div></div></div>';
    return htmlPag;
}

function isFieldType(id, arrType) {
    for (var i = 0; i < arrType.length; i++) {
        if (id.startsWith(arrType[i])) {
            return true;
        }
    }
    return false;
}