codenautas/backend-plus

View on GitHub
for-client/my-tables.js

Summary

Maintainability
F
1 mo
Test Coverage
"use strict";

/*global myOwn, my, XLSX, Pikaday  */
/*global miniMenuPromise, dialogPromise, alertPromise, confirmPromise, promptPromise, simpleFormPromise */
/*global Blob, document, CustomEvent, URL  */

var whenMergeOverride=true;

var Big = require('big.js');
//var bestGlobals = require('best-globals');
var html = require('js-to-html').html;
var JSON4all = require('json4all');
var TypedControls = require('typed-controls');
var typeStore=require('type-store');
var likeAr=require('like-ar');

var changing = bestGlobals.changing;
var coalesce = bestGlobals.coalesce;

var MAX_SAFE_INTEGER = 9007199254740991;

/** @param { {[key:string]:any} } a */
/** @param { {[key:string]:any} } b */
function sameMembers(a,b){
    for(var attr in a){
        if(a[attr]!==b[attr]) return false;
    }
    for(var attr in b){
        if(a[attr]!==b[attr]) return false;
    }
    return true;
}

/** @param {any} a */
/** @param {any} b */
function sameValue(a,b){
    return a==b ||
      a instanceof Date && b instanceof Date && a.getTime() == b.getTime() ||
      typeof a === 'number' && (a>MAX_SAFE_INTEGER || a< -MAX_SAFE_INTEGER) && Math.abs(a/b)<1.00000000000001 && Math.abs(a/b)>0.99999999999999 ||
      a && !!a.sameValue && a.sameValue(b);
}

myOwn.i18n.messages.en=changing(myOwn.i18n.messages.en, {
    Delete : "Delete",
    Filter : "Filter",
    actualValueInDB: "actual value in database",
    allGWillDelete: "({$t} records will be deleted)",
    allRecordsDeleted: "all records where deleted",
    allTWillDelete: "(ALL {$t} records will be deleted)",
    anotherUserChangedTheRow: "The data was changed in the database",
    confirmDeleteAll: "Do you want to delete these records?",
    deleteAllRecords: "delete all records",
    deleteRecord: "delete record",
    details: "details",
    download: "download",
    distinctFrom:'distinct from',
    export: "export",
    empty:'empty',
    equalTo:'equal to',
    exhibitedColumns:'Exhibited Columns',
    filterAdd:'add line to filter (line OR line)',
    filteredCompleteTable:'filtered complete table',
    hiddenColumns:'Hidden columns',
    filter : "filter",
    filterOff: "filter off",
    format: "format",
    greaterThan:'greater than',
    greaterEqualThan:'greater igual than',
    hideOrShow: "hide or show colums",
    import: "import",
    importDataFromFile: "import data from external file",
    insertAbove: "insert record above this",
    insertRecordAtBottom: "insert a new record in the bottom of the table",
    lessThan:'less than',
    lessDetails: "hide details",
    lessEqualThan:'less equal than',
    loading: "loading",
    mandatoryFieldOmited:'mandatory fields omited: ',
    newUnsavedRow: "new unsaved row",
    notEmpty:'not empty',
    notSimilarTo:'not similar to',
    numberExportedRows:"Rows exported",
    oldValue: "old value",
    oneRowDeleted: "one row deleted.",
    xRowsDeleted: "{$x} rows deleted.",
    oneRowInserted: "one row inserted.",
    xRowsInserted: "{$x} rows inserted.",
    oneRowUpdated: "one row updated.",
    xRowsUpdated: "{$x} rows updated.",
    oneRowSkipped: "one skipped row",
    xRowsSkipped: "{$x} skipped row",
    optionsForThisTable: "options for this table",
    orientationToggle: "toggle orientation (vertical vs horizontal)",
    prepare: "prepare",
    preparingForExport: "preparing for export",
    recordsReimaining: "{$r} records remains in the table",
    refresh: "refresh - retrive data from database",
    replaceNewLineWithSpace: 'replace new lines with spaces',
    showInheritedKeys: "show inherited keys",
    similarTo:'similar to',
    simplificateSpaces:'simplificate spaces',
    skippedColumn:'skipped column',
    skippedColumns:'skipped columns',
    skipUnknownFieldsAtImport:'skip unknown fields at import',
    table: "table",
    thereAreNot: "no",
    uploadFile: "upload file $1",
    verticalEdit: "vertical edit",
    xOverTWillDelete: "({$x} over a total of {$t} records will be deleted)",
    zoom: "zoom",
});

myOwn.i18n.messages.es=changing(myOwn.i18n.messages.es, {
    Delete : "Eliminar",
    Filter : "Filtrar",
    actualValueInDB: "valor actual en la base de datos",
    allGWillDelete: "(se borrarán {$t} registros)",
    allRecordsDeleted: "todos los registros fueron borrados",
    allTWillDelete: "(se borrarán todos los registros: {$t} registros)",
    anotherUserChangedTheRow: "Hubo un cambio en la base de datos para este registro",
    confirmDeleteAll: "¿Desea borrar estos registros?",
    deleteAllRecords: "borrar todos los registros",
    deleteRecord: "borrar este registro",
    details: "detalles",
    distinctFrom: "distinto de",
    equalTo:'igual a',
    exhibitedColumns:'Columnas que se muestran',
    download: "descargar",
    empty:'vacío',
    export: "exportar",
    filter : "filtrar",
    filterOff: "desactiva el filtro (ver todos los registros)",
    filterAdd:'otro renglón en el filtro (se mostrarán renglones que cumplan alguna de las líneas del filtro)',
    filteredCompleteTable:'tabla completa y filtrada',
    format: "formato",
    greaterThan:'mayor que',
    greaterEqualThan:'mayor igual que',
    hiddenColumns:'Columnas ocultas',
    hideOrShow: "ocultar o mostrar columnas",
    import: "importar",
    importDataFromFile: "importar datos de un archivo externo",
    insertAbove: "agregar un registro encima de éste",
    insertRecordAtBottom: "insertar un registro nuevo en la tabla",
    lessThan:'menor que',
    lessDetails: "dejar de mostrar los detalles asocialdos al registro",
    lessEqualThan:'menor igual que',
    loading: "cargando",
    mandatoryFieldOmited:'faltan campos obligatorios: ',
    newUnsavedRow: "el nuevo registro (aun no grabado)",
    notEmpty:'no vacío',
    notSimilarTo:'no contiene',
    numberExportedRows:"Filas exportadas",
    oldValue: "valor anterior",
    oneRowDeleted: "un registro borrado",
    xRowsDeleted: "{$x} registros borrados",
    oneRowInserted: "un registro insertado.",
    xRowsInserted: "{$x} registros insertados.",
    oneRowUpdated: "un registro modificados.",
    xRowsUpdated: "{$x} registros modificados.",
    oneRowSkipped: "un registro salteado",
    xRowsSkipped: "{$x} registros salteados",
    optionsForThisTable: "opciones para esta tabla",
    orientationToggle: "cambiar la orientación de la tabla (por fila o por columna)",
    prepare: "preparar",
    preparingForExport: "preparando para exportar",
    recordsReimaining: "quedan {$r} registros en la tabla",
    refresh: "refrescar la grilla desde la base de datos",
    replaceNewLineWithSpace: 'replazar saltos de línea por espacios',
    showInheritedKeys: "mostrar las columnas relacionadas",
    similarTo:'parecido a',
    simplificateSpaces:'simplificar espacios',
    skippedColumn:'Columna salteada',
    skippedColumns:'Columnas salteadas',
    skipUnknownFieldsAtImport:'saltear columnas que no existan',
    table: "tabla",
    thereAreNot: "no hay",
    uploadFile: "subir el archivo $1",
    verticalEdit: "edición en forma de ficha",
    xOverTWillDelete: "(se borrarán {$x} registros sobre un total de {$t})",
    zoom: "zoom",
});

/** @param {string} text */
function regex4search(text){
    return new RegExp(
        text.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
        // .replace(/"/g,"\\b")
        .replace(/[ñÑ]/g,'(?:gn|nn?i?|[ñÑ])')
        .replace(/[cCçÇ]/g,'[cçÇ]')
        .replace(/[áÁàÀäÄãÃ]/gi,'[AáÁàÀäÄãÃ]')
        .replace(/[éÉèÈëË]/gi,'[EéÉèÈëË]')
        .replace(/[íÍìÌïÏ]/gi,'[IíÍìÌïÏ]')
        .replace(/[óÓòÒöÖõÕ]/gi,'[OóÓòÒöÖõÕ]')
        .replace(/[úÚùÙüÜ]/gi,'[UúÚùÙüÜ]')
        .replace(/a/gi,'[AáÁàÀäÄãÃ]')
        .replace(/e/gi,'[EéÉèÈëË]')
        .replace(/i/gi,'[IíÍìÌïÏ]')
        .replace(/o/gi,'[OóÓòÒöÖõÕ]')
        .replace(/u/gi,'[UúÚùÙüÜ]')
        // .replace(/\s+/g,'.*\\s+.*') mas estricto, exige el espacio
        .replace(/\s+/g,'.*')
    , 'i');
}

myOwn.firstDisplayCount = 20;
myOwn.firstDisplayOverLimit = 30;
myOwn.displayCountBreaks = [100,250,1000];
myOwn.displayCountBreaks = [50,100,500];
myOwn.comparatorWidth = 16;
myOwn.comparatorParameterNull={
    '\u2205':true,
    '!=\u2205':true,
}
myOwn.comparator={
    '=':function(valueToCheck,condition){return valueToCheck == condition;},
    '~':function(valueToCheck,condition){return condition==null || regex4search(condition.toString()).test(valueToCheck);},
    '!~':function(valueToCheck,condition){return condition==null || !regex4search(condition.toString()).test(valueToCheck);},
    '/R/i':function(valueToCheck,condition){return condition==null || RegExp(condition,'i').test(valueToCheck);},
    '\u2205':function(valueToCheck,condition){return valueToCheck == null;},//\u2205 = conjunto vacío
    '!=\u2205':function(valueToCheck,condition){return valueToCheck != null;},//\u2205 = conjunto vacío
    '>':function(valueToCheck,condition){return (valueToCheck>condition); },
    '>=':function(valueToCheck,condition){return (valueToCheck>=condition); },
    '<':function(valueToCheck,condition){return (valueToCheck<condition); },
    '<=':function(valueToCheck,condition){return (valueToCheck<=condition); },
    '!=':function(valueToCheck,condition){return valueToCheck != condition;},
    'not-an-operator':function(valueToCheck,condition){ return 'Operator does not exist'; },
    'traductor':{
        '=':'igual',
        '~':'parecido',
        '!~':'not-like',
        '/R/i':'expresion-regular',
        '\u2205':'vacio',
        '!=\u2205':'not-empty',
        '>':'mayor',
        '>=':'mayor-igual',
        '<':'menor',
        '<=':'menor-igual',
        '!=':'not-equal'
    }
};

myOwn.getStructureFromLocalDb = function getStructureFromLocalDb(tableName){
    return my.ldb.getStructure(tableName);
}

myOwn.getStructuresToRegisterInLdb = function getStructuresToRegisterInLdb(parentTableDef, structuresArray){
    var promiseChain = Promise.resolve();
    structuresArray.push(parentTableDef);
    if(parentTableDef.offline.mode==='master'){
        parentTableDef.offline.details.forEach(function(tableName){
            promiseChain = promiseChain.then(function(){
                var dummyElement = html.div().create();
                var Connector = my.TableConnector;
                var connector = new Connector({
                    my:my, 
                    tableName: tableName,
                    getElementToDisplayCount:function(){ return dummyElement }
                });
                connector.getStructure();
                return connector.whenStructureReady.then(function(tableDef){
                    return getStructuresToRegisterInLdb(tableDef, structuresArray)
                });
            });
        });
    }
    parentTableDef.foreignKeys.forEach(function(foreignKey){
        promiseChain = promiseChain.then(function(){
            var dummyElement = html.div().create();
            var Connector = my.TableConnector;
            var connector = new Connector({
                my:my, 
                tableName: foreignKey.references,
                getElementToDisplayCount:function(){ return dummyElement }
            });
            connector.getStructure();
            return connector.whenStructureReady.then(function(tableDef){
                structuresArray.push(tableDef);
            });
        });
    });
    promiseChain=promiseChain.then(function(){
        return structuresArray;
    })
    return promiseChain;
}

myOwn.skipInFixedFields = Symbol("skipInFixedFields");

myOwn.TableConnector = function(context, opts){
    var connector = this;
    for(var attr in context){
        connector[attr] = context[attr];
    }
    connector.opts = opts||{};
    connector.fixedFields = connector.opts.fixedFields || [];
    connector.fixedField = {};
    connector.pick = connector.opts.pick;
    connector.hideBecauseRelated = {};
    connector.fixedFields.forEach(function(pair){
        if(!pair.range && pair.value != myOwn.skipInFixedFields){
            connector.fixedField[pair.fieldName] = pair.value;
            if (!pair.show) {
                connector.hideBecauseRelated[pair.fieldName] = true;
            }
        }
    });
    connector.parameterFunctions=connector.opts.parameterFunctions||{};
};

myOwn.TableConnector.prototype.getStructure = function getStructure(opts){
    var opts = changing({
        registerInLocalDB: false, 
        waitForFreshStructure: false 
    }, opts || {});
    var connector = this;
    var my = connector.my;
    var getStructureFromBackend = function getStructureFromBackend(tableName){
        return my.ajax.table_structure({
            table:tableName,
        }).then(function(tableDef){
            connector.def = changing(tableDef, connector.opts.tableDef||{});
            return connector.def;
        });
    }
    var structureFromBackend = getStructureFromBackend(connector.tableName);
    if(my.ldb && opts.registerInLocalDB){
        var canContinue = structureFromBackend.then(function(tableDef){
            return my.getStructuresToRegisterInLdb(tableDef,[]).then(function(structuresToRegister){
                var promiseChain=Promise.resolve();
                structuresToRegister.forEach(function(structureToRegister){
                    promiseChain = promiseChain.then(function(){
                        return my.ldb.registerStructure(structureToRegister);
                    });
                });
                return promiseChain;
            });
        });
        if(!opts.waitForFreshStructure){
            canContinue = Promise.resolve();
        }
        connector.whenStructureReady = canContinue.then(function(){
            return my.getStructureFromLocalDb(connector.tableName);
        }).then(function(tableDef){
            if(!tableDef){ 
                return structureFromBackend;
            }
            connector.localDef = connector.def = changing(tableDef, connector.opts.tableDef||{});
            return connector.def;
        })
    }else{
        connector.whenStructureReady = structureFromBackend;
    }
    return connector.whenStructureReady;
};

myOwn.TableConnector.prototype.getData = function getData(){
    var connector = this;
    if(((connector.opts||{}).tableDef||{}).forInsertOnlyMode){
        return Promise.resolve([]);
    }
    return connector.my.ajax.table_data({
        table:connector.tableName,
        fixedFields:connector.fixedFields,
        paramfun:connector.parameterFunctions||{},
        pick:connector.pick
    }).then(function(rows){
        return connector.whenStructureReady.then(function(){
            if(connector.getElementToDisplayCount){
                connector.getElementToDisplayCount().textContent=rows.length+' '+my.messages.displaying+'...';
            }
            return bestGlobals.sleep(10);
        }).then(function(){
            connector.my.adaptData(connector.def, rows);
            return rows;
        });
    }).catch(function(err){
        if(connector.getElementToDisplayCount){
            var elementToDisplayError=connector.getElementToDisplayCount()
            if(elementToDisplayError){
                elementToDisplayError.appendChild(html.span({style:'color:red', title: err.message},' error').create());
            }
        }
        throw err;
    });
};

myOwn.TableConnector.prototype.deleteRecord = function deleteRecord(depot, opts){
    return (depot.primaryKeyValues===false?
        Promise.resolve():
        depot.my.ajax.table_record_delete({
            table:depot.def.name, 
            primaryKeyValues:depot.primaryKeyValues,
            launcher:opts.launcher
        }).then(function(){
            depot._isDeleted=true;
            depot.tr.dispatchEvent(new CustomEvent('deletedRowOk'));
            var grid=depot.manager;
            grid.dom.main.dispatchEvent(new CustomEvent('deletedRowOk'));
        })
    );
};

