-Feature: Ab sofort werden alle Configdateien über eine Example.config.php definiert. über das Dashboard kann nun über den neuen Punkt "Config" die eigentliche config.php bearbeitet werden. Änderungen durch Programmupdates werden jetzt automatisch in der example.config.php definiert und beim nächsten Speichern der config-datei über die Website angepasst.
-Feature: Scrollen der Listen können einzeln abeschalten werden
This commit is contained in:
@@ -23,15 +23,71 @@ $Epi = new Epirent();
|
||||
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
// === Höhe der Scroll-Container bis Viewport-Ende anpassen ===
|
||||
// === PHP-Flags in JS bringen ===
|
||||
const SCROLL_FLAGS = {
|
||||
checkout: <?php echo (defined('EnableScrollingCheckOut') && EnableScrollingCheckOut) ? 'true' : 'false'; ?>,
|
||||
checkin: <?php echo (defined('EnableScrollingCheckIn') && EnableScrollingCheckIn) ? 'true' : 'false'; ?>,
|
||||
aufgaben: <?php echo (defined('EnableScrollingAufgaben') && EnableScrollingAufgaben) ? 'true' : 'false'; ?>
|
||||
};
|
||||
|
||||
// Mapping: DOM-ID -> Flag-Key
|
||||
const SCROLLER_KEYS = new Map([
|
||||
['checkout-scroll', 'checkout'],
|
||||
['checkin-scroll', 'checkin'],
|
||||
['aufgaben-scroll', 'aufgaben']
|
||||
]);
|
||||
const TABLE_KEYS = new Map([
|
||||
['checkout-table', 'checkout'],
|
||||
['checkin-table', 'checkin'],
|
||||
['aufgaben-table', 'aufgaben']
|
||||
]);
|
||||
|
||||
function keyForEl(el, map) {
|
||||
if (!el || !el.id) return null;
|
||||
return map.get(el.id) || null;
|
||||
}
|
||||
function isEnabledByEl(el) {
|
||||
const k = keyForEl(el, SCROLLER_KEYS);
|
||||
return !!(k && SCROLL_FLAGS[k]);
|
||||
}
|
||||
function isEnabledByKey(key) {
|
||||
return !!SCROLL_FLAGS[key];
|
||||
}
|
||||
|
||||
// === Höhe der Scroll-Container: nebeneinander = volle Höhe; gestapelt = Drittel ===
|
||||
function sizeScrollContainers() {
|
||||
$('.tableFixHead').each(function () {
|
||||
const rect = this.getBoundingClientRect();
|
||||
const bottomMargin = 16;
|
||||
const available = window.innerHeight - rect.top - bottomMargin;
|
||||
this.style.maxHeight = (available > 120 ? available : 120) + 'px';
|
||||
const nodes = [
|
||||
document.querySelector('#checkout-scroll'),
|
||||
document.querySelector('#checkin-scroll'),
|
||||
document.querySelector('#aufgaben-scroll')
|
||||
].filter(Boolean);
|
||||
|
||||
if (nodes.length === 0) return;
|
||||
|
||||
// Ermitteln, ob alles in einer Zeile (nebeneinander) oder gestapelt
|
||||
const tops = nodes.map(n => n.getBoundingClientRect().top);
|
||||
const ROW_TOL = 8; // px
|
||||
const uniqueRows = [];
|
||||
tops.forEach(t => {
|
||||
const exists = uniqueRows.some(rt => Math.abs(rt - t) <= ROW_TOL);
|
||||
if (!exists) uniqueRows.push(t);
|
||||
});
|
||||
const isOneRow = uniqueRows.length === 1;
|
||||
|
||||
const bottomMargin = 16;
|
||||
const minH = 120;
|
||||
|
||||
nodes.forEach(node => {
|
||||
// Wenn Scrolling für diesen Bereich deaktiviert ist, Höhe trotzdem setzen (wie gefordert)
|
||||
const rect = node.getBoundingClientRect();
|
||||
if (isOneRow) {
|
||||
const available = window.innerHeight - rect.top - bottomMargin;
|
||||
node.style.maxHeight = Math.max(minH, available) + 'px';
|
||||
} else {
|
||||
const third = Math.floor(window.innerHeight / 3) - bottomMargin;
|
||||
node.style.maxHeight = Math.max(minH, third) + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,6 +102,7 @@ $Epi = new Epirent();
|
||||
function attachScrollGuards($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
if (!el || el.__guardsBound) return;
|
||||
if (!isEnabledByEl(el)) return; // nur wenn Feature aktiv
|
||||
const state = ensureState(el);
|
||||
|
||||
const markActive = () => {
|
||||
@@ -60,45 +117,40 @@ $Epi = new Epirent();
|
||||
el.__guardsBound = true;
|
||||
}
|
||||
|
||||
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf ===
|
||||
// Klont den ersten <tbody> als __loopClone ans Tabellenende, wenn Inhalt > Sichtbereich.
|
||||
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf & nur wenn aktiv ===
|
||||
function buildSeamlessLoop($table, $scroller) {
|
||||
$table.find('tbody.__loopClone').remove();
|
||||
|
||||
const $main = $table.find('tbody').first();
|
||||
const scEl = $scroller.get(0);
|
||||
const enabled = isEnabledByEl(scEl);
|
||||
const st = ensureState(scEl);
|
||||
st.loopHeight = 0;
|
||||
|
||||
if ($main.length === 0 || $main.children().length === 0) {
|
||||
st.loopHeight = 0;
|
||||
return;
|
||||
}
|
||||
if (!enabled) return; // deaktiviert -> keine Loops
|
||||
|
||||
const mainH = $main.get(0).offsetHeight;
|
||||
const needScroll = mainH > scEl.clientHeight + 1;
|
||||
const $main = $table.find('tbody').first();
|
||||
if ($main.length === 0 || $main.children().length === 0) return;
|
||||
|
||||
if (!needScroll) {
|
||||
st.loopHeight = 0;
|
||||
return;
|
||||
}
|
||||
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
|
||||
if (!needScroll) 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
|
||||
st.loopHeight = $main.get(0).offsetHeight; // 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 (!isEnabledByEl(el)) return;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -118,6 +170,7 @@ $Epi = new Epirent();
|
||||
|
||||
function maybeStartAutoScroll($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
if (!isEnabledByEl(el)) { stopAutoScroll($scroller); return; }
|
||||
const st = ensureState(el);
|
||||
if (st.userActive) return;
|
||||
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 80);
|
||||
@@ -125,33 +178,33 @@ $Epi = new Epirent();
|
||||
}
|
||||
|
||||
// === 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 enabled = isEnabledByEl(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 () {
|
||||
// Loop neu bewerten/aufbauen
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
|
||||
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; });
|
||||
if (enabled) {
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
if (st.loopHeight > 0) {
|
||||
const newPos = Math.floor(posRatio * st.loopHeight);
|
||||
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
||||
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
||||
}
|
||||
stopAutoScroll($scroller);
|
||||
startAutoScroll($scroller, 1, 80);
|
||||
} else {
|
||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||
stopAutoScroll($scroller);
|
||||
}
|
||||
// Auto-Scroll (wieder) starten
|
||||
stopAutoScroll($scroller);
|
||||
startAutoScroll($scroller, 1, 80);
|
||||
} else {
|
||||
// kein Overflow -> ganz oben und Auto-Scroll aus
|
||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||
$table.find('tbody.__loopClone').remove();
|
||||
stopAutoScroll($scroller);
|
||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||
}
|
||||
|
||||
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
||||
@@ -161,9 +214,9 @@ $Epi = new Epirent();
|
||||
// === Initialisierung ===
|
||||
$(document).ready(function () {
|
||||
sizeScrollContainers();
|
||||
|
||||
$(window).on('resize', function () {
|
||||
sizeScrollContainers();
|
||||
// Nach Layoutwechsel neu entscheiden
|
||||
['#checkout', '#checkin', '#aufgaben'].forEach(prefix => {
|
||||
const $scroller = $(`${prefix}-scroll`);
|
||||
const $table = $(`${prefix}-table`);
|
||||
@@ -172,32 +225,39 @@ $Epi = new Epirent();
|
||||
});
|
||||
});
|
||||
|
||||
// Guards pro Scroller
|
||||
attachScrollGuards($('#checkout-scroll'));
|
||||
attachScrollGuards($('#checkin-scroll'));
|
||||
attachScrollGuards($('#aufgaben-scroll'));
|
||||
// Guards nur für aktivierte Scroller
|
||||
[['#checkout-scroll','checkout'], ['#checkin-scroll','checkin'], ['#aufgaben-scroll','aufgaben']].forEach(([sel,key]) => {
|
||||
if (isEnabledByKey(key)) attachScrollGuards($(sel));
|
||||
});
|
||||
|
||||
// Erstladen je Tabelle: laden -> Loop bauen -> ggf. Auto-Scroll -> zyklisch refreshen
|
||||
function initOne(scrollerSel, tableSel, tbodySel, url, ms) {
|
||||
// Erstladen je Tabelle
|
||||
function initOne(scrollerSel, tableSel, tbodySel, url, ms, key) {
|
||||
const $scroller = $(scrollerSel);
|
||||
const $table = $(tableSel);
|
||||
const $target = $(tbodySel);
|
||||
const enabled = isEnabledByKey(key);
|
||||
|
||||
$target.load(url, function () {
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
maybeStartAutoScroll($scroller);
|
||||
if (enabled) {
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
maybeStartAutoScroll($scroller);
|
||||
} else {
|
||||
$table.find('tbody.__loopClone').remove();
|
||||
stopAutoScroll($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);
|
||||
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000, 'checkout');
|
||||
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000, 'checkin');
|
||||
initOne('#aufgaben-scroll', '#aufgaben-table', '#AufgabenTableHolder', 'sources/getAufgabenTable.php', 30000, 'aufgaben');
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
/* Scroll-Wrapper je Tabelle */
|
||||
.tableFixHead {
|
||||
|
||||
Reference in New Issue
Block a user