Endlos Scrollen der Listen implementiert (falls notwendig)

This commit is contained in:
2025-10-09 20:42:19 +02:00
parent 1358e17ac3
commit 0ff0c0d55e

View File

@@ -22,54 +22,182 @@ $Epi = new Epirent();
<link href="css/sticky-footer.css" rel="stylesheet">
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="text/javascript">
// Dynamische Höhe: Wrapper exakt bis Viewport-Unterkante
// === Höhe der Scroll-Container bis Viewport-Ende anpassen ===
function sizeScrollContainers() {
$('.tableFixHead').each(function () {
const rect = this.getBoundingClientRect();
const bottomMargin = 16; // kleiner Abstand zum Rand
const bottomMargin = 16;
const available = window.innerHeight - rect.top - bottomMargin;
this.style.maxHeight = (available > 120 ? available : 120) + 'px';
});
}
// Lädt tbody via AJAX und erhält die Scrollposition relativ zum unteren Rand
function smartLoad($scroller, $target, url, intervalMs) {
const scroller = $scroller.get(0);
// Abstand vom unteren Rand merken (wichtiger als absolute scrollTop)
const fromBottomBefore = scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop;
// === Scroll-/Interaktionsstatus je Scroller ===
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
function ensureState(el) {
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
return scrollState.get(el);
}
function attachScrollGuards($scroller) {
const el = $scroller.get(0);
if (!el || el.__guardsBound) return;
const state = ensureState(el);
const markActive = () => {
state.userActive = true;
clearTimeout(state._quietT);
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
stopAutoScroll($scroller);
};
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
el.__guardsBound = true;
}
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf ===
// Klont den ersten <tbody> als __loopClone ans Tabellenende, wenn Inhalt > Sichtbereich.
function buildSeamlessLoop($table, $scroller) {
$table.find('tbody.__loopClone').remove();
const $main = $table.find('tbody').first();
const scEl = $scroller.get(0);
const st = ensureState(scEl);
if ($main.length === 0 || $main.children().length === 0) {
st.loopHeight = 0;
return;
}
const mainH = $main.get(0).offsetHeight;
const needScroll = mainH > scEl.clientHeight + 1;
if (!needScroll) {
st.loopHeight = 0;
return;
}
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
$table.append($clone);
st.loopHeight = mainH; // Höhe des Original-Inhalts als Loop-Länge
}
// === Auto-Scroll (nahtlos) ===
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
const el = $scroller.get(0);
const st = ensureState(el);
if (st.autoTimer) return; // schon aktiv
if (st.loopHeight <= 0) return; // kein Overflow => nicht scrollen
const tick = () => {
if (st.userActive) { stopAutoScroll($scroller); return; }
el.scrollTop += speedPx;
// Nahtlos: sobald wir das Ende des Original-Blocks überschreiten, ziehen wir loopHeight ab
if (el.scrollTop >= st.loopHeight) {
el.scrollTop -= st.loopHeight;
}
};
st.autoTimer = setInterval(tick, stepMs);
}
function stopAutoScroll($scroller) {
const el = $scroller.get(0);
const st = ensureState(el);
if (st.autoTimer) {
clearInterval(st.autoTimer);
st.autoTimer = null;
}
}
function maybeStartAutoScroll($scroller) {
const el = $scroller.get(0);
const st = ensureState(el);
if (st.userActive) return;
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 40);
else stopAutoScroll($scroller);
}
// === AJAX-Reload: relative Position innerhalb der Loop erhalten ===
// Wir merken die Position modulo loopHeight; nach dem Reload setzen wir proportional um.
function smartLoad($scroller, $table, $target, url, intervalMs) {
const scEl = $scroller.get(0);
const st = ensureState(scEl);
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
const posRatio = Math.min(1, posInLoop / oldLoop);
$target.load(url, function () {
// Nach dem Ersetzen: dieselbe Distanz zum unteren Rand wiederherstellen
const newScrollTop = scroller.scrollHeight - scroller.clientHeight - fromBottomBefore;
// Begrenzen, falls weniger Inhalt
scroller.scrollTop = Math.max(0, newScrollTop);
// Loop neu bewerten/aufbauen
buildSeamlessLoop($table, $scroller);
// Nächstes Update planen
setTimeout(function () {
smartLoad($scroller, $target, url, intervalMs);
}, intervalMs);
if (st.loopHeight > 0) {
// Neue relative Position setzen (proportional)
const newPos = Math.floor(posRatio * st.loopHeight);
if (Math.abs(scEl.scrollTop - newPos) > 1) {
// rAF für flüssiges Setzen außerhalb des load()-Layouts
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
}
// Auto-Scroll (wieder) starten
stopAutoScroll($scroller);
startAutoScroll($scroller, 1, 40);
} else {
// kein Overflow -> ganz oben und Auto-Scroll aus
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
stopAutoScroll($scroller);
}
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
});
}
// === Initialisierung ===
$(document).ready(function () {
sizeScrollContainers();
$(window).on('resize', sizeScrollContainers);
$(window).on('resize', function () {
sizeScrollContainers();
// Nach Layoutwechsel neu entscheiden
['#checkout', '#checkin', '#aufgaben'].forEach(prefix => {
const $scroller = $(`${prefix}-scroll`);
const $table = $(`${prefix}-table`);
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
});
});
// Initial laden + Auto-Refresh, jeweils eigener Scroller
smartLoad($('#checkout-scroll'), $('#getCheckOutTableHolder'), 'sources/getCheckOutTable.php', 5000);
smartLoad($('#checkin-scroll'), $('#getCheckInTableHolder'), 'sources/getCheckInTable.php', 5000);
smartLoad($('#aufgaben-scroll'), $('#AufgabenTableHolder'), 'sources/getAufgabenTable.php', 30000);
// Guards pro Scroller
attachScrollGuards($('#checkout-scroll'));
attachScrollGuards($('#checkin-scroll'));
attachScrollGuards($('#aufgaben-scroll'));
// Erstladen je Tabelle: laden -> Loop bauen -> ggf. Auto-Scroll -> zyklisch refreshen
function initOne(scrollerSel, tableSel, tbodySel, url, ms) {
const $scroller = $(scrollerSel);
const $table = $(tableSel);
const $target = $(tbodySel);
$target.load(url, function () {
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
});
}
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000);
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000);
initOne('#aufgaben-scroll', '#aufgaben-table', '#AufgabenTableHolder', 'sources/getAufgabenTable.php', 30000);
});
</script>
<style>
/* Scroll-Wrapper je Tabelle */
.tableFixHead {
@@ -98,7 +226,7 @@ $Epi = new Epirent();
}
</style>
</head>
<body style="background-color: black;">
<body style="margin-bottom:0px; background-color: black;">
<div class="container-fluid">
<div class="row">
@@ -218,7 +346,6 @@ if (UsePackingNoteDateForCheckin) {
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body>