myOwn.cloneRow = function cloneRow(row){
    return JSON4all.parse(JSON4all.stringify(row));
};

myOwn.TableConnector.prototype.saveRecord = function saveRecord(depot, opts){
    var sendedForUpdate = depot.my.cloneRow(depot.rowPendingForUpdate);
    return depot.my.ajax.table_record_save({
        table: depot.def.name,
        primaryKeyValues: depot.primaryKeyValues,
        newRow: depot.rowPendingForUpdate,
        oldRow: depot.retrievedRow,
        status: depot.status
    },opts).then(function(result){
        var updatedRow=result.row;
        depot.my.adaptData(depot.def,[updatedRow]);
        return {sendedForUpdate:sendedForUpdate, updatedRow:updatedRow};
    });
};

myOwn.TableConnector.prototype.recordEnter = function recordEnter(depot){
    return (!this.my.config.cursor || depot.primaryKeyValues===false?
        Promise.resolve():
        depot.my.ajax.table_record_enter({
            table:depot.def.name, 
            primaryKeyValues:depot.primaryKeyValues
        })
    );
};
myOwn.TableConnector.prototype.recordLeave = function recordLeave(depot){
    return (!this.my.config.cursor || depot.primaryKeyValues===false?
        Promise.resolve():
        depot.my.ajax.table_record_leave({
            table:depot.def.name, 
            primaryKeyValues:depot.primaryKeyValues
        })
    );
};


myOwn.TableConnectorDirect = myOwn.TableConnector;

myOwn.TableConnectorLocal = function(context, opts){
    var connector = this;
    for(var attr in context){
        connector[attr] = context[attr];
    }
    connector.opts = opts||{};
    connector.fixedFields = connector.opts.fixedFields || [];
    connector.fixedField = {};
    connector.pick = connector.opts.pick;    
    connector.hideBecauseRelated = {};
    connector.fixedFields.forEach(function(pair){
        if(!pair.range && pair.value != myOwn.skipInFixedFields){
            connector.fixedField[pair.fieldName] = pair.value;
            if (!pair.show) {
                connector.hideBecauseRelated[pair.fieldName] = true;
            }
        }
    });
    connector.parameterFunctions=connector.opts.parameterFunctions||{};
};

myOwn.TableConnectorLocal.prototype.getStructure = function getStructure(){
    var connector = this;
    connector.whenStructureReady = my.getStructureFromLocalDb(connector.tableName).then(function(tableDef){
        if(!tableDef){ 
            var err = new Error();
            err.code='NO-STRUCTURE';
            throw err;
        }
        connector.def = changing(tableDef, connector.opts.tableDef||{});
        return connector.def;
    })
    return connector.whenStructureReady;
};

myOwn.TableConnectorLocal.prototype.getData = function getData(){
    var connector = this;
    if(((connector.opts||{}).tableDef||{}).forInsertOnlyMode){
        return Promise.resolve([]);
    }
    if(connector.parameterFunctions){
        for(var x in connector.parameterFunctions||{}){
            throw new Error('no soportado parameterFunctions');
        }
    }
    return connector.whenStructureReady.then(function(){
        var parentKey = [];
        var filterValues = connector.fixedFields.slice();
        var primaryKey = connector.def.primaryKey.slice();
        var indexOfKey;
        while(primaryKey.length && connector.fixedField[primaryKey[0]]){
            var key=primaryKey.shift();
            parentKey.push(connector.fixedField[key]);
        }
        return my.ldb.getChild(connector.def.name,parentKey).then(function(rows){
            var result = rows.filter(function(row){
                return !filterValues.find(function(pair){
                    return row[pair.fieldName]!=pair.value
                });
            });
            if(connector.def.sortColumns){
                return result.sort(function(a,b){
                    return bestGlobals.compareForOrder(connector.def.sortColumns)(a,b);
                })
            }
            /*
            if(connector.def.sortColumns || connector.def.sortColumns == null){
                var ordenator=bestGlobals.compareForOrder(connector.def.sortColumns || connector.def.primaryKey.map(function(k){ return {column:k}}));
                return result.sort(ordenator)
            }
            */
            return result;
        })
    }).then(function(rows){
        connector.getElementToDisplayCount().textContent=rows.length+' '+my.messages.displaying+'...';
        return bestGlobals.sleep(10).then(function(){
            connector.my.adaptData(connector.def, rows);
            return rows;
        });
    }).catch(function(err){
        connector.getElementToDisplayCount().appendChild(html.span({style:'color:red', title: err.message},' error').create());
        throw err;
    })
};

myOwn.TableConnectorLocal.prototype.deleteRecord = function deleteRecord(depot, opts){
    return (depot.primaryKeyValues===false?
        Promise.resolve():
        db[depot.def.name].delete(depot.primaryKeyValues).then(function(){
            depot._isDeleted=true;
            depot.tr.dispatchEvent(new CustomEvent('deletedRowOk'));
            var grid=depot.manager;
            grid.dom.main.dispatchEvent(new CustomEvent('deletedRowOk'));
        })
    );
};

myOwn.TableConnectorLocal.prototype.saveRecord = function saveRecord(depot, opts){
    var connector = this;
    var sendedForUpdate = depot.my.cloneRow(depot.rowPendingForUpdate);
    if(depot.row.$dirty){
        depot.row.$dirty=true;
    }
    return my.ldb.putOne(connector.tableName,depot.row).then(function(row){
        return {sendedForUpdate:sendedForUpdate, updatedRow:row};
    });
};

myOwn.TableConnectorLocal.prototype.recordEnter = function recordEnter(depot){
    return Promise.resolve();
};
myOwn.TableConnectorLocal.prototype.recordLeave = function recordLeave(depot){
    return Promise.resolve();
};

myOwn.TableGrid = function(context, mainElement){
    var grid = this;
    for(var attr in context){
        grid[attr] = context[attr];
    }
    grid.dom={
        main: mainElement,
        aggregate: {}
    };
    grid.modes = {
        saveByField: true,
        withColumnDetails: null, // null = autodetect
    };
    grid.view = {};
};

function upadteNumberOfRows(depot,grid){
    depot.manager.dom.footInfo.displayTo.textContent=grid.depotsToDisplay.length;
}

var TIME_STAMP_PROP = Symbol('TIME_STAMP_PROP');
myOwn.setTimeStamp = function setTimeStamp(row){
    var timeStamp = new Date().getTime();
    row[TIME_STAMP_PROP] = timeStamp;
    console.log('SET=', timeStamp, row[TIME_STAMP_PROP], JSON4all.toUrl(row))
    return timeStamp;
}

myOwn.skipTimeStamp = function skipTimeStamp(row, timeStamp){
    var skip = (row[TIME_STAMP_PROP] || 0) > timeStamp;
    if (!skip) row[TIME_STAMP_PROP] = timeStamp;
    return skip;
}

myOwn.tableGrid = function tableGrid(tableName, mainElement, opts){
    var my = this;
    var grid = new my.TableGrid({my: this}, mainElement);
    opts = opts || {};
    grid.detailingForUrl=opts.detailingForUrl;
    grid.detailingPath=opts.detailingPath;
    var Connector = my.offline.mode?my.TableConnectorLocal:my.TableConnector;
    grid.connector = new Connector({
        my:this, 
        tableName: tableName, 
        getElementToDisplayCount:function(){ return (grid.dom.footInfo||{}).displayTo||html.div().create(); }
    }, opts);
    var preparing = grid.prepareAndDisplayGrid().then(function(){
        if(opts.detailing){
            grid.depots.forEach(function(depot){
                var goIntoDetail=opts.detailing[(/^\w/.test(depot.lastsPrimaryKeyValues)?"":"=")+depot.lastsPrimaryKeyValues]||opts.detailing["*"];
                if(goIntoDetail){
                    likeAr(goIntoDetail).forEach(function(detailing, actionName){
                        depot.detailControls[actionName].displayDetailGrid({detailing:detailing});
                    })
                }
            })
        }
        grid.refreshAllRows = async function(force){
            var timeStamp = new Date().getTime();
            await grid.connector.my.ajax.table_data({
                table:grid.connector.tableName,
                fixedFields:grid.connector.fixedFields,
                paramfun:grid.connector.parameterFunctions||{},
                pick:grid.connector.pick
            }).then(function(rows){
                var primaryKey = grid.def.primaryKey;
                var getPrimaryKeyValues=function getPrimaryKeyValues(primaryKey, row){
                    return primaryKey.map(function(fieldName){
                        return row[fieldName]
                    })
                };
                var tick = Math.random();
                var thereIsANewRecord = grid.depots.filter(depot => depot.status == 'new').length;
                rows.forEach(function(row){
                    if (my.skipTimeStamp(row, timeStamp)) return;
                    var primaryKeyValuesForRow = getPrimaryKeyValues(primaryKey, row);
                    var depot = grid.depots.find(function(depot){
                        var primaryKeyValuesForDepotRow = getPrimaryKeyValues(primaryKey, depot.row);
                        return sameValue(JSON.stringify(primaryKeyValuesForRow),JSON.stringify(primaryKeyValuesForDepotRow))
                    });
                    if (!depot) {
                        var depot = grid.createDepotFromRow(row);
                        var needToAddInGrid = true;
                    }
                    var needToDisplayDepot = !!grid.filterDepots([depot])[0];
                    if (!needToDisplayDepot) return;
                    //chequeo que exista depot por las dudas
                    if ((!depot || !depot.tr || !depot.tr.parentNode) && !thereIsANewRecord && !grid.vertical) {
                        var depotsToDisplay = grid.filterDepots(grid.depots);
                        if (needToAddInGrid) {
                            var depot = grid.createDepotFromRow(row);
                            grid.depots.push(depot);
                            var depotsToDisplay = grid.filterDepots(grid.depots);
                            grid.sortDepotsToDisplay(depotsToDisplay);
                        } else if (depot.tr && !depot.tr.parentNode) {
                            depot.tr = null
                        }
                        var iRow = -1;
                        var i = 0;
                        while (i < depotsToDisplay.length){
                            var aDepot = depotsToDisplay[i];
                            if (depot == aDepot) break;
                            if (aDepot && aDepot.tr && aDepot.tr.parentNode && aDepot.tr.rowIndex != null) {
                                iRow = aDepot.tr.sectionRowIndex
                            }
                            i++;
                        }
                        grid.createRowElements(iRow + 1, depot);
                        grid.updateRowData(depot);
                        depot.tick = tick
                        if (!force) {
                            changeIoStatus(depot,'background-change', depot.row);
                            setTimeout(function(){
                                changeIoStatus(depot,'ok', depot.row);
                            },3000);
                        }
                    } else if (depot) { 
                        if (!sameValue(JSON.stringify(row),JSON.stringify(depot.row))) {
                            //grid.retrieveRowAndRefresh(depot); 
                            if(depot.tr){
                                grid.depotRefresh(depot,{updatedRow:row, sendedForUpdate:{}},{noDispatchEvents:true});
                            }
                        }
                        depot.tick = tick
                    }
                })
                if(!thereIsANewRecord){
                    var i = 0;
                    var depotsToDisplay = grid.filterDepots(grid.depots);
                    var depotsToDelete = depotsToDisplay.filter(depot => depot.tick != tick);
                    var depot;
                    if (myOwn.config.config['grid-row-retain-moved-or-deleted'] && !force) { 
                        var depotsToRetain = grid.depots.filter(depot => depot.tick == tick);
                        for (depot of depotsToRetain) {
                            depot['$refreshed'] = false
                            if (depot.tr && depot.tr.getAttribute('not-here')) depot.tr.removeAttribute('not-here')
                        }
                    }
                    while (depot = depotsToDelete.pop()) {
                        depot.manager.displayAsDeleted(depot, force ? 'change-ff' : 'unknown'); 
                        if (myOwn.config.config['grid-row-retain-moved-or-deleted']) { 
                            if(!depot['$refreshed']){
                                grid.retrieveRowAndRefresh(depot,{retrieveIgnoringWhere:true})
                                depot['$refreshed'] = true
                            }
                        }
                    }
                }
            })
        }
        if(grid.def.refrescable && !grid.def.forInsertOnlyMode){
            if (window.currentAutofrefresh) clearInterval(window.currentAutofrefresh);
            window.currentAutofrefresh = setInterval(grid.refreshAllRows,8000);
        }
        if (grid.def.selfRefresh && !grid.def.forInsertOnlyMode) {
            var refresh = function refresh(){
                grid.refreshAllRows();
            }
            grid.dom.main.addEventListener('deletedRowOk', refresh);
            grid.dom.main.addEventListener('savedRowOk', refresh);
        }
    });
    grid.waitForReady = function waitForReady(fun){
        return preparing.then(function(){
            return grid;
        }).then(fun||function(){}).then(function(){ return grid; });
    };
    return grid;
};

myOwn.TableGrid.prototype.hideColumnsViaCss = function hideColumnsViaCss(){
    var grid = this;
    var autoStyle = my.inlineCss('bp-hidden-columns-'+(grid.def.gridAlias || grid.def.name));
    autoStyle.innerHTML = grid.view.hiddenColumns.map(function(columnName){
        return "[my-table|='"+(grid.def.gridAlias || grid.def.name)+"'] [my-colname='"+columnName+"'] {display:none}";
    }).join('\n');
}

myOwn.TableGrid.prototype.displayPreLoadMessage = function displayPreLoadMessage(){
    var div=html.div({class:'div-loading-over'}).create();
    var rect=my.getRect(this.dom.main);
    div.style.left  =rect.left  +'px';
    div.style.top   =rect.top   +'px';
    div.style.width =rect.width +'px';
    div.style.heigth=rect.heigth+'px';
    this.dom.main.appendChild(div);
    div.textContent = my.messages.loading+'...';
};

myOwn.TableGrid.prototype.displayLoadingMessage = function displayLoadingMessage(){
    this.dom.main.style.background='#EEE';
    this.dom.main.textContent = my.messages.loading+'...';
};

myOwn.TableGrid.prototype.createDepotFromRow = function createDepotFromRow(row, status){
    var grid = this;
    var depot = {
        my: grid.my,
        def: grid.def,
        connector: grid.connector, 
        manager: grid,
        rowControls:{},
        row: row,
        retrievedRow: changing(row, {"$allow.delete":null,"$allow.update":null}, changing.options({deletingValue:null})),
        rowPendingForUpdate:{},
        primaryKeyValues:false,
        status: status||'preparing',
        detailControls:{},
        detailRows:[],
        actionButton:{},
        allow:{delete:row["$allow.delete"], update:row["$allow.update"]}
    };
    return depot;
};

myOwn.TableGrid.prototype.prepareDepots = function prepareDepots(rows){
    var grid = this;
    grid.depots = rows.map(grid.createDepotFromRow.bind(grid));
    grid.view.sortColumns=(grid.view.sortColumns||grid.def.sortColumns||grid.def.primaryKey||[]).map(
        function(k){ return typeof k == "string" ? {column:k} : k }
    );
};

myOwn.TableGrid.prototype.updateSortArrow = function updateSortArrow(){
    var grid = this;
    var oldSortImgs = grid.dom.table.getElementsByClassName('sort-img');
    var oldSortSpans = grid.dom.table.getElementsByClassName('sort-span');
    for (var i = oldSortImgs.length; i > 0; i--) {
        oldSortImgs[i-1].remove();
    }
    for (var i = oldSortSpans.length; i > 0; i--) {
        oldSortSpans[i-1].remove();
    }
    grid.view.sortColumns.forEach(function(sortColumn, index){
        Array.prototype.forEach.call(grid.dom.table.getElementsByClassName('th-name'), function(th){
            if(th.getAttribute('my-colname') == sortColumn.column){
                var order = sortColumn.order?sortColumn.order:1;
                th.setAttribute('ordered-direction',order);
                th.setAttribute('ordered-order',index+1);
            }
        });
    });
};

myOwn.TableGrid.prototype.refresh = function refresh(){
    return this.prepareAndDisplayGrid();
};

