Files
Plataforma-de-desarrolladores/SIG.Web/Scripts/filtrosig.js
Jorge Luis caudillo esparza 0776ed8c59 Cambios en Gis lotes
2026-10-03 14:43:42 -07:00

1282 lines
26 KiB
JavaScript

(function (window, $) {
'use strict';
if (!$) {
console.error('FiltroSig requiere jQuery.');
return;
}
var menuFlotante = null;
function cerrarMenuFlotante() {
if (!menuFlotante) {
return;
}
var actual = menuFlotante;
menuFlotante = null;
actual.control.removeClass('menu-open');
actual.flecha.attr('aria-expanded', 'false');
actual.menu.removeClass('filtrosig-menu-flotante').removeAttr('style');
if ($.contains(document, actual.control[0])) {
actual.menu.detach().appendTo(actual.control);
} else {
actual.menu.remove();
}
}
function posicionarMenuFlotante() {
if (!menuFlotante) {
return;
}
var actual = menuFlotante;
if (!$.contains(document, actual.control[0])) {
cerrarMenuFlotante();
return;
}
var borde = actual.flecha[0].getBoundingClientRect();
var ancho = window.innerWidth;
var alto = window.innerHeight;
actual.menu.css({
width: Math.min(240, ancho - 12),
minWidth: Math.min(210, ancho - 12),
maxHeight: Math.max(50, alto - 12),
left: 0,
top: 0,
right: 'auto',
bottom: 'auto'
});
var altura = actual.menu.outerHeight();
var arriba = borde.bottom + 4;
if (arriba + altura > alto - 6) {
arriba = Math.max(6, borde.top - altura - 4);
}
actual.menu.css({
left: Math.max(6, Math.min(borde.right - actual.menu.outerWidth(), ancho - actual.menu.outerWidth() - 6)),
top: Math.min(arriba, Math.max(6, alto - altura - 6))
});
}
function alternarMenuFlotante(control, menu, flecha) {
var cerrar = menuFlotante && menuFlotante.control[0] === control[0];
cerrarMenuFlotante();
if (cerrar) {
return;
}
menuFlotante = { control: control, menu: menu, flecha: flecha };
control.addClass('menu-open');
flecha.attr('aria-expanded', 'true');
menu.detach().appendTo(control.closest('form')).addClass('filtrosig-menu-flotante');
posicionarMenuFlotante();
}
window.addEventListener('resize', posicionarMenuFlotante);
document.addEventListener('scroll', function (evento) {
if (menuFlotante && !menuFlotante.menu[0].contains(evento.target)) {
posicionarMenuFlotante();
}
}, true);
$(document).on('keydown.sigFiltroMenu', function (evento) {
if (evento.key === 'Escape') {
cerrarMenuFlotante();
}
});
var FILTRO_RENDERED = 'filtrosigrendered';
var FILTRO_HIDDEN = 'filtrosig-hidden';
var FILTRO_IGNORE = 'filtrosig-ignore';
/**
* Inicializa todos los GridView marcados con:
*
* class="filtrosig"
*
* que todavía no hayan sido renderizados.
*/
window.RenderizarFiltrosSig = function () {
$('table.filtrosig')
.not('.' + FILTRO_RENDERED)
.each(function () {
var $grid = $(this);
prepararFilasEspeciales($grid);
var campos = obtenerCampos($grid);
crearControl($grid, campos);
$grid.addClass(FILTRO_RENDERED).attr('data-sig-filtro-inicializado', '1');
});
};
/**
* Método público fijo para filtrar un GridView.
*
* grid:
* DOM table, jQuery table o id.
*
* texto:
* Texto a buscar.
*
* tipo:
* all
* column
* data
*
* campo:
* índice de columna o nombre del data.
*/
window.FiltrarGrid = function (grid, texto, tipo, campo) {
var $grid = obtenerGrid(grid);
if (!$grid.length) {
return;
}
texto = normalizarTexto(texto);
var $filas = obtenerFilasFiltrables($grid);
$filas.each(function () {
var $fila = $(this);
var coincide = false;
if (texto === '') {
coincide = true;
}
else if (tipo === 'column') {
coincide = filtrarPorColumna(
$fila,
campo,
texto
);
}
else if (tipo === 'data') {
coincide = filtrarPorData(
$fila,
campo,
texto
);
}
else {
coincide = filtrarTodo(
$fila,
texto
);
}
if (coincide) {
/*
* IMPORTANTE:
*
* Solo quitamos nuestra propia clase.
*
* NO modificamos:
*
* style="display:none"
* clases externas
* ocultamientos realizados por otras funciones.
*/
$fila.removeClass(FILTRO_HIDDEN);
}
else {
/*
* Esta es nuestra única marca para ocultar.
*/
$fila.addClass(FILTRO_HIDDEN);
}
});
actualizarContador($grid);
};
/**
* Obtiene el GridView.
*/
function obtenerGrid(grid) {
if (grid instanceof jQuery) {
return grid;
}
if (grid instanceof HTMLElement) {
return $(grid);
}
if (typeof grid === 'string') {
if (grid.charAt(0) === '#') {
return $(grid);
}
return $('#' + grid);
}
return $();
}
/**
* Marca las filas que FiltroSig NO debe ocultar.
*/
function prepararFilasEspeciales($grid) {
$grid.find('tr').each(function () {
var $tr = $(this);
/*
* Si contiene TH es header.
*/
if ($tr.children('th').length > 0) {
$tr.addClass(FILTRO_IGNORE);
}
/*
* Compatibilidad con las filas ADtr
* existentes en SIG.
*/
if ($tr.hasClass('ADtr')) {
$tr.addClass(FILTRO_IGNORE);
}
});
/*
* THEAD y TFOOT siempre se ignoran.
*/
$grid
.find('thead tr, tfoot tr')
.addClass(FILTRO_IGNORE);
}
/**
* Obtiene:
*
* - Todos
* - columnas visibles del GridView
* - campos data adicionales
*/
function obtenerCampos($grid) {
var campos = [];
/*
* TODOS
*/
campos.push({
tipo: 'all',
campo: '',
titulo: 'Todos'
});
/*
* COLUMNAS DEL GRIDVIEW
*/
var $headers = obtenerHeaders($grid);
$headers.each(function (index) {
var $th = $(this);
/*
* Permite excluir una columna:
*
* data-filtrosig-ignore="true"
*/
if (
String(
$th.attr('data-filtrosig-ignore')
).toLowerCase() === 'true'
) {
return;
}
var titulo = $.trim(
$th.text()
);
/*
* Si no tiene título normalmente es
* una columna de botones.
*/
if (!titulo) {
return;
}
campos.push({
tipo: 'column',
campo: index,
titulo: titulo
});
});
/*
* DATAS ADICIONALES
*
* Ejemplo:
*
* data-filtrosig-datas=
* "cliente:Cliente,etapa:Etapa,cvecliente:Clave cliente"
*/
var configuracionDatas =
$grid.attr('data-filtrosig-datas');
if (configuracionDatas) {
configuracionDatas
.split(',')
.forEach(function (item) {
item = $.trim(item);
if (!item) {
return;
}
var partes =
item.split(':');
var campo =
$.trim(partes[0]);
var titulo =
partes.length > 1
? $.trim(
partes.slice(1).join(':')
)
: campo;
campos.push({
tipo: 'data',
campo: campo,
titulo: titulo
});
});
}
return campos;
}
/**
* Obtiene los headers.
*/
function obtenerHeaders($grid) {
var $headers =
$grid.find('thead tr:first th');
if ($headers.length) {
return $headers;
}
/*
* Fallback para GridView sin
* UseAccessibleHeader.
*/
return $grid.find('tr:first th');
}
/**
* Genera el control visual de búsqueda.
*/
function crearControl($grid, campos) {
var gridId =
$grid.attr('id');
/*
* Si por alguna razón el GridView
* no tiene ID, generamos uno.
*/
if (!gridId) {
gridId =
'filtrosig_grid_' +
Math.random()
.toString(36)
.substring(2, 10);
$grid.attr(
'id',
gridId
);
}
/*
* CONTENEDOR
*/
var $contenedor = $('<div>', {
'class': 'filtrosig-container',
'data-filtrosig-grid': gridId
});
/*
* CONTROL
*/
var $control = $('<div>', {
'class': 'filtrosig-control'
});
/*
* INPUT
*
* autocomplete se coloca mediante attr()
* para evitar conflicto con jQuery UI autocomplete.
*/
var $input = $('<input>', {
type: 'text',
'class': 'filtrosig-input',
placeholder: 'Buscar...'
});
$input.attr(
'autocomplete',
'off'
);
/*
* CONTENEDOR AZUL
*/
var $acciones = $('<div>', {
'class': 'filtrosig-actions'
});
/*
* BOTÓN BUSCAR
*
* Usa FontAwesome.
*/
var $buscar = $('<button>', {
type: 'button',
'class': 'filtrosig-search',
title: 'Buscar'
}).html(
'<span class="fa fa-search"></span>'
);
/*
* NOMBRE DEL CAMPO SELECCIONADO
*/
var $campoActual = $('<span>', {
'class': 'filtrosig-selected-field'
});
/*
* BOTÓN FLECHA
*/
var $flecha = $('<button>', {
type: 'button',
'class': 'filtrosig-arrow',
title: 'Seleccionar campo'
}).html(
'<span class="filtrosig-chevron"></span>'
);
/*
* MENÚ
*/
var $menu = $('<div>', {
'class': 'filtrosig-menu'
});
/*
* GENERAR OPCIONES
*/
campos.forEach(function (item) {
var $opcion = $('<button>', {
type: 'button',
'class': 'filtrosig-option',
text: item.titulo
});
$opcion.attr(
'data-tipo',
item.tipo
);
$opcion.attr(
'data-campo',
item.campo
);
$opcion.attr(
'data-titulo',
item.titulo
);
if (item.tipo === 'all') {
$opcion.addClass(
'active'
);
}
$menu.append(
$opcion
);
});
/*
* SELECCIÓN INICIAL
*/
$control.data(
'tipo',
'all'
);
$control.data(
'campo',
''
);
$control.data(
'titulo',
'Todos'
);
/*
* ARMAR PARTE AZUL
*/
$acciones
.append($buscar)
.append($campoActual)
.append($flecha);
/*
* ARMAR CONTROL
*/
$control
.append($input)
.append($acciones)
.append($menu);
$contenedor.append(
$control
);
/*
* Colocamos el control inmediatamente
* antes del GridView.
*/
$grid.before(
$contenedor
);
/*
* =====================================================
* TEMPORIZADOR PARA FILTRADO AUTOMÁTICO
* =====================================================
*
* Si el usuario escribe:
*
* A
*
* esperamos 1 segundo.
*
* Si antes escribe:
*
* AB
*
* cancelamos el anterior y volvemos
* a esperar 1 segundo.
*/
var timeoutFiltro = null;
/*
* =====================================================
* ABRIR / CERRAR MENÚ
* =====================================================
*/
$flecha.on('click', function (e) {
e.preventDefault();
e.stopPropagation();
alternarMenuFlotante($control, $menu, $flecha);
});
/*
* =====================================================
* SELECCIONAR CAMPO
* =====================================================
*/
$menu.on(
'click',
'.filtrosig-option',
function () {
var $opcion =
$(this);
var tipo =
$opcion.attr(
'data-tipo'
);
var campo =
$opcion.attr(
'data-campo'
);
var titulo =
$opcion.attr(
'data-titulo'
);
/*
* Guardamos selección.
*/
$control.data(
'tipo',
tipo
);
$control.data(
'campo',
campo
);
$control.data(
'titulo',
titulo
);
/*
* Cambiar opción activa.
*/
$menu
.find('.filtrosig-option')
.removeClass('active');
$opcion.addClass(
'active'
);
/*
* TODOS:
*
* No mostramos título.
*
* COLUMNA / DATA:
*
* mostramos título entre
* lupa y flecha.
*/
if (tipo === 'all') {
$campoActual.text('');
$control.removeClass(
'campo-seleccionado'
);
}
else {
$campoActual.text(
titulo
);
$control.addClass(
'campo-seleccionado'
);
}
/*
* Cerrar menú.
*/
cerrarMenuFlotante();
/*
* Si había un filtro automático
* esperando, lo cancelamos.
*/
if (timeoutFiltro !== null) {
clearTimeout(
timeoutFiltro
);
timeoutFiltro = null;
}
/*
* Aplicar inmediatamente el filtro
* usando el nuevo campo.
*/
ejecutarFiltro(
$grid,
$control,
$input
);
}
);
/*
* =====================================================
* CLICK EN LA LUPA
* =====================================================
*
* Ejecuta inmediatamente.
*/
$buscar.on('click', function () {
/*
* Cancelar filtro automático pendiente.
*/
if (timeoutFiltro !== null) {
clearTimeout(
timeoutFiltro
);
timeoutFiltro = null;
}
ejecutarFiltro(
$grid,
$control,
$input
);
});
/*
* =====================================================
* ENTER
* =====================================================
*
* Ejecuta inmediatamente.
*/
$input.on('keydown', function (e) {
if (
e.key === 'Enter' ||
e.keyCode === 13
) {
e.preventDefault();
/*
* Cancelar filtro automático pendiente.
*/
if (timeoutFiltro !== null) {
clearTimeout(
timeoutFiltro
);
timeoutFiltro = null;
}
ejecutarFiltro(
$grid,
$control,
$input
);
}
});
/*
* =====================================================
* FILTRADO AUTOMÁTICO AL ESCRIBIR
* =====================================================
*
* DEBOUNCE DE 1 SEGUNDO.
*
* Cada vez que escribe:
*
* 1. Cancela el timeout anterior.
* 2. Espera nuevamente 1 segundo.
* 3. Si no escribió nada más,
* ejecuta FiltrarGrid().
*
* Si borra todo:
*
* se limpia el filtro inmediatamente.
*/
$input.on('input', function () {
/*
* Cancelar ejecución anterior.
*/
if (timeoutFiltro !== null) {
clearTimeout(
timeoutFiltro
);
timeoutFiltro = null;
}
/*
* Si quedó vacío, no esperamos.
*
* Quitamos nuestro filtro inmediatamente.
*/
if (
$.trim(
$(this).val()
) === ''
) {
ejecutarFiltro(
$grid,
$control,
$input
);
return;
}
/*
* Esperar 1 segundo desde
* la última escritura.
*/
timeoutFiltro =
setTimeout(function () {
ejecutarFiltro(
$grid,
$control,
$input
);
timeoutFiltro = null;
}, 1000);
});
}
/**
* Ejecuta el método público FiltrarGrid.
*/
function ejecutarFiltro(
$grid,
$control,
$input
) {
FiltrarGrid(
$grid,
$input.val(),
$control.data('tipo'),
$control.data('campo')
);
}
/**
* Filtra utilizando:
*
* - todo el texto de la fila
* - todos los data-* existentes en la fila
*/
function filtrarTodo(
$fila,
texto
) {
/*
* Texto visible de toda la fila.
*/
var contenido =
normalizarTexto(
$fila.text()
);
/*
* Además agregamos TODOS los data-*
* de la fila.
*/
$.each(
$fila[0].attributes,
function () {
if (
this.name &&
this.name.indexOf('data-') === 0
) {
contenido +=
' ' +
normalizarTexto(
this.value
);
}
}
);
return contenido.indexOf(
texto
) !== -1;
}
/**
* Filtra exclusivamente por una columna.
*/
function filtrarPorColumna(
$fila,
indice,
texto
) {
indice =
parseInt(
indice,
10
);
if (isNaN(indice)) {
return false;
}
var $celda =
$fila
.children('td,th')
.eq(indice);
if (!$celda.length) {
return false;
}
var contenido =
normalizarTexto(
$celda.text()
);
return contenido.indexOf(
texto
) !== -1;
}
/**
* Filtra exclusivamente por un data-*.
*
* Ejemplo:
*
* data-etapa="Etapa 3"
*
* campo:
*
* etapa
*/
function filtrarPorData(
$fila,
campo,
texto
) {
var valor =
$fila.attr(
'data-' + campo
);
if (valor === undefined) {
valor = '';
}
valor =
normalizarTexto(
valor
);
return valor.indexOf(
texto
) !== -1;
}
/**
* Obtiene solamente las filas que
* FiltroSig tiene permitido filtrar.
*/
function obtenerFilasFiltrables($grid) {
return $grid
.find('tbody > tr')
.not('.ADtr')
.not('.' + FILTRO_IGNORE);
}
/**
* Actualiza contador opcional.
*
* Grid:
*
* data-filtrosig-contador="IdContador"
*/
function actualizarContador($grid) {
var idContador =
$grid.attr(
'data-filtrosig-contador'
);
if (!idContador) {
return;
}
var $contador =
$('#' + idContador);
if (!$contador.length) {
return;
}
/*
* :visible respeta:
*
* - filtrosig-hidden
* - display:none externo
* - otras clases que oculten filas
*/
var cantidad =
obtenerFilasFiltrables($grid)
.filter(':visible')
.length;
$contador.text(
cantidad
);
}
/**
* Normaliza el texto para la búsqueda.
*
* - minúsculas
* - trim
* - elimina diferencias por acentos
*
* Ejemplo:
*
* Mexico
*
* encuentra:
*
* México
*/
function normalizarTexto(texto) {
if (
texto === null ||
texto === undefined
) {
return '';
}
texto =
String(texto)
.toLowerCase()
.trim();
/*
* Búsqueda tolerante a acentos.
*/
if (texto.normalize) {
texto =
texto
.normalize('NFD')
.replace(
/[\u0300-\u036f]/g,
''
);
}
return texto;
}
/*
* =========================================================
* CERRAR MENÚ AL HACER CLICK AFUERA
* =========================================================
*/
$(document).on(
'click.filtrosig',
function () {
cerrarMenuFlotante();
}
);
/*
* Evita que un click dentro del control
* cierre inmediatamente el menú.
*/
$(document).on(
'click.filtrosig',
'.filtrosig-control',
function (e) {
e.stopPropagation();
}
);
/*
* =========================================================
* PRIMERA CARGA
* =========================================================
*/
$(function () {
RenderizarFiltrosSig();
});
/*
* =========================================================
* ASP.NET WEBFORMS / UPDATEPANEL
* =========================================================
*
* Después de cualquier postback parcial
* volvemos a buscar GridViews .filtrosig.
*
* Si el GridView fue reemplazado:
*
* ya no tendrá filtrosigrendered
*
* y se generará nuevamente.
*
* Si sobrevivió al postback:
*
* conserva filtrosigrendered
*
* y NO se duplica.
*/
if (
typeof Sys !== 'undefined' &&
Sys.WebForms &&
Sys.WebForms.PageRequestManager
) {
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(cerrarMenuFlotante);
Sys.WebForms.PageRequestManager
.getInstance()
.add_endRequest(function () {
RenderizarFiltrosSig();
});
}
})(window, window.jQuery);