OPCIÓN A: A2 Virtual Scroll + A3 Celery + A4 asyncpg PoC + A5 particionamiento
A2 — Virtual scroll en tablas grandes: - Nuevo helper VirtualScroll en pos/static/js/virtual-scroll.js - inventory.js: tabla de productos con virtual scroll - customers.js: tabla de clientes con virtual scroll - fleet.js: renderMaintenance() y renderHistory() con virtual scroll - Templates envueltos en .vs-container para scroll A3 — Celery worker queue: - pos/celery_app.py + pos/tasks.py (warm cache, bulk import, reports) - Blueprint tasks_bp.py con endpoints /pos/api/tasks/* - Script scripts/start_celery.sh A4 — asyncpg + Quart PoC: - pos/async_catalog.py: endpoint /pos/api/catalog/async-search - scripts/benchmark_async_catalog.py: benchmark Flask vs Quart A5 — Particionar vehicle_parts: - scripts/partition_vehicle_parts.py: migración segura por hash (16 particiones) - Soporta --dry-run, --skip-swap, --skip-drop Tests: 36/36 pasando
This commit is contained in:
@@ -94,45 +94,53 @@ const Customers = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
function renderCustomerRow(c) {
|
||||
const tier = tierMap[c.price_tier] || 'Mostrador';
|
||||
const tClass = tierClass[c.price_tier] || 'mostrador';
|
||||
const limit = parseFloat(c.credit_limit || 0);
|
||||
const balance = parseFloat(c.credit_balance || 0);
|
||||
const available = Math.max(0, limit - balance);
|
||||
const usedPct = limit > 0 ? Math.round((balance / limit) * 100) : 0;
|
||||
const creditClass = usedPct >= 80 ? 'none' : usedPct >= 60 ? 'low' : '';
|
||||
const num = String(c.id).padStart(5, '0');
|
||||
const selClass = (currentCustomer && currentCustomer.id === c.id) ? 'selected' : '';
|
||||
return '<tr class="' + selClass + '" onclick="selectCustomer(' + c.id + ')">' +
|
||||
'<td class="cell-num">' + num + '</td>' +
|
||||
'<td>' +
|
||||
'<div class="cell-name">' + (c.name || '') + '</div>' +
|
||||
'<div class="cell-name-sub hide-mobile">' + (c.email || '') + '</div>' +
|
||||
'</td>' +
|
||||
'<td class="cell-rfc hide-mobile">' + (c.rfc || '-') + '</td>' +
|
||||
'<td class="hide-mobile">' + (c.phone || '-') + '</td>' +
|
||||
'<td class="hide-mobile" style="font-size:var(--text-caption);color:var(--color-text-secondary);">' + (c.email || '-') + '</td>' +
|
||||
'<td><span class="tipo-chip tipo-chip--' + tClass + '">' + tier + '</span></td>' +
|
||||
'<td class="cell-credit ' + creditClass + '">' + fmt(available) + '</td>' +
|
||||
'<td class="cell-date hide-mobile">' + formatDate(c.last_purchase || c.created_at) + '</td>' +
|
||||
'<td>' + statusBadge(c) + '</td>' +
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
var customersVS = null;
|
||||
|
||||
function renderTable(customers) {
|
||||
const tbody = document.getElementById('customersBody');
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (!customers || customers.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" style="text-align:center;padding:var(--space-8);color:var(--color-text-muted);">Sin resultados.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
customers.forEach((c, idx) => {
|
||||
const tier = tierMap[c.price_tier] || 'Mostrador';
|
||||
const tClass = tierClass[c.price_tier] || 'mostrador';
|
||||
const limit = parseFloat(c.credit_limit || 0);
|
||||
const balance = parseFloat(c.credit_balance || 0);
|
||||
const available = Math.max(0, limit - balance);
|
||||
const usedPct = limit > 0 ? Math.round((balance / limit) * 100) : 0;
|
||||
const creditClass = usedPct >= 80 ? 'none' : usedPct >= 60 ? 'low' : '';
|
||||
const num = String(c.id).padStart(5, '0');
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
if (currentCustomer && currentCustomer.id === c.id) tr.className = 'selected';
|
||||
tr.onclick = () => selectCustomer(c.id);
|
||||
tr.innerHTML = `
|
||||
<td class="cell-num">${num}</td>
|
||||
<td>
|
||||
<div class="cell-name">${c.name || ''}</div>
|
||||
<div class="cell-name-sub hide-mobile">${c.email || ''}</div>
|
||||
</td>
|
||||
<td class="cell-rfc hide-mobile">${c.rfc || '-'}</td>
|
||||
<td class="hide-mobile">${c.phone || '-'}</td>
|
||||
<td class="hide-mobile" style="font-size:var(--text-caption);color:var(--color-text-secondary);">${c.email || '-'}</td>
|
||||
<td><span class="tipo-chip tipo-chip--${tClass}">${tier}</span></td>
|
||||
<td class="cell-credit ${creditClass}">${fmt(available)}</td>
|
||||
<td class="cell-date hide-mobile">${formatDate(c.last_purchase || c.created_at)}</td>
|
||||
<td>${statusBadge(c)}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
if (!customersVS) {
|
||||
customersVS = new VirtualScroll({
|
||||
container: tbody,
|
||||
rowHeight: 52,
|
||||
buffer: 3,
|
||||
renderRow: renderCustomerRow,
|
||||
emptyHtml: '<tr><td colspan="9" style="text-align:center;padding:var(--space-8);color:var(--color-text-muted);">Sin resultados.</td></tr>'
|
||||
});
|
||||
}
|
||||
customersVS.setData(customers);
|
||||
}
|
||||
|
||||
function renderPagination(pag) {
|
||||
|
||||
@@ -298,52 +298,66 @@ var Fleet = (function() {
|
||||
});
|
||||
}
|
||||
|
||||
function renderMaintRow(item) {
|
||||
var v = item.vehicle;
|
||||
var s = item.schedule;
|
||||
var now = new Date();
|
||||
var isOverdue = false;
|
||||
if (s.next_due_at && new Date(s.next_due_at) < now) isOverdue = true;
|
||||
if (s.next_due_km && s.next_due_km <= v.current_mileage) isOverdue = true;
|
||||
|
||||
var interval = '';
|
||||
if (s.interval_km) interval += fmt(s.interval_km) + ' km';
|
||||
if (s.interval_km && s.interval_months) interval += ' / ';
|
||||
if (s.interval_months) interval += s.interval_months + ' meses';
|
||||
|
||||
var next = '';
|
||||
if (s.next_due_at) next += fmtDate(s.next_due_at);
|
||||
if (s.next_due_at && s.next_due_km) next += ' / ';
|
||||
if (s.next_due_km) next += fmt(s.next_due_km) + ' km';
|
||||
|
||||
return '<tr>' +
|
||||
'<td><strong>' + esc(v.plate || 'S/P') + '</strong><br>' +
|
||||
'<span style="font-size:var(--text-caption);color:var(--color-text-muted);">' +
|
||||
esc((v.make || '') + ' ' + (v.model || '')) + '</span></td>' +
|
||||
'<td>' + esc(s.maintenance_type) + '</td>' +
|
||||
'<td class="mono">' + (interval || '—') + '</td>' +
|
||||
'<td>' + fmtDate(s.last_done_at) + (s.last_done_km ? '<br><span class="mono" style="font-size:var(--text-caption);">' + fmt(s.last_done_km) + ' km</span>' : '') + '</td>' +
|
||||
'<td>' + (next || '—') + '</td>' +
|
||||
'<td><span class="badge ' + (isOverdue ? 'badge--overdue' : 'badge--active') + '">' +
|
||||
(isOverdue ? 'Vencido' : 'Al dia') + '</span></td>' +
|
||||
'<td><button class="btn btn--sm btn--ghost" onclick="Fleet.openLogModalFor(' + v.id + ',' + s.id + ',\'' + esc(s.maintenance_type) + '\')">Registrar</button></td>' +
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
var maintVS = null;
|
||||
|
||||
function renderMaintenance(results) {
|
||||
var body = document.getElementById('maintBody');
|
||||
var rows = [];
|
||||
var items = [];
|
||||
|
||||
results.forEach(function(r) {
|
||||
var v = r.vehicle;
|
||||
r.schedules.forEach(function(s) {
|
||||
var now = new Date();
|
||||
var isOverdue = false;
|
||||
if (s.next_due_at && new Date(s.next_due_at) < now) isOverdue = true;
|
||||
if (s.next_due_km && s.next_due_km <= v.current_mileage) isOverdue = true;
|
||||
|
||||
var interval = '';
|
||||
if (s.interval_km) interval += fmt(s.interval_km) + ' km';
|
||||
if (s.interval_km && s.interval_months) interval += ' / ';
|
||||
if (s.interval_months) interval += s.interval_months + ' meses';
|
||||
|
||||
var next = '';
|
||||
if (s.next_due_at) next += fmtDate(s.next_due_at);
|
||||
if (s.next_due_at && s.next_due_km) next += ' / ';
|
||||
if (s.next_due_km) next += fmt(s.next_due_km) + ' km';
|
||||
|
||||
rows.push(
|
||||
'<tr>' +
|
||||
'<td><strong>' + esc(v.plate || 'S/P') + '</strong><br>' +
|
||||
'<span style="font-size:var(--text-caption);color:var(--color-text-muted);">' +
|
||||
esc((v.make || '') + ' ' + (v.model || '')) + '</span></td>' +
|
||||
'<td>' + esc(s.maintenance_type) + '</td>' +
|
||||
'<td class="mono">' + (interval || '—') + '</td>' +
|
||||
'<td>' + fmtDate(s.last_done_at) + (s.last_done_km ? '<br><span class="mono" style="font-size:var(--text-caption);">' + fmt(s.last_done_km) + ' km</span>' : '') + '</td>' +
|
||||
'<td>' + (next || '—') + '</td>' +
|
||||
'<td><span class="badge ' + (isOverdue ? 'badge--overdue' : 'badge--active') + '">' +
|
||||
(isOverdue ? 'Vencido' : 'Al dia') + '</span></td>' +
|
||||
'<td><button class="btn btn--sm btn--ghost" onclick="Fleet.openLogModalFor(' + v.id + ',' + s.id + ',\'' + esc(s.maintenance_type) + '\')">Registrar</button></td>' +
|
||||
'</tr>'
|
||||
);
|
||||
items.push({vehicle: r.vehicle, schedule: s});
|
||||
});
|
||||
});
|
||||
|
||||
if (!rows.length) {
|
||||
if (!items.length) {
|
||||
body.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:var(--space-6);color:var(--color-text-muted);">' +
|
||||
'No hay programas de mantenimiento.<br><button class="btn btn--primary btn--sm" style="margin-top:var(--space-3);" onclick="Fleet.openScheduleModal()">+ Crear Programa</button></td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
body.innerHTML = rows.join('');
|
||||
if (!maintVS) {
|
||||
maintVS = new VirtualScroll({
|
||||
container: body,
|
||||
rowHeight: 64,
|
||||
buffer: 3,
|
||||
renderRow: renderMaintRow,
|
||||
emptyHtml: '<tr><td colspan="7" style="text-align:center;padding:var(--space-6);color:var(--color-text-muted);">No hay programas de mantenimiento.</td></tr>'
|
||||
});
|
||||
}
|
||||
maintVS.setData(items);
|
||||
}
|
||||
|
||||
// ─── History Tab ───
|
||||
@@ -371,6 +385,21 @@ var Fleet = (function() {
|
||||
});
|
||||
}
|
||||
|
||||
function renderHistoryRow(l) {
|
||||
return '<tr>' +
|
||||
'<td>' + fmtDate(l.created_at) + '</td>' +
|
||||
'<td><strong>' + esc(l._plate) + '</strong><br>' +
|
||||
'<span style="font-size:var(--text-caption);color:var(--color-text-muted);">' + esc(l._make) + '</span></td>' +
|
||||
'<td>' + esc(l.maintenance_type) + '</td>' +
|
||||
'<td class="mono">' + fmt(l.mileage_at) + '</td>' +
|
||||
'<td class="mono">' + fmtMoney(l.cost) + '</td>' +
|
||||
'<td>' + esc(l.employee_name || '—') + '</td>' +
|
||||
'<td style="max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + esc(l.notes || '—') + '</td>' +
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
var historyVS = null;
|
||||
|
||||
function renderHistory(results) {
|
||||
var body = document.getElementById('historyBody');
|
||||
var allLogs = [];
|
||||
@@ -393,20 +422,16 @@ var Fleet = (function() {
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
allLogs.forEach(function(l) {
|
||||
html += '<tr>' +
|
||||
'<td>' + fmtDate(l.created_at) + '</td>' +
|
||||
'<td><strong>' + esc(l._plate) + '</strong><br>' +
|
||||
'<span style="font-size:var(--text-caption);color:var(--color-text-muted);">' + esc(l._make) + '</span></td>' +
|
||||
'<td>' + esc(l.maintenance_type) + '</td>' +
|
||||
'<td class="mono">' + fmt(l.mileage_at) + '</td>' +
|
||||
'<td class="mono">' + fmtMoney(l.cost) + '</td>' +
|
||||
'<td>' + esc(l.employee_name || '—') + '</td>' +
|
||||
'<td style="max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + esc(l.notes || '—') + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
body.innerHTML = html;
|
||||
if (!historyVS) {
|
||||
historyVS = new VirtualScroll({
|
||||
container: body,
|
||||
rowHeight: 48,
|
||||
buffer: 3,
|
||||
renderRow: renderHistoryRow,
|
||||
emptyHtml: '<tr><td colspan="7" style="text-align:center;padding:var(--space-6);color:var(--color-text-muted);">No hay registros de mantenimiento</td></tr>'
|
||||
});
|
||||
}
|
||||
historyVS.setData(allLogs);
|
||||
}
|
||||
|
||||
// ─── Alerts Tab ───
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
var currentPage = 1;
|
||||
var currentSearch = '';
|
||||
var draftCountId = null;
|
||||
var inventoryVS = null;
|
||||
|
||||
// --- API helper ---
|
||||
function apiFetch(url, opts) {
|
||||
@@ -52,6 +53,24 @@
|
||||
// STOCK / PRODUCTS (panel-stock)
|
||||
// =====================================================================
|
||||
|
||||
function renderInventoryRow(it) {
|
||||
return '<tr style="cursor:pointer;" onclick="viewProductDetail(' + it.id + ')">' +
|
||||
'<td class="td--mono">' + esc(it.barcode) + '</td>' +
|
||||
'<td class="td--mono">' + esc(it.part_number) + '</td>' +
|
||||
'<td class="td--primary">' + esc(it.name) + '</td>' +
|
||||
'<td>' + esc(it.brand) + '</td>' +
|
||||
'<td style="text-align:right" class="td--primary">' + it.stock + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.cost) + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.price_1) + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.price_2) + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.price_3) + '</td>' +
|
||||
'<td>' + esc(it.location) + '</td>' +
|
||||
'<td>' +
|
||||
'<button class="btn btn--ghost btn--sm" onclick="event.stopPropagation();viewHistory(' + it.id + ')">Historial</button> ' +
|
||||
'<button class="btn btn--ghost btn--sm" onclick="event.stopPropagation();printBarcode(\'' + esc(it.barcode) + '\',\'' + esc(it.part_number) + '\',\'' + esc(it.name) + '\')">Etiqueta</button>' +
|
||||
'</td></tr>';
|
||||
}
|
||||
|
||||
function loadItems(page, search) {
|
||||
currentPage = page || 1;
|
||||
currentSearch = search !== undefined ? search : currentSearch;
|
||||
@@ -69,23 +88,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = items.map(function (it) {
|
||||
return '<tr style="cursor:pointer;" onclick="viewProductDetail(' + it.id + ')">' +
|
||||
'<td class="td--mono">' + esc(it.barcode) + '</td>' +
|
||||
'<td class="td--mono">' + esc(it.part_number) + '</td>' +
|
||||
'<td class="td--primary">' + esc(it.name) + '</td>' +
|
||||
'<td>' + esc(it.brand) + '</td>' +
|
||||
'<td style="text-align:right" class="td--primary">' + it.stock + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.cost) + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.price_1) + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.price_2) + '</td>' +
|
||||
'<td style="text-align:right" class="td--amount">$' + fmt(it.price_3) + '</td>' +
|
||||
'<td>' + esc(it.location) + '</td>' +
|
||||
'<td>' +
|
||||
'<button class="btn btn--ghost btn--sm" onclick="event.stopPropagation();viewHistory(' + it.id + ')">Historial</button> ' +
|
||||
'<button class="btn btn--ghost btn--sm" onclick="event.stopPropagation();printBarcode(\'' + esc(it.barcode) + '\',\'' + esc(it.part_number) + '\',\'' + esc(it.name) + '\')">Etiqueta</button>' +
|
||||
'</td></tr>';
|
||||
}).join('');
|
||||
if (!inventoryVS) {
|
||||
inventoryVS = new VirtualScroll({
|
||||
container: tbody,
|
||||
rowHeight: 48,
|
||||
buffer: 3,
|
||||
renderRow: renderInventoryRow,
|
||||
emptyHtml: '<tr><td colspan="11" style="text-align:center;padding:30px;color:var(--color-text-muted);">Sin productos</td></tr>'
|
||||
});
|
||||
}
|
||||
inventoryVS.setData(items);
|
||||
|
||||
// Pagination
|
||||
var pg = data.pagination || {};
|
||||
|
||||
155
pos/static/js/virtual-scroll.js
Normal file
155
pos/static/js/virtual-scroll.js
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* virtual-scroll.js — Lightweight vanilla-JS virtual scroll helper.
|
||||
* Supports <div> containers and <tbody> tables.
|
||||
*
|
||||
* Usage:
|
||||
* var vs = new VirtualScroll({
|
||||
* container: document.getElementById('myTableBody'),
|
||||
* rowHeight: 48,
|
||||
* buffer: 5,
|
||||
* renderRow: function(item, index) { return '<tr>...</tr>'; }
|
||||
* });
|
||||
* vs.setData(arrayOfItems);
|
||||
*/
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
function VirtualScroll(opts) {
|
||||
this.container = opts.container;
|
||||
this.rowHeight = opts.rowHeight || 48;
|
||||
this.buffer = opts.buffer || 5;
|
||||
this.renderRow = opts.renderRow || function() { return ''; };
|
||||
this.emptyHtml = opts.emptyHtml || '';
|
||||
this.data = [];
|
||||
this._scrollHandler = this._onScroll.bind(this);
|
||||
this._resizeHandler = this._onResize.bind(this);
|
||||
this._isTbody = this.container.tagName === 'TBODY';
|
||||
this._init();
|
||||
}
|
||||
|
||||
VirtualScroll.prototype._init = function() {
|
||||
var c = this.container;
|
||||
if (!this._isTbody) {
|
||||
c.style.overflowY = 'auto';
|
||||
c.style.position = 'relative';
|
||||
if (!c.style.maxHeight && !c.style.height) {
|
||||
c.style.maxHeight = '60vh';
|
||||
}
|
||||
} else {
|
||||
// For tbody, scroll is on the parent element (table wrapper)
|
||||
var table = c.closest('table');
|
||||
if (table) {
|
||||
var wrapper = table.parentElement;
|
||||
if (wrapper && wrapper.classList.contains('vs-container')) {
|
||||
wrapper.addEventListener('scroll', this._scrollHandler, { passive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', this._resizeHandler, { passive: true });
|
||||
};
|
||||
|
||||
VirtualScroll.prototype.setData = function(data) {
|
||||
this.data = data || [];
|
||||
this._render();
|
||||
};
|
||||
|
||||
VirtualScroll.prototype.refresh = function() {
|
||||
this._render();
|
||||
};
|
||||
|
||||
VirtualScroll.prototype._onScroll = function() {
|
||||
this._render();
|
||||
};
|
||||
|
||||
VirtualScroll.prototype._onResize = function() {
|
||||
this._render();
|
||||
};
|
||||
|
||||
VirtualScroll.prototype._getScrollTop = function() {
|
||||
if (this._isTbody) {
|
||||
var table = this.container.closest('table');
|
||||
if (table) {
|
||||
var wrapper = table.parentElement;
|
||||
if (wrapper && wrapper.classList.contains('vs-container')) {
|
||||
return wrapper.scrollTop;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return this.container.scrollTop;
|
||||
};
|
||||
|
||||
VirtualScroll.prototype._getContainerHeight = function() {
|
||||
if (this._isTbody) {
|
||||
var table = this.container.closest('table');
|
||||
if (table) {
|
||||
var wrapper = table.parentElement;
|
||||
if (wrapper && wrapper.classList.contains('vs-container')) {
|
||||
return wrapper.clientHeight;
|
||||
}
|
||||
}
|
||||
return 600;
|
||||
}
|
||||
return this.container.clientHeight;
|
||||
};
|
||||
|
||||
VirtualScroll.prototype._render = function() {
|
||||
var data = this.data;
|
||||
var rowH = this.rowHeight;
|
||||
var buffer = this.buffer;
|
||||
|
||||
if (!data.length) {
|
||||
if (this._isTbody) {
|
||||
this.container.innerHTML = this.emptyHtml;
|
||||
} else {
|
||||
this.container.innerHTML = this.emptyHtml;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var scrollTop = this._getScrollTop();
|
||||
var containerHeight = this._getContainerHeight();
|
||||
var startIdx = Math.max(0, Math.floor(scrollTop / rowH) - buffer);
|
||||
var endIdx = Math.min(data.length, Math.ceil((scrollTop + containerHeight) / rowH) + buffer);
|
||||
|
||||
var html = '';
|
||||
if (this._isTbody) {
|
||||
// Top spacer row
|
||||
var topSpacerHeight = startIdx * rowH;
|
||||
if (topSpacerHeight > 0) {
|
||||
html += '<tr style="height:' + topSpacerHeight + 'px;"><td colspan="99" style="padding:0;border:0;"></td></tr>';
|
||||
}
|
||||
for (var i = startIdx; i < endIdx; i++) {
|
||||
html += this.renderRow(data[i], i);
|
||||
}
|
||||
// Bottom spacer row
|
||||
var bottomSpacerHeight = (data.length - endIdx) * rowH;
|
||||
if (bottomSpacerHeight > 0) {
|
||||
html += '<tr style="height:' + bottomSpacerHeight + 'px;"><td colspan="99" style="padding:0;border:0;"></td></tr>';
|
||||
}
|
||||
} else {
|
||||
for (var j = startIdx; j < endIdx; j++) {
|
||||
html += this.renderRow(data[j], j);
|
||||
}
|
||||
}
|
||||
|
||||
this.container.innerHTML = html;
|
||||
};
|
||||
|
||||
VirtualScroll.prototype.destroy = function() {
|
||||
if (this._isTbody) {
|
||||
var table = this.container.closest('table');
|
||||
if (table) {
|
||||
var wrapper = table.parentElement;
|
||||
if (wrapper && wrapper.classList.contains('vs-container')) {
|
||||
wrapper.removeEventListener('scroll', this._scrollHandler);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.container.removeEventListener('scroll', this._scrollHandler);
|
||||
}
|
||||
window.removeEventListener('resize', this._resizeHandler);
|
||||
};
|
||||
|
||||
window.VirtualScroll = VirtualScroll;
|
||||
})(window);
|
||||
Reference in New Issue
Block a user