myOwn.TableGrid.prototype.prepareAndDisplayGrid = function prepareAndDisplayGrid(){
    var grid = this;
    grid.displayPreLoadMessage();
    var structureRequest = grid.connector.getStructure().then(function(tableDef){
        grid.def = tableDef;
        grid.vertical = tableDef.layout.vertical;
        grid.displayLoadingMessage();
        return grid.prepareGrid();
    });
    return grid.connector.getData().then(function(rows){
        return structureRequest.then(function(){
            if(grid.def.layout.errorList){
                if(!rows.length){
                    grid.dom.caption.textContent=my.messages.thereAreNot+': '+grid.dom.caption.textContent;
                    grid.dom.table.setAttribute('error-list','no-errors');
                }else{
                    grid.dom.table.setAttribute('error-list','have-errors');
                    // grid.dom.caption.style.backgroundColor='#F44';
                }
            }
            grid.prepareDepots(rows);
            grid.displayGrid();
            grid.updateSortArrow();
            if(grid.def.forInsertOnlyMode){
                grid.createRowInsertElements();
            }
            grid.refreshAggregates();
            grid.dom.main.style.background=null;
            return grid;
        });
    }).catch(function(err){
        grid.my.log(err);
        throw err;
    });
};

myOwn.ColumnGrid = function ColumnGrid(opts){
    for(var optName in opts){
        this[optName] = opts[optName];
    }
};

myOwn.ColumnGrid.prototype.th = function th(){
    return html.th();
};

myOwn.ColumnGrid.prototype.thDetail = function thDetail(){
    return html.th({class:'th-detail'}, (this.fieldDef||{}).label);
};

myOwn.ColumnGrid.prototype.thAgg = function thAgg(){
    return html.th({class:'th-agg'});
};

myOwn.ColumnGrid.prototype.thFilter = function thFilter(){
    return html.th();
};

myOwn.ColumnGrid.prototype.td = function td(){
    return html.td().create();
};

myOwn.ActionColumnGrid = function ActionColumnGrid(opts){
    myOwn.ColumnGrid.call(this,opts);
};

myOwn.ActionColumnGrid.prototype = Object.create(myOwn.ColumnGrid.prototype);

myOwn.ActionColumnGrid.prototype.th = function th(){
    return html.th({class:'grid-th-actions'}, this.actions);
};

myOwn.ActionColumnGrid.prototype.thFilter = function thFilter(depot){
    var grid = this.grid;
    if(depot.firstFilter){
        var buttonFilter=html.button({id:'button-filter'},myOwn.messages.Filter+"!").create();
        buttonFilter.addEventListener('click',function(){
            grid.updateFilterInfo(' (F) ');
            grid.displayBody();
        });
    }else{
        var buttonFilter=html.button({class:'table-button', 'when-filter':'yes', "skip-enter":true}, [
            html.img({
                src:my.path.img+'destroy-filter.png',
                alt:'FILTER OFF',
                title:my.messages.filterOff
            })
        ]).create();
        buttonFilter.addEventListener('click',function(){
            // HACIENDO
            grid.view.filter = grid.view.filter.filter(d=>d!=depot);
            var info = my.tableInfo(this);
            info.tr.parentNode.removeChild(info.tr);
        });
    }
    return html.th([buttonFilter]);
};

myOwn.ActionColumnGrid.prototype.td = function td(depot){
    var grid = this.grid;
    var thActions=html.th({class:['grid-th','grid-th-actions']}).create();
    thActions.rowSpan = 1 + this.grid.def.layout.extraRows;
    var actionNamesList = ['insert','delete','vertical-edit'].concat(grid.def.actionNamesList);
        if(!grid.def.forInsertOnlyMode || grid.def.actionNamesListForInsertOnlyMode){
            if(grid.def.forInsertOnlyMode && grid.def.actionNamesListForInsertOnlyMode){
                actionNamesList = grid.def.actionNamesListForInsertOnlyMode;
            }
            actionNamesList.forEach(function(actionName){
            var actionDef = my.tableAction[actionName];
            if(grid.def.allow[actionName] && depot.allow[actionName] !== false){
                var buttonAction=html.button({class:'table-button', "skip-enter":true}, [
                    html.img({src:actionDef.img, alt:actionDef.alt, title:my.messages[actionDef.titleMsg]})
                ]).create();
                thActions.appendChild(buttonAction);
                buttonAction.addEventListener('click', function(){
                    actionDef.actionRow(depot, {launcher:buttonAction});
                });
                depot.actionButton[actionName]=buttonAction;
            }
        });
    }
    return thActions;
};

myOwn.ActionColumnGrid.prototype.thAgg = function thAgg(){
    return html.th({class:'th-action'}, this.actionsBottom);
};

myOwn.DataColumnGrid = function DataColumnGrid(opts){
    myOwn.ColumnGrid.call(this,opts);
};

myOwn.DataColumnGrid.prototype = Object.create(myOwn.ColumnGrid.prototype);

myOwn.DataColumnGrid.prototype.cellAttributes = function cellAttributes(specificAttributes,opts){
    var fieldDef = this.fieldDef;
    var grid=this.grid;
    var opts=opts||{};
    var attr=changing({"my-colname":fieldDef.name},specificAttributes);
    if(!opts.skipMandatory){
        if(fieldDef.nullable!==true && fieldDef.isPk){
            attr["my-mandatory"]="pk";
        }else if(fieldDef.nullable===false){
            attr["my-mandatory"]="normal";
        }
    }
    if(grid.connector.hideBecauseRelated[fieldDef.name] && !fieldDef.alwaysShow){
        attr["inherited-pk-column"]="yes";
    }
    if(fieldDef.referencesField){
        if(grid.connector.hideBecauseRelated[fieldDef.referencesField] && !fieldDef.alwaysShow){
            attr["inherited-pk-column"]="yes";
        }
        if(grid.def.field[fieldDef.referencesField].isPk){
            attr["my-fixed2left-column"]=true;
        }
    }
    if(fieldDef.isPk){
        attr["my-pk-column"]=fieldDef.isPk;
        attr["my-fixed2left-column"]=true;
    }
    return attr;
}
    
myOwn.DataColumnGrid.prototype.th = function th(){
    var fieldDef = this.fieldDef;
    var grid = this.grid;
    var th=html.th(this.cellAttributes({class: "th-name", title:fieldDef.description||''}),fieldDef.title).create();
    if(fieldDef.width){
        th.style.width=fieldDef.width+'px';
        th.style.minWidth=fieldDef.width+'px';
    }
    th.addEventListener('click',function(mouseEvent){
        if(mouseEvent.altKey){
            grid.view.hiddenColumns.push(fieldDef.name);
            grid.hideColumnsViaCss();
        }else{
            var sortColumnResult = grid.view.sortColumns.find(function(sortColumn){
                return sortColumn.column == fieldDef.name;
            });
            var currentOrder = sortColumnResult?sortColumnResult.order:null;
            if(sortColumnResult && sortColumnResult == grid.view.sortColumns[0]){
                var newOrder = currentOrder?-currentOrder:1;
            }else{
                var newOrder = 1;
            }
            grid.view.sortColumns=grid.view.sortColumns.filter(function(sortColumn){
                return sortColumn.column != fieldDef.name;
            });
            grid.view.sortColumns.unshift({column:fieldDef.name, order:newOrder});
            grid.updateSortArrow();
            grid.displayBody();
        }
    });
    return th;
};

myOwn.DataColumnGrid.prototype.thFilter = function thFilter(depot, iColumn){
    var grid = this.grid;
    var fieldDef = this.fieldDef;
    var fieldName=fieldDef.name;
    depot.rowSymbols[fieldDef.name]=depot.rowSymbols[fieldDef.name]||'~';
    var filterImage=my.path.img+my.comparator.traductor[depot.rowSymbols[fieldDef.name]]+'.png';
    var th=html.td(this.cellAttributes({class:"autoFilter", "typed-controls-direct-input":true},{skipMandatory:true})).create();
    var symbolFilter=th;
    symbolFilter.style.backgroundImage='url('+filterImage+')';
    var elementFilter=th;
    depot.rowControls[fieldName]=elementFilter;
    TypedControls.adaptElement(elementFilter,fieldDef);
    if(fieldName in depot.row){
        elementFilter.setTypedValue(depot.row[fieldName]);
    }
    elementFilter.addEventListener('update',function(){
        depot.row[fieldDef.name]=this.getTypedValue();
    });
    symbolFilter.addEventListener('click',function(event){
        var rect=my.getRect(this);
        if(event.pageX>=rect.left && event.pageX<=rect.left + myOwn.comparatorWidth){
            miniMenuPromise([
                {value:'=',     img:my.path.img+'igual.png'      ,label:myOwn.messages.equalTo},
                {value:'~',     img:my.path.img+'parecido.png'   ,label:myOwn.messages.similarTo},
                {value:'!~',    img:my.path.img+'not-like.png'   ,label:myOwn.messages.notSimilarTo},
                {value:'\u2205',img:my.path.img+'vacio.png'      ,label:myOwn.messages.empty},
                {value:'!=\u2205',img:my.path.img+'not-empty.png',label:myOwn.messages.notEmpty},
                {value:'>',     img:my.path.img+'mayor.png'      ,label:myOwn.messages.greaterThan},
                {value:'>=',    img:my.path.img+'mayor-igual.png',label:myOwn.messages.greaterEqualThan},
                {value:'<',     img:my.path.img+'menor.png'      ,label:myOwn.messages.lessThan},
                {value:'<=',    img:my.path.img+'menor-igual.png',label:myOwn.messages.lessEqualThan},
                {value:'!=',    img:my.path.img+'not-equal.png'  ,label:myOwn.messages.distinctFrom},
            ],{underElement:symbolFilter}).then(function(result){
                filterImage=my.path.img+my.comparator.traductor[result]+'.png';
                symbolFilter.style.backgroundImage='url('+filterImage+')';
                depot.rowSymbols[fieldDef.name]=result;
            });
            event.preventDefault();
        }
    });
    return th;
};

myOwn.DataColumnGrid.prototype.thDetail = function thDetail(){
    var fieldDef = this.fieldDef||{};
    return html.th(this.cellAttributes({class:"th-detail", title:fieldDef.description||''}), fieldDef.label);
};

myOwn.DataColumnGrid.prototype.thAgg = function thAgg(){
    var grid=this.grid;
    var th=html.th(this.cellAttributes({class:"th-agg"})).create();
    if(this.fieldDef.aggregate){
        TypedControls.adaptElement(th,{typeName:'decimal'});
        grid.dom.aggregate[this.fieldDef.name]=th;
    }
    return th;
};

myOwn.DataColumnGrid.prototype.td = function td(depot, iColumn, tr, saveRow){
    var grid = this.grid;
    var fieldDef = this.fieldDef;
    var forInsert = false; // TODO: Verificar que esto está en desuso
    var enabledInput=depot.allow.update !== false && grid.def.allow.update /* && !grid.connector.fixedField[fieldDef.name] */ && (forInsert?fieldDef.allow.insert:fieldDef.allow.update);
    var directInput=true;
    var control;
    var td;
    if(fieldDef.clientSide && my.clientSides[fieldDef.clientSide].cellDef){
        var cellDef = my.clientSides[fieldDef.clientSide].cellDef;
        control = html[cellDef.tagName||'input'](changing({class:'bp-input'},cellDef.inputAttrs||{})).create();
        td = html.td(this.cellAttributes(cellDef.cellAttrs||{}),[control]).create();
    }else if(fieldDef.mobileInputType && my.mobileMode){
        var inputAttrs = {type:'text'};
        if(fieldDef.mobileInputType=='number'){
            inputAttrs.type = 'number';
            //inputAttrs.pattern = "[0-9]*";
        }
        control = html.input(changing({class:'bp-input'},inputAttrs)).create();
        td = html.td(this.cellAttributes({}),[control]).create();
    }else{
        control = td = html.td(this.cellAttributes({"typed-controls-direct-input":directInput})).create();
    }
    // td.clientControl = clientControl;
    td.rowSpan = td.rowSpan + fieldDef.extraRow;
    if(fieldDef.typeName=='number'){
        throw new Error("There's a field in the table defined as Number (Number type is deprecated)");
    }
    TypedControls.adaptElement(control, fieldDef);
    if(!enabledInput){
        control.disable(true);
    }
    depot.rowControls[fieldDef.name] = control;
    if(depot.row[fieldDef.name]!=null){
        control.setTypedValue(depot.row[fieldDef.name]);
    }
    if(!fieldDef.clientSide || fieldDef.serverSide && fieldDef.inTable!==false){
        control.addEventListener('update',function(){
            var value = this.getTypedValue();
            if(!sameValue(value,depot.row[fieldDef.name])){
                this.setAttribute('io-status', 'pending');
                depot.rowPendingForUpdate[fieldDef.name] = value;
                depot.row[fieldDef.name] = value;
                if(grid.modes.saveByField){
                    saveRow(depot,{visiblyLogErrors:false});
                }
                var promiseChain = Promise.resolve();
                if (fieldDef.references) {
                    promiseChain = promiseChain.then(
                        grid.setInheritedFields(depot, function(fkDef){
                            return fkDef.references == fieldDef.references && 
                            fkDef.fields.find(function(field){
                                return field.source == fieldDef.name
                            }) &&
                            fkDef.displayFields.length >= 0 
                        }
                    ));
                }
                promiseChain.then(function(){
                    grid.updateRowData(depot,true);
                });
            }
        });
    }
    control.depot=depot;
    control.addEventListener('update', function(){
        grid.refreshAggregates();
        if(grid.def.specialValidator){
            var specialMandatories=myOwn.validators[grid.def.specialValidator].getMandatoryMap(depot.row);
            Array.prototype.forEach.call(tr.cells, function(cell){
                var fieldName = cell.getAttribute("my-colname");
                var specialMandatory=specialMandatories[fieldName];
                if(cell.hasAttribute("my-special")){
                    if(!specialMandatory){
                        cell.removeAttribute("my-special");
                    }else if(cell.getAttribute("my-special")!=specialMandatory){
                        cell.setAttribute("my-special", specialMandatory);
                    }
                }else{
                    if(specialMandatory){
                        cell.setAttribute("my-special", specialMandatory);
                    }
                }
            });
        }
    })
    return td;
};

myOwn.DetailColumnGrid = function DetailColumnGrid(opts){
    myOwn.ColumnGrid.call(this,opts)
};

myOwn.DetailColumnGrid.prototype = Object.create(myOwn.ColumnGrid.prototype);

myOwn.DetailColumnGrid.prototype.th = function th(){
    var grid = this.grid;
    var detailTableDef = this.detailTableDef;
    grid.detailNames = grid.detailNames || {};
    var detailName = detailTableDef.table;
    if(grid.detailNames[detailName]){
        detailName = detailTableDef.table+' '+detailTableDef.abr;
        if(grid.detailNames[detailName]){
            detailName = likeAr(grid.detailNames[detailName]).array().length;
        }
    }
    grid.detailNames[detailName]={};
    this.detailName=detailName;
    var th=html.th({class:'grid-th-details', "my-defname":this.detailTableDef.table||this.detailTableDef.wScreen, title:this.detailTableDef.label},this.detailTableDef.abr);
    return th;
};

myOwn.DetailColumnGrid.prototype.td = function td(depot, iColumn, tr){
    var grid = this.grid;
    var detailTableDef = this.detailTableDef;
    var detailTableNameAndAbr = this.detailName;
    var detailControl = depot.detailControls[detailTableNameAndAbr] || { show:false };
    if(detailTableDef.condition){
        if(!my.conditions[detailTableDef.condition](depot)){
            return html.td({class:['grid-th','grid-th-details'], "my-relname":detailTableDef.table}).create();
        }
    }
    detailControl.img = html.img({
        src:my.path.img+'detail-unknown.png',
        alt:'DETAIL',
        title:detailTableDef.label||my.messages.details
    }).create();
    var menuRef = detailTableDef.table ? {table:detailTableDef.table} : {w:detailTableDef.wScreen, autoproced:true};
    var calculateFixedFields = function(){
        return detailTableDef.fields
        .filter(function(pair){
            return !pair.nullMeansAll || depot.row[pair.source] != null;
        })
        .map(function(pair){
            var fieldCondition={fieldName: pair.target, value:'value' in pair ? pair.value : depot.row[pair.source]}
            if(pair.range){
                fieldCondition.range=pair.range;
            }
            return fieldCondition;
        });
    }
    detailControl.calculateFixedFields = calculateFixedFields;
    var updateHrefBeforeClick = function(){
        var fixedFields = calculateFixedFields();
        menuRef.fixedFields=fixedFields;
        this.setForkeableHref(menuRef);
    }
    var buttonClick=function(event){
        var fixedFields = calculateFixedFields();
        menuRef.fixedFields=fixedFields;
        this.setForkeableHref(menuRef);
        if(!event.ctrlKey && event.button!=1){
            event.preventDefault();
        }
        detailControl.displayDetailGrid({fixedFields:fixedFields, detailing:{}},event);    
    }
    detailControl.forceDisplayDetailGrid = function(opts){
        if(detailControl.show){
            this.displayDetailGrid(opts);
        }
        this.divDetail=null;
        this.displayDetailGrid(opts);
    }
    detailControl.refreshDetailGrid = function(opts){
        var detailControl=this;
        if(detailControl.show){
            
        }
    }
    detailControl.displayDetailGrid = function(opts,event){
        var result;
        event=event||{};
        if(!('fixedFields' in opts)){
            opts.fixedFields = calculateFixedFields();
        }
        var fixedFields = opts.fixedFields;
        var spansForSmooth = [iColumn+1, 999];
        if(!detailControl.show && !event.ctrlKey && event.button!=1){
            detailControl.img.src=my.path.img+'detail-contract.png';
            detailControl.img.alt="[-]";
            detailControl.img.title=my.messages.lessDetails;
            var newTr = grid.my.insertRow({under:tr,smooth:{height:70, spans:spansForSmooth},autoFocus:false});
            detailControl.tr = newTr;
            var tdMargin = newTr.insertCell(-1);
            tdMargin.colSpan = td.cellIndex+1;
            var tdGrid = newTr.insertCell(-1);
            tdGrid.colSpan = tr.cells.length-td.cellIndex;
            var divGrid = tdGrid;
            divGrid.style.maxWidth=td.parentNode.offsetWidth - td.offsetLeft + 'px';
            divGrid.style.overflowX='visible';
            tdGrid.className='my-detail-grid';
            tdGrid.style.overflowX='visible';
            if(!detailControl.divDetail){
                if(detailTableDef.wScreen){
                    var params=likeAr.toPlainObject(fixedFields,'fieldName','value');
                    var wScreen=my.wScreens[detailTableDef.wScreen];
                    if(!wScreen.parameters){
                        alertPromise('error lack of parameters in wScreen '+detailTableDef.wScreen);
                    }
                    wScreen.mainAction(params,divGrid);
                }else{
                    result = grid.my.tableGrid(detailTableDef.table, divGrid, {
                        fixedFields: fixedFields, 
                        detailing:opts.detailing, 
                        detailingForUrl:grid.detailingForUrl,
                        detailingPath:(grid.detailingPath||[]).concat(depot.lastsPrimaryKeyValues),
                        parentDepot: depot
                    }).waitForReady(function(g){
                        detailControl.divDetail=g.dom.table;
                        if(detailTableDef.refreshParent || grid.def.complexDef && detailTableDef.refreshParent!==false){
                            var refresh = function refresh(){
                                grid.retrieveRowAndRefresh(depot);
                            }
                            g.dom.main.addEventListener('deletedRowOk', refresh);
                            g.dom.main.addEventListener('savedRowOk', refresh);
                        }
                        detailControl.refreshAllRowsInGrid=function(force){
                            if(detailTableDef.refreshFromParent || force){
                                if (force) {
                                    g.dom.main.style.opacity=0.5;
                                    fixedFields = calculateFixedFields()
                                    g.connector.fixedFields = fixedFields
                                }
                                setTimeout(async ()=>{
                                    await g.refreshAllRows(force);
                                    g.dom.main.style.opacity=1;
                                },1)
                            }
                        }
                        return g;
                    });
                }
            }else{
                divGrid.appendChild(detailControl.divDetail);
            }
            detailControl.show = true;
            newTr.detailTableNameAndAbr=detailTableNameAndAbr;
            newTr.isDetail=true;
            depot.detailRows.push(newTr);
            // opts.detailing[depot.lastsPrimaryKeyValues]=opts.detailing[depot.lastsPrimaryKeyValues]||{};
            // opts.detailing[depot.lastsPrimaryKeyValues][detailTableNameAndAbr]={};
        }else{
            detailControl.img.src=my.path.img+'detail-expand.png';
            detailControl.img.alt="[+]";
            detailControl.img.title=detailTableDef.label||my.messages.details;
            if(detailControl.tr){
                grid.my.fade(detailControl.tr, {smooth:{spans:spansForSmooth, content:detailControl.divDetail}});
                depot.detailRows = depot.detailRows.filter(function(tr){ return tr!==detailControl.tr;});
            }
            detailControl.show = false;
            detailControl.tr = null;
            // opts.detailing[depot.lastsPrimaryKeyValues]=opts.detailing[depot.lastsPrimaryKeyValues]||{};
            // delete opts.detailing[depot.lastsPrimaryKeyValues][detailTableNameAndAbr];
        }
        return result;
    };
    var button = my.createForkeableButton(menuRef,{label:detailControl.img, onclick:buttonClick, updateHrefBeforeClick:updateHrefBeforeClick, class:'table-button'});
    button.setAttribute("skip-enter",true);
    // var button = html.button({class:'table-button', "skip-enter":true}, [detailControl.img]).create();
    var td = html.td({class:['grid-th','grid-th-details'], "my-relname":detailTableDef.table||detailTableDef.wScreen}, button).create();
    depot.detailControls[detailTableNameAndAbr] = detailControl;
    return td;
};

myOwn.SpecialColumnGrid = function SpecialColumnGrid(opts){
    myOwn.ColumnGrid.call(this,opts);
};

myOwn.SpecialColumnGrid.prototype = Object.create(myOwn.ColumnGrid.prototype);

myOwn.SpecialColumnGrid.prototype.th = function th(){
    return html.th({class:this.class});
};

myOwn.SpecialColumnGrid.prototype.thDetail = myOwn.SpecialColumnGrid.prototype.th;
myOwn.SpecialColumnGrid.prototype.thAgg = myOwn.SpecialColumnGrid.prototype.th;

function s2ab(s) {
    var buf;
    var i;
    if(typeof ArrayBuffer !== 'undefined') {
        buf = new ArrayBuffer(s.length);
        var view = new Uint8Array(buf);
        for (i=0; i!=s.length; ++i){
            view[i] = s.charCodeAt(i) & 0xFF;
        }
    } else {
        buf = new Array(s.length);
        for (i=0; i!=s.length; ++i){
            buf[i] = s.charCodeAt(i) & 0xFF;
        }
    }
    return buf;
}

function Workbook() {
    if(!(this instanceof Workbook)){
        return new Workbook();
    }
    this.SheetNames = [];
    this.Sheets = {};
}

myOwn.TableGrid.prototype.prepareMenu = function prepareMenu(button){
    button.src=my.path.img+'menu-dots.png';
    button.title=my.messages.optionsForThisTable;
    var grid=this;
    var menuOptions=[];
    menuOptions.push({img:my.path.img+'refresh.png', value:true, label:my.messages.refresh, doneFun:function(){
        return grid.refresh();
    }});
    menuOptions.push({img:my.path.img+'show-inherited-keys.png', value:true, label:my.messages.showInheritedKeys, doneFun:function(){
        grid.view.showInheritedKeys = !grid.view.showInheritedKeys;
        grid.dom.table.parentNode.setAttribute('show-inherited-keys', grid.view.showInheritedKeys?'yes':'no');
        return Promise.resolve(true);
    }});
    menuOptions.push({img:my.path.img+'show-hide-columns.png', value:true, label: my.messages.hideOrShow, doneFun:function(){
        dialogPromise(function(dialogWindow, closeWindow){
            var button=html.button({class:'hide-or-show'},'ok').create();
            var createSelectElement=function createSelectElement(columns,hideOrShowId,disabledItems){
                var selectElement=html.select(
                    {id:hideOrShowId, class:'hide-or-menu', multiple:true, size:Math.max(Math.min(grid.def.fields.lenght,10),4)},
                    columns.map(function(column){
                        return html.option({value: column, disabled:disabledItems.indexOf(column)!==-1},column)
                    })
                ).create()
                return selectElement;
            };
            var selectColumnsToHideElement=createSelectElement(
                grid.def.fields.map(function(def){return def.name;}),
                'hide-columns',
                grid.view.hiddenColumns
            );
            var selectColumnsToShowElement=createSelectElement(
                grid.view.hiddenColumns,
                'show-columns',
                []
            );
            var hideOrShowTable=html.table({class:"show-or-hide"},[
                html.tr([
                    html.td({class:'show-or-hide-title'},my.messages.exhibitedColumns),
                    html.td(),
                    html.td({class:'show-or-hide-title'},my.messages.hiddenColumns)
                ]),
                html.tr([
                    html.td([selectColumnsToHideElement]),
                    html.td([
                        html.div([html.img({class:'show-or-hide-img',src:my.path.img+'show.png'})]),
                        html.div([html.img({class:'show-or-hide-img',src:my.path.img+'hide.png'})])
                    ]),
                    html.td([selectColumnsToShowElement]),
                ]),
                html.tr([html.td({colspan:3},[button])])
            ]);
            dialogWindow.appendChild(hideOrShowTable.create());
            selectColumnsToHideElement.addEventListener('change',function(){
                Array.prototype.forEach.call(selectColumnsToHideElement.selectedOptions, function(option) {
                    option.disabled=true;
                    selectColumnsToShowElement.add(html.option({value:option.value}, option.value).create());
                    grid.view.hiddenColumns.push(option.value);
                });
                grid.hideColumnsViaCss();
            });
            selectColumnsToShowElement.addEventListener('change',function(){
                var listOptionsToShow=Array.prototype.slice.call(selectColumnsToShowElement.selectedOptions); 
                listOptionsToShow.forEach(function(option) {
                    grid.view.hiddenColumns.splice(grid.view.hiddenColumns.indexOf(option.value),1);
                    selectColumnsToShowElement.removeChild(option);
                    var elementToEnable=selectColumnsToHideElement.querySelector("[value="+option.value+"]");
                    elementToEnable.disabled=false;
                });
                grid.hideColumnsViaCss();
            });
            var showAndHide=function showAndHide(selectColumnElement,showOrHide){
                Array.prototype.forEach.call(selectColumnElement.children,function(child){
                    if(showOrHide=='hide'){
                        hideColumn(child.value)
                        grid.view.hiddenColumns.push(child.value);
                    }else{
                        showColmn(child.value);
                    }
                })
            };
            button.addEventListener('click',function(){
                closeWindow();
            })
        })
        return true;
    }});
    if(grid.def.allow.export){
        menuOptions.push({img:my.path.img+'export.png', value:true, label:my.messages.export, doneFun: function(){
            myOwn.dialogDownload(grid);
        }});
    }
    if(grid.def.allow.import){
        var showWithMiniMenu = true;
        var messages = {};
        menuOptions.push(
            myOwn.dialogUpload(
                'table_upload', 
                {
                    table:grid.def.name, 
                    prefilledFields:grid.connector.fixedFields,
                    skipUnknownFieldsAtImport:null
                },
                function(result){
                    var messages=[];
                    if(result.uploaded){
                        if(result.uploaded.inserted==1){
                            messages.push(my.messages.oneRowInserted);
                        }else if(result.uploaded.inserted>1){
                            messages.push(my.messages.xRowsInserted.replace('{$x}',result.uploaded.inserted));
                        }
                        if(result.uploaded.updated==1){
                            messages.push(my.messages.oneRowUpdated);
                        }else if(result.uploaded.updated>1){
                            messages.push(my.messages.xRowsUpdated.replace('{$x}',result.uploaded.updated));
                        }
                        if(result.uploaded.skipped==1){
                            messages.push(my.messages.oneRowSkipped);
                        }else if(result.uploaded.skipped>1){
                            messages.push(my.messages.xRowsSkipped.replace('{$x}',result.uploaded.skipped));
                        }
                        if(result.uploaded.deleted==1){
                            messages.push(my.messages.oneRowDeleted);
                        }else if(result.uploaded.deleted>1){
                            messages.push(my.messages.xRowsDeleted.replace('{$x}',result.uploaded.deleted));
                        }
                        if(result.uploaded.skippedColumns.length==1){
                            messages.push(my.messages.skippedColumn+": "+result.uploaded.skippedColumns.join(', '));
                        }else if(result.uploaded.skippedColumns.length>1){
                            messages.push(my.messages.skippedColumns+": "+result.uploaded.skippedColumns.join(', '));
                        }
                        return messages.join(' \r\n');
                    }
                    return result.message || '';
                },
                showWithMiniMenu,
                messages,
                grid,
                false,
                ['skipUnknownFieldsAtImport','simplificateSpaces','replaceNewLineWithSpace']
            )
        );
    }
    if(grid.def.allow.delete || grid.def.allow.deleteAll){
        menuOptions.push({img:my.path.img+'delete-all-rows.png', value:true, label:my.messages.deleteAllRecords, doneFun:function(){
            return confirmPromise(my.messages.confirmDeleteAll+(
                grid.depotsToDisplay.length<grid.depots.length?my.messages.xOverTWillDelete:(
                    grid.connector.fixedFields.length?my.messages.allGWillDelete:my.messages.allTWillDelete
                )
            ).replace('{$x}',grid.depotsToDisplay.length).replace('{$t}',grid.depots.length)
            ).then(function(){
                return my.ajax.table_records_delete({
                    table:grid.def.name,
                    rowsToDelete:grid.depotsToDisplay.map(function(depot){
                        return likeAr(depot.row).filter(function(_value, name){
                            var fieldDef = grid.def.field[name];
                            if(fieldDef.clientSide && !fieldDef.serverSide || fieldDef.inTable === false){
                                return false;
                            }
                            return true;
                        }).plain();
                    }),
                    expectedRemainCount:grid.depots.length-grid.depotsToDisplay.length
                },{mayBeSlow:true}).then(function(message){
                    return Promise.all([
                        grid.refresh(),
                        alertPromise((
                            message.record_count?my.messages.recordsReimaining:my.messages.allRecordsDeleted
                        ).replace('{$r}',message.remaining_record_count))
                    ]);
                });
            });
        }});
    }
    var urlFilteredCompleteTable='menu?w=table&table='+grid.def.name+'&fc='+JSON.stringify(
        grid.connector.fixedFields.map(function(ff){
            return {column:ff.fieldName, operator:'=', value:ff.value};
        })
    );
    menuOptions.push({
        img:my.path.img+'filtered-complete-table.png', 
        label:my.messages.filteredCompleteTable, 
        href:urlFilteredCompleteTable,
        showPage:my.showPage,
    });
    button.onclick=function(){
        miniMenuPromise(menuOptions,{
            underElement:button,
            withCloseButton:false,
        });
    };
};

myOwn.INCLUDE_LOOKUP_COLUMNS_IN_TXT_EXPORT=true;

myOwn.dialogDownload = function dialogDownload(grid){
    return dialogPromise(function(dialogWindow, closeWindow){
        var prepareDownloadElement=html.button({class:'export-a'},my.messages.prepare).create();
        var downloadElement=html.a({class:'export-a'},my.messages.download).create();
        var input={
            xlsx : html.input({class:'export-radio',type:'radio', value:'xlsx', name:'format', checked:true}).create(),
            txt  : html.input({class:'export-radio',type:'radio', value:'txt' , name:'format'}).create(),
            csv  : html.input({class:'export-radio',type:'radio', value:'csv' , name:'format'}).create(),
            fromOtherTables : html.input({type:'checkbox', checked:true , name:'fromOtherTables'}).create() ,
            readOnly        : html.input({type:'checkbox', checked:true , name:'readOnly'}).create() ,
            hiddens         : html.input({type:'checkbox', checked:false, name:'hiddens'}).create() ,
        }
        var mainDiv=html.div({class:'dialog-export',"current-state":"chossing"}, [
            html.div({class:"state-dialog"},[
                html.div(my.messages.format),
                html.div([html.label([input.xlsx, '.xlsx'])]),
                html.div([html.label([input.txt , '.txt "|"'])]),
                html.div([html.label([input.csv , '.csv ","'])]),
                html.br(),
                html.div([my.messages.exportFields,':']),
                html.div([html.label([input.fromOtherTables , html.span(my.messages.fromOtherTables)])]),
                html.div([html.label([input.readOnly        , html.span(my.messages.readOnly)])]),
                html.div([html.label([input.hiddens         , html.span(my.messages.hiddens)])]),
                html.br(),
                // html.div([prepareDownloadElement]),
            ]),
            html.div({class:'state-preparing'}, [
                html.div(my.messages.preparingForExport),
                html.img({
                    class:'img-preparing', 
                    src:'img/preparing.png', 
                    alt:my.messages.preparingForExport, 
                    title:my.messages.preparingForExport, 
                }),
            ]),
            html.div({class:'state-ready'}, [downloadElement]),
            html.div('.'), 
        ]).create();
        dialogWindow.appendChild(mainDiv);
        var fieldsDef2Export=[];
        var extraColumns={}
        var lastColumnExported=-1;
        var separator=';';
        var replacer=function(x){ return x};
        var dotExtension='.txt';
        var prepare=function(){
            mainDiv.setAttribute("current-state", "preparing");
            setTimeout(function(){
                fieldsDef2Export=grid.def.fields.filter(function(fieldDef){ 
                    return (fieldDef.inTable!==false || input.fromOtherTables.checked) 
                        && fieldDef.visible 
                        && (fieldDef.allow.update || input.readOnly.checked || fieldDef.isPk)
                        && (!grid.view.hiddenColumns.includes(fieldDef.name) || input.hiddens.checked);
                });
                if(input.xlsx.checked){
                    excelExport();
                }else{
                    if(input.csv.checked){
                        separator=',';
                        replacer=function(txt){ 
                            return /[\n,"]/.test(txt)?'"'+txt.replace(/"/g,'""')+'"':txt
                        }
                        dotExtension='.csv';
                    }else{
                        separator='|';
                        var trans={
                            '|':'\\x7C',
                            '\\':'\\\\',
                            '\r':'\\r',
                            '\n':'\\n',
                        }
                        replacer=function(txt){ 
                            return txt.replace(/[|\\\r\n]/g,function(char){
                                return trans[char];
                            })
                        }
                        dotExtension='.tab';
                    }
                    txtExport();
                }
            },100)
        }
        prepareDownloadElement.onclick=prepare;
        if("auto prepare"){
            likeAr(input).forEach(function(input){ input.onclick=prepare; })
            setTimeout(prepare,200);
        }
        var txtExport = function(){
            var data=[];
            var titles = fieldsDef2Export.map(function(fieldDef){
                if(fieldDef.inTable===false && !input.fromOtherTables.checked) return '';
                return fieldDef.name;
            });
            data.push(titles);
            grid.depotsToDisplay.forEach(function(depot){
                data.push(fieldsDef2Export.map(function(fieldDef){
                    if(fieldDef.inTable===false && !input.fromOtherTables.checked) return '';
                    var value=depot.row[fieldDef.name];
                    var type=fieldDef.typeName;
                    return value!=null?typeStore.typerFrom(fieldDef).toPlainString(value):'';
                }));
            });
            var blob = new Blob([data.map(function(line){return line.map(replacer).join(separator)}).join('\r\n')], {type: 'text/plain'});
            var url = URL.createObjectURL(blob); 
            downloadElement.href=url;
            downloadElement.setAttribute("download", grid.def.name+dotExtension);
            mainDiv.setAttribute("current-state", "ready");
        }
        var STYLE_HEADER={ font: {bold:true, underline:true}/*, alignment:{horizontal:'center'}*/};
        var STYLE_EXTRA_HEADER={ font: {bold:true, underline:true, color:{rgb: "800040"}}/*, alignment:{horizontal:'center'}*/};
        var STYLE_PK={font:{bold:true}};
        var STYLE_LOOKUP={font:{color:{ rgb: "5588DD" }}};
        var populateTableXLS = function populateTableXLS(ws, depots, fieldDefs, topRow, leftColumn){
            topRow=topRow||0;
            leftColumn=leftColumn||0;
            fieldDefs.forEach(function(field,iColumn){
                ws[XLSX.utils.encode_cell({c:iColumn+leftColumn,r:topRow})]={t:'s',v:field.name, s:STYLE_HEADER};
                lastColumnExported = Math.max(lastColumnExported, iColumn);
            });
            depots.forEach(function(depot, iRow){
                var addCell = function addCell(value, fieldDef, iColumn){
                    if(value!=null){
                        var excelValue=typeStore.typerFrom(fieldDef).toExcelValue(value);
                        var valueType=typeStore.typerFrom(fieldDef).toExcelType(value);
                        var cell={t:valueType,v:excelValue};
                        if(fieldDef.typeName == 'interval'){
                            cell.z='[h]:mm:ss';
                        }
                        if(fieldDef.isPk){
                            cell.s=STYLE_PK;
                        }else if(!fieldDef.allow || !fieldDef.allow.update || depot.allow.update === false){
                            cell.s=STYLE_LOOKUP;
                        }
                        ws[XLSX.utils.encode_cell({c:iColumn+leftColumn,r:iRow+1+topRow})]=cell;
                    }
                }
                fieldDefs.forEach(function(fieldDef, iColumn){
                    var value=depot.row[fieldDef.name];
                    if(grid.def.exportJsonFieldAsColumns == fieldDef.name){
                        var fields = typeof value == "string" ? JSON.parse(value) : value;
                        for(var name in fields){
                            var pos = extraColumns[name];
                            if(pos==null){
                                lastColumnExported++;
                                extraColumns[name] = lastColumnExported;
                                ws[XLSX.utils.encode_cell({c:lastColumnExported+leftColumn,r:topRow})]={t:'s',v:name, s:STYLE_EXTRA_HEADER};
                                pos = lastColumnExported;
                            }
                            addCell(fields[name], {typeName:typeof fields[name] == 'number'? 'decimal':'text'}, pos)
                        }
                    }else{
                        addCell(value, fieldDef, iColumn);
                    }
                });
            });
            ws["!ref"]="A1:"+XLSX.utils.encode_cell({c:lastColumnExported+leftColumn,r:grid.depotsToDisplay.length+topRow});
        }
        var excelExport = function(){
            var wb = XLSX.utils.book_new();
            var ws = {};
            var exportFileInformationWs={};
            var i=0;
            exportFileInformationWs[XLSX.utils.encode_cell({c:0,r:++i})]={t:'s',v:'table',s:STYLE_HEADER};
            exportFileInformationWs[XLSX.utils.encode_cell({c:1,r:  i})]={t:'s',v:grid.def.name};
            exportFileInformationWs[XLSX.utils.encode_cell({c:0,r:++i})]={t:'s',v:'date',s:STYLE_HEADER};
            exportFileInformationWs[XLSX.utils.encode_cell({c:1,r:  i})]={t:'s',v:new Date().toISOString()};
            exportFileInformationWs[XLSX.utils.encode_cell({c:0,r:++i})]={t:'s',v:'user',s:STYLE_HEADER};
            exportFileInformationWs[XLSX.utils.encode_cell({c:1,r:  i})]={t:'s',v:my.config.username};
            // grid.def.allow.forEach(function(action,iAction){
            //     exportFileInformationWs[XLSX.utils.encode_cell({c:iAction,r:2})]={t:'s',v:action};
            // })
            if(grid.def.exportMetadata){
                if(grid.def.exportMetadata.fieldProperties){
                    var fieldPropertiesDefs=grid.def.exportMetadata.fieldProperties.map(function(propName, i){
                        return {name:propName, typeName:'text', isPk:!i};
                    });
                    var fieldPropertiesDepot=fieldsDef2Export.filter(function(fieldDef){
                        return coalesce(fieldDef.exportMetadata,grid.def.exportMetadata.exportAnyField,true);
                    }).map(function(fieldDef){
                        return {row:fieldDef};
                    });
                    populateTableXLS(exportFileInformationWs, fieldPropertiesDepot,fieldPropertiesDefs,i+1,1);
                }
            }
            populateTableXLS(ws, grid.depotsToDisplay, fieldsDef2Export);
            var sheet1name=grid.def.name.length>27?grid.def.name.slice(0,27)+'...':grid.def.name;
            var sheet2name=grid.def.name!=="metadata"?"metadata":"meta-data";
            wb.SheetNames=[sheet1name,sheet2name];
            wb.Sheets[sheet1name]=ws;
            exportFileInformationWs["!ref"]="A1:F100";
            wb.Sheets[sheet2name]=exportFileInformationWs;
            var wbFile = XLSX.write(wb, {bookType:'xlsx', bookSST:false, type: 'binary', cellDates:true});
            var blob = new Blob([s2ab(wbFile)],{type:"application/octet-stream"});
            mainDiv.setAttribute("current-state", "ready");
            var url = URL.createObjectURL(blob); 
            downloadElement.href=url;
            downloadElement.setAttribute("download", grid.def.name+".xlsx");
        };
    });
};

myOwn.dialogUpload = function dialogUpload(ajaxPath, ajaxParams, ajaxPrepareResultFun, showWithMiniMenu, messages, refresheable, acceptPhotos, optsNames){
    messages = changing(my.messages, messages||{})
    optsNames = optsNames || [];
    var doneFun = function doneFun(){
        var fileAttr={class:'import-button',type:'file',style:'min-width:400px'};
        if(acceptPhotos){
            fileAttr.accept='image/*';
        }
        var buttonFile=html.input(fileAttr).create();
        var buttonConfirmImport=html.input({class:'import-button',type:'button', value:messages.import}).create();
        buttonConfirmImport.disabled = true;
        var laodingIndicator=html.div({class:'indicator'},' ').create();
        var loadingBar=html.div({class:'progress-bar', style:'width:400px; height:8px;'},[laodingIndicator]).create();
        var divProgress=html.div({class:'result-progress', style:'width:400px; height:20px; margin:0px;'}).create();
        var progressIndicator=html.div({class:'indicator'},' ').create();
        var progressBar=html.div({class:'progress-bar', style:'width:400px; height:8px;'},[progressIndicator]).create();
        buttonFile.onchange = function(){
            buttonConfirmImport.disabled = false;
        }
        var displayProgressBar = function displayProgressBar(progress, progressIndicator){
            if(!progress){
                return;
            }
            if(progress.loaded){
                if(progress.lengthComputable){
                    progressIndicator.style.width=progress.loaded*100/progress.total+'%';
                    progressIndicator.title=Math.round(progress.loaded*100/progress.total)+'%';
                }else{
                    progressIndicator.style.backgroundColor='#D4D';
                    progressIndicator.title='N/D %';
                }
            }
            if(progress.message){
                divProgress.textContent=progress.message;
            }
        }
        var uploadingProgress=function(progress){
            displayProgressBar(progress, laodingIndicator);
        };
        var informProgress=function(progress){
            displayProgressBar(progress, progressIndicator);
        };
        buttonConfirmImport.addEventListener('click', function(){
            var files = buttonFile.files;
            buttonConfirmImport.value=my.messages.loading+'...';
            buttonConfirmImport.disabled=true;
            var eButton=function(result){
                buttonConfirmImport.disabled=false;
                if(result instanceof Error){
                    throw result;
                }
                return result;
            }
            bestGlobals.sleep(100).then(function(){
                var fun=typeof ajaxPath=="string"?my.ajax[ajaxPath]:(
                    ajaxPath.reduce(function(acum, name){
                        return acum[name];
                    },my.ajax)
                );
                return fun(changing(ajaxParams, {
                    files:files
                }),{uploading:uploadingProgress, informProgress:informProgress}).then(eButton,eButton);
            }).then(ajaxPrepareResultFun).then(this.dialogPromiseDone,this.dialogPromiseDone);
        });
        var buttons = {}
        optsNames.map((name, i)=>{
            var defValue = !!i;
            ajaxParams[name]=defValue;
            buttons[name] = html.input({type:'checkbox', checked:defValue}).create()
            buttons[name].onchange = function(){
                ajaxParams[name] = buttons[name].checked;
            }
        });
        simpleFormPromise({elementsList:[
            messages.importDataFromFile,
            buttonFile, 
            ...(optsNames.map(name => html.label([html.br(), buttons[name], messages[name]]).create())),
            html.br().create(),
            buttonConfirmImport,
            html.br().create(),
            loadingBar,
            divProgress,
            progressBar,
        ]}).then(function(message){
            return Promise.all([
                refresheable && refresheable.refresh(),
                alertPromise(message)
            ]);
        },function(err){
            return Promise.all([
                refresheable && refresheable.refresh(),
                my.alertError(err)
            ]);
        });
    }
    if(showWithMiniMenu){
        return {img:my.path.img+'import.png', value:true, label:my.messages.import, doneFun: doneFun};
    }else{
        doneFun();
    }
};

myOwn.TableAggregates={}
myOwn.TableAggregates.avg=function(){
    this.n=0;
    this.sum=new Big(0);
};
myOwn.TableAggregates.avg.prototype.acum=function acum(value){
    if(value!=null){
        this.n++;
        this.sum = this.sum.plus(value);
    }
}
myOwn.TableAggregates.avg.prototype.result=function result(){
    if(!this.n) return null;
    return this.sum.div(this.n);
}
myOwn.TableAggregates.sum = function(){
    myOwn.TableAggregates.avg.call(this);
}
myOwn.TableAggregates.sum.prototype = Object.create(myOwn.TableAggregates.avg.prototype);
myOwn.TableAggregates.avg.prototype.result=function result(){
    return this.sum;
}
myOwn.TableAggregates.count=function(){
    this.n=0;
};
myOwn.TableAggregates.count.prototype.acum=function acum(value){
    if(value!=null){
        this.n++;
    }
}
myOwn.TableAggregates.count.prototype.result=function result(){
    return this.n;
}
myOwn.TableAggregates.min=function(){
};
myOwn.TableAggregates.min.prototype.acum=function acum(value){
    if(value!=null){
        if(this.value==null || this.value>value){
            this.value = value;
        }
    }
}
myOwn.TableAggregates.min.prototype.result=function result(){
    return this.value;
}
myOwn.TableAggregates.max=function(){
};
myOwn.TableAggregates.max.prototype.acum=function acum(value){
    if(value!=null){
        if(this.value==null || this.value<value){
            this.value = value;
        }
    }
}
myOwn.TableAggregates.max.prototype.result=function result(){
    return this.value;
}
myOwn.TableAggregates.countTrue=function(){
    this.n=0;
};
myOwn.TableAggregates.countTrue.prototype.acum=function acum(value){
    if(value===true){
        this.n++;
    }
}
myOwn.TableAggregates.countTrue.prototype.result=function result(){
    return this.n;
}
myOwn.TableGrid.prototype.refreshAggregates = function refreshAggregates(){
    var grid = this;
    var my = grid.my;
    var aggData={};
    grid.def.fields.forEach(function(fieldDef){
        if(fieldDef.aggregate){
            aggData[fieldDef.name]=new myOwn.TableAggregates[fieldDef.aggregate]();
        }
    });
    grid.depots.forEach(function(depot){
        grid.def.fields.forEach(function(fieldDef){
            if(fieldDef.aggregate){
                aggData[fieldDef.name].acum(depot.row[fieldDef.name],depot.row);
            }
        })
    });
    grid.def.fields.forEach(function(fieldDef){
        if(fieldDef.aggregate && grid.dom.aggregate[fieldDef.name]){
            grid.dom.aggregate[fieldDef.name].setTypedValue(aggData[fieldDef.name].result());
        }
    })
    if(my.refreshErrorListWithAggregates){
        if(grid.def.layout.errorList){
            if(!grid.depotsToDisplay.length){
                grid.dom.caption.textContent=my.messages.thereAreNot+': '+grid.dom.caption.textContent;
                grid.dom.table.setAttribute('error-list','no-errors');
            }else{
                grid.dom.table.setAttribute('error-list','have-errors');
                // grid.dom.caption.style.backgroundColor='#F44';
            }
        }
    }
};

myOwn.TableGrid.prototype.prepareGrid = function prepareGrid(){
    var grid = this;
    var my = grid.my;
    grid.view.hiddenColumns=grid.view.hiddenColumns||grid.def.hiddenColumns||[];
    grid.def.fields.forEach(function(fieldDef){
        if(!fieldDef.visible){
            grid.view.hiddenColumns.push(fieldDef.name);
        }
        if(fieldDef.aggregate){
            grid.modes.withAggregateRow=true;
        }
    });
    grid.hideColumnsViaCss();
    var buttonInsert;
    var buttonCreateFilter;
    var buttonCreateFilterAdd;
    var buttonDestroyFilter;
    var buttonOrientation;
    var buttonMenu;
    var getSaveModeImgSrc=function(){ return my.path.img+(grid.modes.saveByField?'tables-update-by-field.png':'tables-update-by-row.png');};
    var buttonSaveModeImg=html.img({src:getSaveModeImgSrc()}).create();
    var buttonSaveMode;
    if(grid.def.allow.update){
        buttonSaveMode=html.button({class:'table-button'}, [buttonSaveModeImg]).create();
        buttonSaveMode.addEventListener('click',function(){
            grid.modes.saveByField = !grid.modes.saveByField;
            buttonSaveModeImg.src=getSaveModeImgSrc();
        });
    }
    if(grid.def.allow.insert && !grid.def.forInsertOnlyMode){
        buttonInsert=html.button({class:'table-button', "enter-clicks":true}, [
            html.img({
                src:my.path.img+'insert.png',
                alt:'INS',
                title:my.messages.insertRecordAtBottom
            })
        ]).create();
        grid.dom.buttonInsert=buttonInsert;
        buttonInsert.addEventListener('click', function(){
            var tr=grid.createRowInsertElements();
        });
    }
    if(grid.def.allow.filter && !grid.def.forInsertOnlyMode){
        buttonCreateFilter=html.button({class:'table-button', 'when-filter':'no', "skip-enter":true}, [
            html.img({
                src:my.path.img+'filter.png',
                alt:'FILTER',
                title:my.messages.filter
            })
        ]).create();
        buttonCreateFilter.addEventListener('click', function(){
            grid.view.filter=[grid.createRowFilter(0,[])];
        });
        buttonCreateFilterAdd=html.button({class:'table-button', 'when-filter':'yes', "skip-enter":true}, [
            html.img({
                src:my.path.img+'filter-line-add.png',
                alt:'FIL',
                title:my.messages.filterAdd
            })
        ]).create();
        buttonCreateFilterAdd.addEventListener('click', function(){
            grid.view.filter.push(grid.createRowFilter(1,[]));
        });
        buttonDestroyFilter=html.button({class:'table-button', 'when-filter':'yes', "skip-enter":true}, [
            html.img({
                src:my.path.img+'destroy-filter.png',
                alt:'FILTER OFF',
                title:my.messages.filterOff
            })
        ]).create();
        buttonDestroyFilter.addEventListener('click', function(){
            grid.destroyRowFilter(0);
            grid.updateFilterInfo('');
            grid.view.filter=[];
            grid.displayBody();
        });
    }
    if(grid.def.allow.orientation){
        buttonOrientation=html.button({class:'table-button'}, [
            html.img({
                src:my.path.img+'orientation-toggle.png',
                alt:'CARD',
                title:my.messages.orientationToggle,
            })
        ]).create();
        buttonOrientation.addEventListener('click',function(){
            grid.vertical = !grid.vertical;
            grid.prepareGrid();
            grid.displayGrid();
            grid.dom.table.setAttribute("my-orientation",grid.vertical?'vertical':'horizontal');
        });
    }
    buttonMenu=html.button({class:'table-button', "skip-enter":true}, [
        html.img({
            src:my.path.img+'menu-dots.png',
        })
    ]).create();
    grid.prepareMenu(buttonMenu);
    grid.columns=[new my.ActionColumnGrid({
        grid:grid, 
        actions:[
            /*buttonInsert,*//*buttonSaveMode,*/buttonCreateFilter,buttonCreateFilterAdd,buttonDestroyFilter,
            buttonOrientation,
            buttonMenu,
        ],
        actionsBottom:[
            buttonInsert
        ]
    })].concat(
        grid.def.detailTables.map(function(detailTableDef){ return new my.DetailColumnGrid({grid:grid, detailTableDef:detailTableDef}); })
    ).concat(
        grid.def.fields.filter(function(fieldDef){return fieldDef.visible}).map(function(fieldDef){ return new my.DataColumnGrid({grid:grid, fieldDef:fieldDef}); })
    // ).concat(
    //     [new my.SpecialColumnGrid({class:"empty-right-column"})]
    );
    if(grid.modes.withColumnDetails==null){
        grid.modes.withColumnDetails=grid.def.fields.some(function(fieldDef){ 
            return fieldDef.label!=fieldDef.title; 
        });
    }
    if(grid.vertical){
        grid.modes.withColumnDetails=false;
    }
    var createInfoColumnStructure = function(elementInfo){
        [
            {name:'displayFrom', value:'0'},
            {name:'elipsis', value:' ... '},
            {name:'displayTo', value:'?'},
            {name:'filterApplied', value:''},
            {name:'rowCount', value:''}
        ].forEach(function(info, i_info){
            elementInfo[info.name] = html.span(info.value).create();
            elementInfo.appendChild(elementInfo[info.name]);
        });
        elementInfo.displayTo.textContent = my.messages.loading;    
    }
    grid.dom.headInfo = html.th({class: 'head-info', colspan:grid.columns.length-1, "is-processing":"1"}).create();
    createInfoColumnStructure(grid.dom.headInfo);
    grid.dom.footInfo = html.th({colspan:grid.columns.length, "is-processing":"1"}).create();
    createInfoColumnStructure(grid.dom.footInfo);
    grid.actualName = (grid.def.gridAlias || grid.def.name) + (grid.connector.fixedFields.length ? '-' + JSON4all.toUrl(grid.connector.fixedFields.map(function(pair){ return pair.value; })) : '')
    var captionTitle = grid.def.title;
    grid.connector.fixedFields.forEach(function(pair){
        var toCaption = grid.def.field[pair.fieldName].toCaption ?? my.config.config['grid-smart-caption']
        if(toCaption && pair.value != null){
            var typeName = grid.def.field[pair.fieldName].typeName;
            captionTitle += ' '
            if(toCaption == 'labeled' || toCaption != 'alone' && (typeName == 'boolean' || typeName == 'integer' || typeName == 'bigint' || typeName == 'decimal')){
                captionTitle += grid.def.field[pair.fieldName].title + ':'
            }
            if(typeName == 'date'){
                var date = bestGlobals.date(new Date(pair.value))
                captionTitle += date.toDmy()
            }else{
                captionTitle += pair.value instanceof Date && pair.value.toDmy ? pair.value.toDmy() : pair.value.toString()
            }
        }
    });
    grid.dom.caption=html.caption({class: 'grid-caption'}, captionTitle).create();
    if(grid.vertical){
        grid.dom.table = html.table({"class":"my-grid", "my-table": grid.actualName},[
            grid.dom.caption,
            html.tbody(
                grid.columns.map(function(column){ 
                    return html.tr([
                        column.th(),
                        grid.modes.withColumnDetails?html.tr(grid.columns.map(function(column){ return column.thDetail(); })):null
                    ]); 
                })
            ),
            html.tfoot([
                html.tr([html.th([buttonInsert]),grid.dom.footInfo])
            ])
        ]).create();
    }else{
        var footRows;
        if(grid.modes.withAggregateRow){
            footRows=[
                html.tr(grid.columns.map(function(column){ return column.thAgg(); })),
                html.tr([html.th({class: 'foot-info-empty-col'}),grid.dom.footInfo])
            ];
        }else{
            footRows=[html.tr([html.th({class: 'foot-info-insert-col'}, [buttonInsert]),grid.dom.footInfo])];
        }
        grid.dom.table = html.table({"class":"my-grid", "my-table": grid.actualName},[
            grid.dom.caption,
            html.thead([
                html.tr({class: 'head-info-row'}, [html.th({class: 'head-info-empty-col'}),grid.dom.headInfo]),
                html.tr(grid.columns.map(function(column){ return column.th(); })),
                grid.modes.withColumnDetails?html.tr(grid.columns.map(function(column){ return column.thDetail(); })):null,
            ]),
            html.tbody(),
            html.tfoot(footRows)
                // [html.tr([html.th([buttonInsert]),grid.dom.footInfo])]
        ]).create();
    }
    grid.setInheritedFields = function(depot, filterFun){
        var promiseArray = [];
        grid.def.foreignKeys.concat(grid.def.softForeignKeys).filter(filterFun||(x=>x))
        .filter(x=>(x.inheritFieldsMode ?? my.config.config['inherit-fields-mode']) != 'no-inherit' ).forEach(function(fkDef){
            var fixedFields = fkDef.fields.map(function(field){
                return {fieldName: field.target, value: depot.row[field.source]};
            })
            var dummyElement = html.div().create();
            var Connector = my.offline.mode?my.TableConnectorLocal:my.TableConnector;
            var myConnector = new Connector({
                my:my, 
                tableName: fkDef.references,
                getElementToDisplayCount:function(){ return dummyElement }
            }, {fixedFields: fixedFields});
            myConnector.getStructure();
            //cargo registro y actualizo displayFields
            promiseArray.push(
                myConnector.getData().then(function(data){
                    var referencedRow = data[0];
                    fkDef.displayFields.forEach(function(displayFieldName){
                        var lookupValue=referencedRow?referencedRow[displayFieldName]:null;
                        var fieldName = fkDef.alias + '__' + displayFieldName;
                        depot.row[fieldName]=lookupValue;
                        if(depot.rowControls[fieldName]){
                            depot.rowControls[fieldName].setTypedValue(lookupValue);
                        }
                    })
                })
            )
        });
        return Promise.all(promiseArray);
    }
    grid.dom.main.innerHTML='';
    grid.dom.main.appendChild(grid.dom.table);
};

myOwn.specialDefaultValue={
    current_date:function(){ return myOwn.config.currentDate||bestGlobals.date.today(); },
    next_number:function(fieldName, aboveDepot, belowDepot){ 
        var manager = aboveDepot.manager ?? belowDepot.manager
        if(!manager){
            return 1;
        }else{
            var max = 0;
            for(var depot of manager.depots){
                if(Number(depot.row[fieldName]) > max){
                    max = Number(depot.row[fieldName])
                }
            }
            return max + 1;
        }
        return belowDepot.row[fieldName]?belowDepot.row[fieldName]+1:(
            aboveDepot.row[fieldName]?aboveDepot.row[fieldName]-1:1
        ); 
    }
}

myOwn.TableGrid.prototype.createRowInsertElements = function createRowInsertElements(aboveDepot, belowDepot){
    var grid = this;
    if(!belowDepot){
        if(aboveDepot){
            if(belowDepot=aboveDepot.tr.previousSibling){
                belowDepot=aboveDepot.tr.previousSibling.depot;
            }
        }else{
            var lastTBody=grid.dom.table.tBodies[grid.dom.table.tBodies.length-1];
            var lastTr=lastTBody.rows[aboveDepot ?? lastTBody.rows.length-1];
            if(lastTr){
                belowDepot=lastTr.depot;
            }
        }
    }
    var position;
    if(grid.vertical){
        position=1;
    }else{
        if(aboveDepot){
            var aboveTr = aboveDepot.tr;
            position = ('sectionRowIndex' in aboveTr?
                aboveTr.sectionRowIndex:
                aboveTr.rowIndex-grid.dom.table.tHead.rows.length
            );
        }else{
            position = aboveDepot ?? grid.dom.table.tBodies[0].rows.length;
        }
        /*
        while(
            position<grid.dom.table.tBodies[0].rows.length && 
            grid.dom.table.tBodies[0].rows[position].isDetail
        ){
            position++;
        }
        */
    }
    var depotForInsert = grid.createDepotFromRow({$allow:{delete:true, update:true}}, 'new');
    grid.connector.fixedFields.forEach(function(pair){
        if(!pair.range && grid.def.field[pair.fieldName].inTable !== false){
            depotForInsert.row[pair.fieldName] = pair.value;
            depotForInsert.rowPendingForUpdate[pair.fieldName] = pair.value;
        }
    });
    grid.def.fields.forEach(function(fieldDef){
        var value=null;
        if('defaultValue' in fieldDef){
            value=fieldDef.defaultValue;
        }
        if('specialDefaultValue' in fieldDef){
            //********************* REVISAR PORQUE ESTABA LA LÍNEA COMENTADA*************
            //value=my.specialDefaultValue[fieldDef.specialDefaultValue](fieldDef.name, depot);
            value=my.specialDefaultValue[fieldDef.specialDefaultValue](fieldDef.name, aboveDepot||{row:{}}, belowDepot||{row:{}});
        }
        if(value!==null){
            depotForInsert.row[fieldDef.name] = value;
            depotForInsert.rowPendingForUpdate[fieldDef.name] = value;
        }
    });
    //TODO: mejorar la posición dentro del splice o concluir que no sirve el splice
    grid.depots.splice(Math.min(grid.depots.length,Math.max(0,position)),0,depotForInsert);
    var newRow = grid.createRowElements(position, depotForInsert);
    grid.updateRowData(depotForInsert,true);
    return newRow;
};
myOwn.TableGrid.prototype.updateTotals = function updateTotals(displayFromContent, displayToContent){
    this.dom.headInfo.displayFrom.textContent = displayFromContent;
    this.dom.headInfo.displayTo.textContent = displayToContent;
    this.dom.footInfo.displayFrom.textContent = displayFromContent;
    this.dom.footInfo.displayTo.textContent = displayToContent;
};
myOwn.TableGrid.prototype.updateFilterInfo = function updateFilterInfo(filterAppliedContent){
    this.dom.headInfo.filterApplied.textContent = filterAppliedContent;
    this.dom.footInfo.filterApplied.textContent = filterAppliedContent;
};
myOwn.TableGrid.prototype.updateRowCountHTML = function updateRowCountHTML(HTML){
    this.dom.headInfo.rowCount.innerHTML=HTML;
    this.dom.footInfo.rowCount.innerHTML=HTML;
};

var changeIoStatus = function changeIoStatus(depot,newStatus, objectWithFieldsOrListOfFieldNames, title){
    var fieldNames=typeof objectWithFieldsOrListOfFieldNames === "string"?[objectWithFieldsOrListOfFieldNames]:(
        objectWithFieldsOrListOfFieldNames instanceof Array?objectWithFieldsOrListOfFieldNames:Object.keys(objectWithFieldsOrListOfFieldNames)
    );
    fieldNames.forEach(function(name){ 
        if(depot.rowControls[name]){
            var td=depot.rowControls[name];
            td.setAttribute('io-status', newStatus); 
            if(title){
                td.title=title;
            }else{
                td.title='';
            }
        }
    });
};
myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
    var grid = this;
    var tbody = grid.dom.table.tBodies[0];
    grid.updateRowData = function updateRowData(depot, skipUpdateStatus){
        var grid = this;
        var forInsert = false; // not define how to detect
        var tr = depot;
        grid.setRowStyle(depot,depot.row, skipUpdateStatus);
        grid.def.fields.filter(function(fieldDef){
            return fieldDef.visible;
        }).forEach(function(fieldDef){
            var td = depot.rowControls[fieldDef.name];
            var editable=depot.allow.update !== false && grid.connector.def.allow.update /* && !grid.connector.fixedField[fieldDef.name]*/ && (forInsert?fieldDef.allow.insert:fieldDef.allow.update && grid.connector.def.field[fieldDef.name].allow.update);
            td.disable(!editable);
            if(fieldDef.clientSide){
                if(!td.clientSidePrepared){
                    grid.my.clientSides[fieldDef.clientSide].prepare(depot, fieldDef.name);
                    td.clientSidePrepared=true;
                }
                if(typeof grid.my.clientSides[fieldDef.clientSide].update === "function"){
                    grid.my.clientSides[fieldDef.clientSide].update(depot, fieldDef.name);
                }
            }
            if(!skipUpdateStatus && (!fieldDef.clientSide || grid.my.clientSides[fieldDef.clientSide].update===true)){
                var newValue=coalesce(depot.row[fieldDef.name],null);
                var actualValue=td.getTypedValue();
                if(!sameValue(newValue,actualValue)){
                    td.setTypedValue(newValue); /// ANALISIS SET-TYPED-VALUE
                }
            }
        });
        if(grid.def.clientSide){
            if(!depot.clientSidePrepared && grid.my.clientSides[grid.def.clientSide].prepare){
                grid.my.clientSides[grid.def.clientSide].prepare(depot);
            };
            depot.clientSidePrepared=true;
            grid.my.clientSides[grid.def.clientSide].update(depot);
        }
    };
    var saveRow = function(depot, opts){
        if(!('saving' in depot)){
            depot.saving = Promise.resolve();
        }
        changeIoStatus(depot,'updating',depot.rowPendingForUpdate);
        depot.saving = depot.saving.then(function(){
            if(!Object.keys(depot.rowPendingForUpdate).length){
                return Promise.resolve();
            }
            if(depot.status==='new'){
                var specialMandatories=grid.def.specialValidator?myOwn.validators[grid.def.specialValidator].getMandatoryMap(depot.row):{};
                var mandatoryOmitted=function(fieldDef){
                    return (specialMandatories[fieldDef.name] || fieldDef.nullable!==true && fieldDef.isPk || fieldDef.nullable===false) 
                        && depot.row[fieldDef.name]==null 
                        && (grid.connector.fixedFields.find(function(pair){
                                return !pair.range && pair.fieldName===fieldDef.name
                            })||{}).value == null
                }
                //var cual=grid.def.fields.filter(mandatoryOmitted)
                if(grid.def.fields.some(mandatoryOmitted)){
                    var where = depot.actionButton.insert ?? depot.tr ?? document.getElementById('main-logo') ?? document.body;
                    if(where){
                        where.title = myOwn.messages.mandatoryFieldOmited + grid.def.fields.filter(mandatoryOmitted).map(f=>f.name).join(', ');
                    }
                    return Promise.resolve(); // no grabo todavía
                };
            }
            var timeStamp = my.setTimeStamp(depot.row);
            return grid.connector.saveRecord(depot, opts).then(function(result){
                if (my.skipTimeStamp(depot.row, timeStamp)) return;
                grid.depotRefresh(depot,result);
            }).catch(function(err){
                changeIoStatus(depot,'error',depot.rowPendingForUpdate,err.message);
            });
        });
    };
    grid.retrieveRowAndRefresh = function retrieveRowAndRefresh(depot, opts){
        // var sendedForUpdate = depot.my.cloneRow(depot.rowPendingForUpdate);
        return my.ajax.table_data({
            table:depot.def.name,
            fixedFields:grid.def.primaryKey.map(function(fieldName, i){ 
                return {fieldName:fieldName, value:depot.primaryKeyValues[i]};
            }),
            pick:grid.def.pick,
            retrieveIgnoringWhere: opts?opts.retrieveIgnoringWhere:false
        }).then(function(result){
            grid.depotRefresh(depot,{updatedRow:result[0], sendedForUpdate:{}}, opts);
        })
    }
    grid.setRowStyle = function setRowStyle(depot, row, skipUpdateStatus){
        if(!skipUpdateStatus){
            depot.status = 'loaded';
            depot.primaryKeyValues = grid.def.primaryKey.map(function(fieldName){ 
                return depot.row[fieldName]; 
            });
            /*
            depot.lastsPrimaryKeyValues = depot.primaryKeyValues.slice(0);
            var i=0;
            while(i<grid.def.primaryKey.length && depot.connector.fixedField[grid.def.primaryKey[i]]){
                depot.lastsPrimaryKeyValues.shift();
                i++;
            }
            */
            depot.lastsPrimaryKeyValues = depot.primaryKeyValues.filter((_,i) => depot.connector.fixedField[grid.def.primaryKey[i]] == null);
            if(depot.lastsPrimaryKeyValues.length==1){
                depot.lastsPrimaryKeyValues=depot.lastsPrimaryKeyValues[0];
            }else{
                depot.lastsPrimaryKeyValues=JSON.stringify(depot.lastsPrimaryKeyValues);
            }
            depot.tr.setAttribute('pk-values',JSON.stringify(depot.primaryKeyValues));
            grid.def.layout.styleColumns.forEach(function(fieldName){ 
                depot.tr.setAttribute('column-'+fieldName,JSON.stringify(depot.row[fieldName]));
            });
        }
        if(row.$dirty){
            var tdActions = depot.tr.querySelector('.grid-th-actions');
            tdActions.style.backgroundImage='url('+my.path.img+'/not-sync.png)'
            tdActions.style.backgroundSize='contain'
            tdActions.style.backgroundRepeat='no-repeat'
        }
    }
    grid.depotRefresh = function depotRefresh(depot,result,opts){
        // ¡ATENCIÓN!: esta función no debe despleegar, llama a updateRowData, ahí se despliega
        opts=opts||{};
        upadteNumberOfRows(depot,grid);
        var retrievedRow = result.updatedRow;
        for(var fieldName in retrievedRow){
            if(!/^\$/.test(fieldName)){
                if(grid.def.field[fieldName] && (!grid.def.field[fieldName].clientSide || grid.def.field[fieldName].serverSide && grid.def.field[fieldName].inTable!==false)){
                    if(depot.rowControls[fieldName]){
                        var value = depot.rowControls[fieldName].getTypedValue();
                        if(!sameValue(depot.row[fieldName], value)){
                            if(grid.def.field[fieldName].allow.update){
                                depot.rowPendingForUpdate[fieldName] = value;
                            }
                            depot.row[fieldName] = value;
                        }
                    }
                }
                if(fieldName in depot.rowPendingForUpdate){
                    var source = fieldName in result.sendedForUpdate?result.sendedForUpdate:depot.retrievedRow;
                    if(sameValue(depot.rowPendingForUpdate[fieldName], retrievedRow[fieldName])){
                        // ok, lo que viene coincide con lo pendiente
                        delete depot.rowPendingForUpdate[fieldName];
                        changeIoStatus(depot,'temporal-ok', fieldName);
                        /*jshint loopfunc: true */
                        setTimeout(function(fieldName){
                            changeIoStatus(depot,'ok', fieldName);
                        },3000,fieldName);
                        /*jshint loopfunc: false */
                    }else if(sameValue(retrievedRow[fieldName], source[fieldName])){
                        // ok, si bien lo que viene no coincide con lo pendiente que sigue pendiente, 
                        // sí coincide con lo que estaba antes de mandar a grabar, 
                        // entonces no hay conflicto el usuario sabe sobre qué está modificando
                        //REVISAR SI SE QUIERE REVERTIR
                    }else{
                        // no coincide con lo pendiente ni con lo anterior, 
                        // hay un conflicto con el conocimiento del usuario que modificó algo que estaba en otro estado
                        changeIoStatus(depot,'write-read-conflict', 
                            fieldName, 
                            myOwn.messages.anotherUserChangedTheRow+'. \n'+
                            myOwn.messages.oldValue+': '+source[fieldName]+(
                                whenMergeOverride?'':'\n'+myOwn.messages.actualValueInDB+': '+retrievedRow[fieldName]
                            )
                            // TODO: sería bueno agregar algúna opción de UNDO!
                        );
                        if(whenMergeOverride){
                            depot.row[fieldName] = retrievedRow[fieldName];
                            if(depot.rowControls[fieldName]){
                                //**VERIFICAR**//  depot.rowControls[fieldName].setTypedValue(retrievedRow[fieldName]);
                            }
                            delete depot.rowPendingForUpdate[fieldName];
                        }
                    }
                }else{
                    if(!sameValue(retrievedRow[fieldName], depot.row[fieldName])){
                        depot.row[fieldName] = retrievedRow[fieldName];
                        if(depot.rowControls[fieldName]){
                            changeIoStatus(depot,'background-change', fieldName);
                            //**VERIFICAR**// */ depot.rowControls[fieldName].setTypedValue(retrievedRow[fieldName]);
                            /*jshint loopfunc: true */
                            setTimeout(function(fieldName){
                                changeIoStatus(depot,'ok', fieldName);
                            },3000,fieldName);
                            /*jshint loopfunc: false */
                        }
                    }
                }
            }else if(!/^\$allow\./.test(fieldName)){
                depot.allow[fieldName.replace(/^\$allow\./,'')] = retrievedRow[fieldName];
            }
        }
        depot.retrievedRow = retrievedRow;
        grid.updateRowData(depot);
        if(!opts.noDispatchEvents){
            depot.tr.dispatchEvent(new CustomEvent('savedRowOk'));
            grid.dom.main.dispatchEvent(new CustomEvent('savedRowOk'));
        }
        grid.refreshAggregates();
        for(var detailControl in depot.detailControls){
            if(depot.detailControls[detailControl].tr){                
                depot.detailControls[detailControl].refreshAllRowsInGrid()
            }
        };
    }
    grid.createExtraRow = function createExtraRow(depot, tbody){
        var tr = html.tr({class:'extra-row'}, [html.td().create()]).create();
        depot.extraRows.push(tr);
        tbody.appendChild(tr);
    }
    grid.createRowElements = function createRowElements(iRow, depot){
        var grid = this;
        var forInsert = iRow>=0;
        var tr={};
        depot.colNumber = null;
        if(grid.vertical){
            tr=grid.dom.table.rows[0];
            depot.colNumber = grid.dom.table.rows[0].childNodes.length;
        }else{
            tr = grid.my.insertRow({section:tbody, iRow:iRow, smooth:depot.status==='new'?{ 
                colCount:grid.def.detailTables.length + grid.def.fields.length
            }:false});
        }
        depot.tr = tr;
        depot.extraRows=[];
        grid.columns.filter(function(column){
            return column.fieldDef?column.fieldDef.visible:true
        }).forEach(function(column, iColumn){
            tr.appendChild(column.td(depot, iColumn, tr, saveRow));
            if(grid.vertical){ 
                tr = tr.nextSibling;
            }
        });
        if(!grid.vertical){ 
            tr.addEventListener('focusout', function(event){
                tr.removeAttribute('current_line');
                if(event.target.parentNode != (event.relatedTarget||{}).parentNode ){
                    depot.connector.recordLeave(depot).then(function(result){
                    });
                    if(Object.keys(depot.rowPendingForUpdate).length){
                        saveRow(depot);
                    }
                }
            });
            tr.addEventListener('focusin',function(event){
                tr.setAttribute('current_line','1');
                if(event.target.parentNode != (event.relatedTarget||{}).parentNode ){
                    return depot.connector.recordEnter(depot).then(function(result){
                    });
                }
            });
        }
        if(grid.def.layout.extraRows > 0){
            for(var i = 0; i < grid.def.layout.extraRows; i++){
                grid.createExtraRow(depot, tbody);
            }
        }
        if(iRow===-1){
            depot.detailRows.forEach(function(detailTr){
                var detailControl = depot.detailControls[detailTr.detailTableNameAndAbr];
                detailControl.tr = detailTr;
                if(detailControl.show){
                    tbody.appendChild(detailTr);
                    detailControl.img.src=my.path.img+'detail-contract.png';
                    detailControl.img.title=my.messages.lessDetails;
                }else{
                    detailControl.img.src=my.path.img+'detail-expand.png';
                    detailControl.img.title=my.messages.details;
                }
            });
        }
        grid.updateTotals(grid.depots.length?1:0, grid.depots.length);
        if(tr){ // en despliegue vertical no hay un tr definido.
            tr.depot = depot;
        }
        return depot;
    };
    grid.destroyRowFilter = function destroyRowFilter(){
        (grid.hasFilterRow||[]).forEach(function(tr){
            grid.dom.table.setAttribute('has-filter',0);
            grid.dom.table.tHead.removeChild(tr);
        });
        delete grid.hasFilterRow;
    };
    grid.dom.table.setAttribute('has-filter',0);
    grid.createRowFilter = function createRowFilter(otherRow, filterColumns){
        var grid = this;
        if(!otherRow && grid.hasFilterRow){
            // return true;
        }
        grid.sizesForFilters=Array.prototype.map.call(grid.dom.table.rows[0].cells,function(cell){
            return cell.offsetWidth;
        });
        // HACIENDO
        var depot = {
            special: 'filter',
            my: grid.my,
            def: grid.def,
            connector: grid.connector, 
            manager: grid,
            rowControls:{},
            row: {},
            rowSymbols: {},
            isFilterPending:false,
            tr: tr,
            actionButton:{},
            firstFilter: !otherRow
        };
        if(!grid.hasFilterRow){
            grid.hasFilterRow=[];
        }
        if(filterColumns){
            filterColumns.forEach(function(filterColumn){
                depot.row[filterColumn.column]=filterColumn.value;
                depot.rowSymbols[filterColumn.column]=filterColumn.operator;
            });
        }
        var tr=html.tr({'class':'filter-line'}, grid.columns.map(function(column, iColumn){
            return column.thFilter(depot, iColumn);
        })).create();
        grid.hasFilterRow.push(tr);
        grid.dom.table.setAttribute('has-filter',1);
        if(grid.dom.table.tHead){ //TODO: el filtro para verticales debe organizarse sin tr
            grid.dom.table.tHead.appendChild(tr);
        }
        return depot;
    };
    grid.filterDepots = function filterDepots(depotsToFilter){
        var grid = this;
        var filterRows = grid.view.filter;
        var depotsToDisplay;
        if(filterRows && filterRows.length){
            depotsToDisplay = depotsToFilter.filter(function(depot,i){
                var iFilter=0;
                while(iFilter<filterRows.length){
                    var filterData=filterRows[iFilter];
                    var partialOk=true;
                    var columnsCompared = 0;
                    for(var column in depot.row){
                        // var compSymb = filterData.rowSymbols && filterData.rowSymbols[column] || filterData[column].operator;
                        var compSymb = filterData.rowSymbols[column];
                        if(compSymb && my.comparator[compSymb] && (my.comparatorParameterNull[compSymb] || filterData.row[column] != null)){
                            var isSatisfied=my.comparator[compSymb](depot.row[column],filterData.row[column]);
                            if(!isSatisfied){
                                partialOk=false;
                            }
                            columnsCompared++;
                        }
                    }
                    if(partialOk && columnsCompared){
                        return true;
                    }
                    iFilter++;
                }
                return false;
            });
        }else{
            depotsToDisplay = depotsToFilter;
        }
        return depotsToDisplay;
    }
    grid.displayBody=function displayBody(){
        var grid = this;
        var depotsToDisplay = grid.filterDepots(grid.depots);
        grid.sortDepotsToDisplay = function sortDepotsToDisplay(depotsToDisplay){
            if(grid.view.sortColumns.length>0){
                return depotsToDisplay.sort(function(depot1, depot2){ 
                    grid.view.sortColumns.forEach(function(orderColumn){
                        if(!grid.def.field[orderColumn.column]){
                            my.log(orderColumn.column+' order no está en '+grid.def.name);
                        }
                        orderColumn.func=myOwn.sortMethods[grid.def.field[orderColumn.column].sortMethod||'default'];
                    })
                    return bestGlobals.compareForOrder(grid.view.sortColumns)(depot1.row, depot2.row);
                });
            }
        }
        grid.sortDepotsToDisplay(depotsToDisplay);
        grid.displayRows = function displayRows(fromRowNumber, toRowNumber, adding){
            var grid = this;
            if(!adding){
                if(grid.vertical){
                    // recorrer todos los tr del tbody y dejar solo las primeras columnas?
                    Array.prototype.forEach.call(tbody.rows,function(tr){
                        var howManyCellsMayNotBeDeleted=1;
                        while(tr.cells.length>howManyCellsMayNotBeDeleted){
                            tr.removeChild(tr.cells[howManyCellsMayNotBeDeleted]);
                        }
                    });
                }else{
                    tbody.innerHTML='';
                }
            }
            /*jshint loopfunc: true */
            for(var iRow=fromRowNumber; iRow<toRowNumber; iRow++){
                (function(depot){
                    var tr=grid.createRowElements(-1, depot);
                    grid.updateRowData(depot);
                })(depotsToDisplay[iRow]);
            }
            /*jshint loopfunc: false */
            grid.updateTotals(depotsToDisplay.length?1:0, iRow);
            grid.updateRowCountHTML('');
            if(iRow<depotsToDisplay.length){
                var addButtonRest = function addButtonRest(toNextRowNumber){
                    var createRestButtonInto = function(domElement){
                        var buttonRest=html.button({class:'foot-info', "enter-clicks":true},"+..."+toNextRowNumber).create();
                        domElement.appendChild(html.span('  ').create());
                        domElement.appendChild(buttonRest);
                        return buttonRest;
                    }
                    var buttonRestClickFun = function(withFocus){
                        grid.displayRows(iRow, toNextRowNumber, true);
                        if(withFocus){
                            my.focusFirstColumnOf(grid.dom.table.tBodies[0].rows[iRow]);
                        }
                    };
                    var buttonRestHead = createRestButtonInto(grid.dom.headInfo.rowCount);
                    var buttonRestFoot = createRestButtonInto(grid.dom.footInfo.rowCount);
                    buttonRestHead.addEventListener('click', buttonRestClickFun.bind(this, false));
                    buttonRestFoot.addEventListener('click', buttonRestClickFun.bind(this, true));
                };
                my.displayCountBreaks.forEach(function(size, iSize){
                    var cut=(iRow+size) - (iRow+size) % size;
                    if(cut*5<=depotsToDisplay.length*3 && (iSize==my.displayCountBreaks.length-1 || cut*5<=my.displayCountBreaks[iSize+1]*3)){
                        addButtonRest(cut);
                    }
                });
                addButtonRest(depotsToDisplay.length);
                if(grid.dom.buttonInsert){
                    grid.dom.buttonInsert.style.visibility='hidden';
                }
            }else{
                if(grid.dom.buttonInsert){
                    grid.dom.buttonInsert.style.visibility='visible';
                }
            }
        };
        var linesToDisplay=(
            depotsToDisplay.length<=(grid.def.firstDisplayOverLimit||myOwn.firstDisplayOverLimit)?
            depotsToDisplay.length:
            (grid.def.firstDisplayCount||my.firstDisplayCount)
        );
        grid.depotsToDisplay=depotsToDisplay;
        grid.displayRows(0, linesToDisplay);
    };
    if(grid.def.filterColumns.length){
        var filterRows=grid.def.filterColumns[0] instanceof Array?grid.def.filterColumns:[grid.def.filterColumns];
        grid.view.filter=[];
        filterRows.forEach(function(filterColumns,iRow){
            grid.view.filter.push(grid.createRowFilter(iRow,filterColumns));
        });
    }
    grid.displayBody();
};

myOwn.TableGrid.prototype.displayAsDeleted = function displayAsDeleted(depot, mode){
    var grid = this;
    var fast = mode == 'change-ff'
    if (mode == 'unknown' && myOwn.config.config['grid-row-retain-moved-or-deleted']) {
        depot.tr.setAttribute('not-here', 'yes'); 
    } else {
        var position = depot.tr ? Math.min(grid.depots.length,Math.max(0,depot.tr.sectionRowIndex)) : 0;
        if(grid.depots[position] !== depot){
            position = grid.depots.indexOf(depot);
        }
        if(position>=0){
            grid.depots.splice(position,1);
        }
        if(grid.vertical){
            var compareColNumberFun = function compareColNumberFun(a, b) {
                var colNumberA = a.colNumber;
                var colNumberB = b.colNumber;
                return(colNumberA > colNumberB)?1:((colNumberA < colNumberB)?-1:0)
            }
            var i = 0;
            Array.prototype.forEach.call(grid.dom.table.rows,function(tr){
                if(i < grid.dom.table.rows.length-1 && tr.childNodes[depot.colNumber]){
                    depot.my.fade(tr.childNodes[depot.colNumber]);
                }
                i++;
            });
            var depots = grid.depots.sort(compareColNumberFun);
            for(var j = depot.colNumber; j <= depots.length; j++){
                depots[j-1].colNumber = j;
            }
        }else{
            if(depot.tr){
                depot.my.fade(depot.tr, {fast});
                for(var detailControl in depot.detailControls){
                    if(depot.detailControls[detailControl].tr){
                        depot.my.fade(depot.detailControls[detailControl].tr, {fast});
                    }
                };
            }
        }
    }
    grid.updateTotals(grid.depots.length?1:0, grid.depots.length);
};

myOwn.confirmDelete=function confirmDelete(depot, opts){
    return depot.my.showQuestion(
        depot.my.messages.Delete+' '+(depot.primaryKeyValues === false ? depot.my.messages.newUnsavedRow : JSON.stringify(depot.primaryKeyValues))+' ?', 
        {askForNoRepeat:depot.my.messages.Delete+', '+depot.def.name}
    );
}

myOwn.tableAction={
    "insert":{
        img: myOwn.path.img+'insert.png',
        alt: "INS",
        titleMsg: 'insertAbove',
        actionRow: function(depot){
            return depot.manager.createRowInsertElements(depot);
        }
    },
    "delete":{
        img: myOwn.path.img+'delete.png',
        alt: "DEL",
        titleMsg: 'deleteRecord',
        actionRow: function(depot, opts){
            return depot.my.confirmDelete(depot, opts).then(function(result){
                if(result){
                    return depot.connector.deleteRecord(depot, changing({reject:false},opts)).then(function(){
                        depot.manager.displayAsDeleted(depot, 'deleted');
                        depot.manager.refreshAggregates();
                    }).catch(depot.my.alertError);
                }
            });
        }
    },
    "vertical-edit":{
        img: myOwn.path.img+'vertical-edit.png',
        alt: "CARD",
        titleMsg: 'verticalEdit',
        actionRow: function(depot){
            var grid = depot.manager;
            if(grid.vertical){
                grid.vertical = false;
                if(grid.allDepots){
                    grid.depots = grid.allDepots;
                }
                grid.prepareGrid();
                grid.displayGrid();
            }else{
                grid.allDepots = grid.depots;
                grid.depots = [depot];
                grid.vertical = true;
                grid.prepareGrid();
                grid.displayGrid();
            }
            grid.dom.table.setAttribute("my-orientation",grid.vertical?'vertical':'horizontal');
        }
    }
};

myOwn.conditions={};

myOwn.clientSides={
    newPass:{
        prepare: function(depot, fieldName){
            var td=depot.rowControls[fieldName];
            td.disable(false);
            TypedControls.adaptElement(td, {typeName:'text'});
            td.addEventListener('update', function(event){
                var newPass = td.getTypedValue();
                if(newPass.trim()){
                    td.setAttribute('io-status','updating');
                    depot.row[fieldName] = newPass;
                    var my=depot.manager.my;
                    my.ajax.admin_chpass({
                        user:depot.row[depot.manager.def.primaryKey[depot.manager.def.primaryKey.length-1]],
                        newpass:newPass
                    }).then(function(){
                        td.setAttribute('io-status','temporal-ok');
                        setTimeout(function(){
                            td.setAttribute('io-status','ok');
                        },3000);
                    },function(err){
                        td.setAttribute('io-status','error');
                        td.title=err.message;
                    });
                }
            }, true);
        }
    },
    displayUrl:{
        cellDef:{tagName:'a'},
        update:function(depot, fieldName){
            var value = depot.row[fieldName];
            depot.rowControls[fieldName].setTypedValue(value);
            depot.rowControls[fieldName].href = value;
        },
        prepare:function(depot, fieldName){
        }
    },
    $lock:{
        update:true,
        prepare:function(depot, fieldName){
            depot.rowControls[fieldName].addEventListener('update',function(){
                var control = this;
                var valor = control.getTypedValue();
                if((valor=='B' || valor=='🔑') && "no estaba lockeado"){
                    control.setTypedValue('⌚');
                    var tokenPromise = Promise.resolve();
                    var token = localStorage.getItem('token');
                    if(!token){
                        tokenPromise = tokenPromise.then(function(){
                            return my.ajax.token_get({
                                useragent:my.config.useragent,
                                username: my.config.username
                            }).then(function(result){
                                token = result.token;
                                localStorage.setItem('token', result.token);
                            });
                        })
                    }
                    tokenPromise = tokenPromise.then(function(){
                        my.ajax.table_record_lock({
                            table:depot.def.name,
                            primaryKeyValues:depot.primaryKeyValues,
                            token:token,
                            softLock: false
                        }).then(function(result){
                            var promiseArray = [];
                            var tables=[depot.def.name].concat(depot.def.offline.details)
                            tables.forEach(function(name,i){
                                promiseArray.push(
                                    my.ldb.putMany(name,result.data[i])
                                );
                            })
                            return Promise.all(promiseArray).then(function(){
                                control.setTypedValue('🔑');
                            })
                        })
                    })
                }
            })
        }
    }
};

myOwn.references={};

myOwn.getReference = function getReference(referenceName, opts){
    opts = changing({
        getValue: function getValue(row){
            return row[reference.tableDef.primaryKey[reference.tableDef.primaryKey.length-1]];
        },
        getLabels: function getLabels(row, includePk){
            var lookupFields = reference.tableDef.lookupFields || reference.tableDef.nameFields;
            return (includePk || !lookupFields.length?
                this.tableDef.primaryKey:[]
            ).concat(lookupFields).map(function(fieldName){
                return row[fieldName];
            });
        },
        getLabel: function getLabel(row){
            return this.getLabels(row).join(', ');
        },
        getImage: function getImage(row){
            return row.image?'img/'+row.image:'';
        },
        fixedFields:[]
    },opts||{});
    var reference={};
    if(!my.references[referenceName] || opts.fixedFields.length){
        var dummyElement = html.div().create();
        var Connector = my.offline.mode?my.TableConnectorLocal:my.TableConnector; 
        var connector=new Connector({
            my:my, 
            tableName: referenceName, 
            getElementToDisplayCount:function(){ return dummyElement; },
            activeOnly: true
        }, {fixedFields:opts.fixedFields});
        connector.getStructure();
        var dataReady=connector.getData().then(function(rows){
            reference.Allrows=rows;
            reference.tableDef=connector.def;
            if(reference.tableDef.activeRecords){
                rows = rows.filter(function(row){
                    return row[reference.tableDef.activeRecords.fieldName];
                });
            }
            reference.rows=rows;
            reference.getValue = opts.getValue;
            reference.getLabels = opts.getLabels;
            reference.getLabel = opts.getLabel;
            reference.getImage = opts.getImage;
            return rows;
        });
        reference=my.references[referenceName]={
            connector:connector,
            dataReady:dataReady
        };
    }else{
        reference=my.references[referenceName]
    }
    return reference;
}

myOwn.autoSetupFunctions.push(function autoSetupMyTables(){
    var my=this;
    TypedControls.Expanders.unshift(myOwn.ExpanderReferences);
    TypedControls.Expanders.unshift(myOwn.ExpanderJsonReadOnly);
});

myOwn.ExpanderJsonReadOnly={
    autoExpand:true,
    whenReadOnly:true,
    whenType: function(typedControl){ 
        var typeInfo = typedControl.controledType.typeInfo;
        return typeInfo.typeName === 'jsonb';
    },
    dialogInput:function(typedControl, opts){
        var typer = typedControl.controledType;
        var value = typedControl.getTypedValue();
        if(typedControl.disabled){
            var div=html.div({class:'json-result'}).create();
            my.agregar_json(div, value);
            return alertPromise(div,{
                underElement:typedControl,
                withCloseButton:false,
            }).then(function(){return false;});
        }
        return promptPromise(typer.typeInfo.label||'', actualValue,{
            underElement:typedControl,
            withCloseButton:false,
        }).then(function(text){
            var value=typer.fromLocalString(text);
            typedControl.setTypedValue(value);
            typedControl.dispatchEvent(new CustomEvent('update'));
        }).catch(function(err){
            if(!DialogPromise){
                return alertPromise(err.message);
            }
        });
    }
}
myOwn.ExpanderReferences={
    autoExpand:true,
    whenType: function(typedControl){ 
        var typeInfo = typedControl.controledType.typeInfo;
        return typeInfo.references && !typeInfo.skipReferenceLookup;
    },
    dialogInput:function(typedControl, opts){
        opts = changing({
            reference: {},
            extraRow: null
        },opts ||{});
        var typeInfo = typedControl.controledType.typeInfo;
        var canceled;
        var reference = my.getReference(typeInfo.references, opts.reference);
        var dataReady = reference.dataReady;
        var timeoutWaiting=setTimeout(function(){
            timeoutWaiting=null;
            dialogPromise(function(dialogWindow, closeWindow){
                var cancelButton=html.button({class:'my-cancel'}, my.messages.Cancel).create();
                cancelButton.onclick=function(){
                    closeWindow('cancel');
                    closeWindow=null;
                };
                dialogWindow.appendChild(html.div([
                    html.div(my.messages.loading+'...'),
                    html.img({class:'hamster-gif', src:my.path.img+'hamster.gif'}),
                    html.div([cancelButton])
                ]).create());
                dataReady.then(function(){
                    if(closeWindow){
                        closeWindow('ready');
                    }
                });
            },{
                underElement:typedControl,
                reject:false,
                withCloseButton:false
            }).then(function(value){
                canceled = value=='cancel';
            });
        },250);
        dataReady.then(function(){
            if(timeoutWaiting){
                clearTimeout(timeoutWaiting);
            }
        });
        return dataReady.then(function(rows){
            if(canceled){
                return Promise.reject();
            }
            if(opts.extraRow){
                rows=rows.concat(opts.extraRow);
            }
            if(typeInfo.referencesFields && typeInfo.referencesFields.length>1){
                var prevFields=typeInfo.referencesFields.slice(0,typeInfo.referencesFields.length-1);
                rows = rows.filter(function(row){
                    return prevFields.find(function(pair){ return !bestGlobals.sameValues(row[pair.target], typedControl.depot.row[pair.source]) }) === undefined
                })
            }
            var menu=rows.map(function(row){
                return {
                    value:reference.getValue(row),
                    labels:reference.getLabels(row,true),
                    img:reference.getImage(row)
                };
            });
            if(typeInfo.nullable){
                menu.push({value:null, labels:['',TypedControls.messages.Null]});
            }
            return miniMenuPromise(menu,{
                underElement:typedControl,
                withCloseButton:true,
                initialSearch:(opts),
                whenReady:function(){
                    typedControl.displayingExpander=null;
                }
            }).then(function(value){
                if(opts.extraRow && value == opts.reference.getValue(opts.extraRow)){
                    promptPromise('ingrese valor',{
                        withCloseButton:true,
                        underElement:typedControl,
                        inputDef:{
                            attributes: {'allow-write': true}
                        }
                    }).then(function(text){
                        typedControl.setTypedValue(text||null, true);    
                    }).catch(function(err){
                        if(!DialogPromise){
                            return alertPromise(err.message);
                        }
                    });
                }else{
                    typedControl.setTypedValue(value, true);
                }
            });
        });
    }
}

myOwn.TableGrid.prototype.captureKeys = function captureKeys() {
    document.addEventListener('keypress', function(evento){
        return;
        if(evento.which==13){ // Enter
            var enfoco=this.activeElement;
            var este=this.activeElement;
            if(enter_hace_tab_en_este_elemento(este)){
                var no_me_voy_a_colgar=2000;
                while(este && this.activeElement===enfoco && no_me_voy_a_colgar--){
                    este=proximo_elemento_que_sea(este,isInteracive);
                    este.focus();
                }
                if(este){
                    evento.preventDefault();
                }
            }
        }
    });
};

myOwn.validators={}

myOwn.sortMethods={
    'charbychar':bestGlobals.forOrder.Native,
    'default':bestGlobals.forOrder,
}