Files
Webklar-Kundenbereich/public/embed/unterkunft.js
T

505 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*! WEBklar Buchungskalender: Einbau-Skript fuer Kundenwebsites.
* <div data-webklar-unterkunft="SITEKEY" data-api="https://project.webklar.com"></div>
* <script src="https://project.webklar.com/embed/unterkunft.js" defer></script>
* Keine Abhaengigkeiten, keine Cookies, kein Storage, keine Drittanfragen. Alle Texte nur ueber textContent.
* Gestaltung ueber CSS-Variablen der Seite (alle optional): --wku-akzent, --wku-akzent-text, --wku-hintergrund, --wku-text,
* --wku-rand, --wku-radius, --wku-schrift, --wku-schrift-titel.
* Belegte Naechte kommen als [von,bis) (bis = Abreisetag, nicht belegt). Datumsrechnung ist reine Kalenderrechnung auf Zeichenketten
* (UTC), die Zeitzone des Browsers spielt nie eine Rolle; "heute" und das Ende des Buchungsfensters liefert der Server. */
(function () {
'use strict'
var SELF = document.currentScript
var STYLE_ID = 'wku-style'
var API_PFAD = '/api/public/unterkunft/v1/'
var AUSWEICH = 'Anfragen gerne per Telefon oder E-Mail.'
var TIMEOUT_MS = 20000
var MONAT_KURZ = ['Jan.', 'Feb.', 'März', 'Apr.', 'Mai', 'Juni', 'Juli', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.']
var MONAT_LANG = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
var WOCHE_KURZ = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
var WOCHE_LANG = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
var EMAIL_FORM = /^[^\s@,;<>"]+@[^\s@,;<>"]+\.[^\s@,;<>"]+$/
var FARBE = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/
var zaehler = 0
/* ------------------------------------------------------------ reine Kalenderlogik --- */
function zwei(n) { return (n < 10 ? '0' : '') + n }
function iso(j, m, t) { return j + '-' + zwei(m) + '-' + zwei(t) }
function zerlege(s) { var p = s.split('-'); return [parseInt(p[0], 10), parseInt(p[1], 10), parseInt(p[2], 10)] }
function utc(s) { var p = zerlege(s); return Date.UTC(p[0], p[1] - 1, p[2]) }
function plusTage(s, n) {
var d = new Date(utc(s) + n * 86400000)
return iso(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate())
}
function tageZwischen(a, b) { return Math.round((utc(b) - utc(a)) / 86400000) }
function wochentag(s) { return (new Date(utc(s)).getUTCDay() + 6) % 7 } // 0 = Montag
function tageImMonat(j, m) { return new Date(Date.UTC(j, m, 0)).getUTCDate() }
var WKU_LOGIK = {
monatsRaster: function (jahr, monat) {
var r = []
var lead = wochentag(iso(jahr, monat, 1))
var n = tageImMonat(jahr, monat)
var i
for (i = 0; i < lead; i += 1) r.push({ tag: null })
for (i = 1; i <= n; i += 1) r.push({ tag: iso(jahr, monat, i) })
while (r.length % 7) r.push({ tag: null })
return r
},
nachtBelegt: function (tag, belegt) {
for (var i = 0; i < (belegt || []).length; i += 1) if (belegt[i].von <= tag && tag < belegt[i].bis) return true
return false
},
waehle: function (zustand, tag, belegt, mindestNaechte) {
var z = zustand || {}
var L = WKU_LOGIK
var neu = function () { return L.nachtBelegt(tag, belegt) ? { von: z.von || null, bis: z.bis || null } : { von: tag, bis: null } }
if (!z.von || z.bis || tag <= z.von) return neu()
for (var d = z.von; d < tag; d = plusTage(d, 1)) if (L.nachtBelegt(d, belegt)) return neu()
if (tageZwischen(z.von, tag) < (mindestNaechte || 1)) return { von: z.von, bis: null, fehler: 'mindestnaechte' }
return { von: z.von, bis: tag }
},
zusammenfassung: function (von, bis) {
var a = zerlege(von)
var b = zerlege(bis)
var n = tageZwischen(von, bis)
var tail = ' · ' + n + (n === 1 ? ' Nacht' : ' Nächte')
if (a[0] !== b[0]) return a[2] + '. ' + MONAT_KURZ[a[1] - 1] + ' ' + a[0] + ' – ' + b[2] + '. ' + MONAT_KURZ[b[1] - 1] + ' ' + b[0] + tail
if (a[1] === b[1]) return a[2] + '.–' + b[2] + '. ' + MONAT_KURZ[a[1] - 1] + tail
return a[2] + '. ' + MONAT_KURZ[a[1] - 1] + ' – ' + b[2] + '. ' + MONAT_KURZ[b[1] - 1] + tail
}
}
var testModus = false
try { testModus = !!(SELF && SELF.dataset && SELF.dataset.test === '1') } catch (e) { testModus = false }
if (testModus) globalThis.__wkuLogik = WKU_LOGIK
function langesDatum(s) {
var p = zerlege(s)
return WOCHE_LANG[wochentag(s)] + ', ' + p[2] + '. ' + MONAT_LANG[p[1] - 1] + ' ' + p[0]
}
/* ------------------------------------------------------------------- Gestaltung --- */
var CSS = [
'.wku-root{--wku-i-akzent:var(--wku-akzent,var(--wku-vorgabe-akzent,#0f766e));--wku-i-akzent-text:var(--wku-akzent-text,var(--wku-vorgabe-akzent-text,#fff));',
'--wku-i-bg:var(--wku-hintergrund,#fff);--wku-i-text:var(--wku-text,#1f2937);--wku-i-rand:var(--wku-rand,#b6bcc6);--wku-i-radius:var(--wku-radius,12px);',
'--wku-i-weich:#f3f4f6;--wku-i-bereich:#e5eceb;--wku-i-fehler:#b91c1c;--wku-i-schrift:var(--wku-schrift,inherit);--wku-i-titel:var(--wku-schrift-titel,var(--wku-schrift,inherit))}',
'@supports (color:color-mix(in srgb,red,blue)){.wku-root{--wku-i-weich:color-mix(in srgb,var(--wku-i-text) 6%,var(--wku-i-bg));--wku-i-bereich:color-mix(in srgb,var(--wku-i-akzent) 18%,var(--wku-i-bg))}}',
'.wku-root{container:wku/inline-size;display:block;position:relative;box-sizing:border-box;color:var(--wku-i-text);font-family:var(--wku-i-schrift);font-size:16px;line-height:1.5;text-align:left;width:100%;max-width:100%}',
'.wku-root :is(button,input,select,textarea,label,h1,h2,h3,h4,h5,h6,p,a,fieldset,legend){all:revert;box-sizing:border-box}',
'.wku-root [hidden]{display:none}',
'.wku-root *,.wku-root *::before,.wku-root *::after{box-sizing:border-box}',
'.wku-root .wku-vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}',
'.wku-root .wku-haupt{display:grid;grid-template-columns:minmax(0,1fr);gap:24px}',
'.wku-root .wku-karte{background:var(--wku-i-bg);border:1px solid var(--wku-i-rand);border-radius:var(--wku-i-radius);padding:20px}',
'.wku-root .wku-titel{font-family:var(--wku-i-titel);font-size:1.2rem;line-height:1.25;font-weight:700;margin:0 0 .75rem}',
'.wku-root.wku-ausweich{margin:0}',
'.wku-root .wku-monate{display:grid;grid-template-columns:minmax(0,1fr);gap:20px}',
'.wku-root .wku-zwei{display:none}',
'.wku-root .wku-monat-kopf{display:flex;align-items:center;justify-content:space-between;gap:.5rem;min-height:44px;margin-bottom:.35rem}',
'.wku-root .wku-monat-name{font-family:var(--wku-i-titel);font-size:1.05rem;font-weight:700;margin:0;flex:1;text-align:center}',
'.wku-root .wku-pfeil{font:inherit;font-size:1.4rem;line-height:1;min-width:44px;min-height:44px;border-radius:50%;border:1px solid var(--wku-i-rand);background:var(--wku-i-bg);color:inherit;cursor:pointer;padding:0}',
'.wku-root .wku-pfeil:hover{background:var(--wku-i-weich)}.wku-root .wku-pfeil[disabled]{opacity:.3;cursor:default}.wku-root .wku-pfeil-leer{display:inline-block;width:44px;height:44px}',
'.wku-root .wku-raster{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));row-gap:2px}',
'.wku-root .wku-wt{font-size:.78rem;font-weight:600;text-align:center;padding:.25rem 0;opacity:.7}',
'.wku-root .wku-leer{min-height:44px}',
'.wku-root .wku-tag{font:inherit;position:relative;min-height:44px;padding:0;margin:0;border:0;border-radius:0;background:transparent;color:inherit;text-align:center;cursor:pointer;overflow:hidden}',
'.wku-root .wku-tag>span{position:relative;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:50%;border:1px solid transparent;z-index:1}',
'.wku-root .wku-tag:hover>span{background:var(--wku-i-weich)}',
'.wku-root .wku-heute>span{border-color:var(--wku-i-text)}',
'.wku-root .wku-vergangen,.wku-root .wku-zu{opacity:.35;cursor:default}.wku-root .wku-vergangen:hover>span,.wku-root .wku-zu:hover>span{background:transparent}',
'.wku-root .wku-belegt{color:#6b7280;cursor:default;background:linear-gradient(to top right,transparent calc(50% - 1px),#9ca3af calc(50% - 1px),#9ca3af calc(50% + 1px),transparent calc(50% + 1px)) center/60% 60% no-repeat}',
'.wku-root .wku-belegt:hover>span{background:transparent}',
'.wku-root .wku-bereich{background:var(--wku-i-bereich)}',
'.wku-root .wku-an{background:linear-gradient(to right,transparent 50%,var(--wku-i-bereich) 50%)}',
'.wku-root .wku-ab{background:linear-gradient(to left,transparent 50%,var(--wku-i-bereich) 50%)}',
'.wku-root .wku-an.wku-ab{background:transparent}',
'.wku-root .wku-an>span,.wku-root .wku-ab>span,.wku-root .wku-an:hover>span,.wku-root .wku-ab:hover>span{background:var(--wku-i-akzent);color:var(--wku-i-akzent-text);border-color:var(--wku-i-akzent)}',
'.wku-root :focus-visible{outline:3px solid var(--wku-i-text);outline-offset:2px}',
'.wku-root .wku-legende{display:flex;flex-wrap:wrap;gap:.25rem 1rem;margin:.75rem 0 0;font-size:.8rem;opacity:.8}',
'.wku-root .wku-zus{margin:1rem 0 0;font-weight:700;min-height:1.5rem}',
'.wku-root .wku-hinweis{margin:.25rem 0 0;font-size:.9rem;color:var(--wku-i-fehler);font-weight:600;min-height:1.4rem}',
'.wku-root .wku-form{margin:0}',
'.wku-root .wku-feld{margin:0 0 .9rem}',
'.wku-root .wku-label{display:block;font-weight:600;font-size:.92rem;margin-bottom:.25rem}',
'.wku-root .wku-stern{color:var(--wku-i-fehler)}',
'.wku-root .wku-eingabe{display:block;width:100%;min-height:44px;font:inherit;color:inherit;background:var(--wku-i-bg);border:1px solid var(--wku-i-rand);border-radius:calc(var(--wku-i-radius) * .7);padding:.5rem .7rem}',
'.wku-root textarea.wku-eingabe{min-height:5.5rem;resize:vertical}',
'.wku-root .wku-eingabe[aria-invalid="true"]{border-color:var(--wku-i-fehler);border-width:2px}',
'.wku-root .wku-feldfehler{color:var(--wku-i-fehler);font-weight:600;font-size:.88rem;margin:.25rem 0 0}',
'.wku-root .wku-reihe{display:grid;grid-template-columns:minmax(0,1fr);gap:0 12px}',
'.wku-root .wku-fehler{margin:0 0 .75rem;padding:.6rem .8rem;border-left:4px solid var(--wku-i-fehler);background:var(--wku-i-weich);font-weight:600}',
'.wku-root .wku-datenschutz{font-size:.85rem;margin:0 0 1rem;opacity:.9}.wku-root .wku-datenschutz a{color:inherit}',
'.wku-root .wku-btn{font:inherit;font-weight:700;min-height:48px;padding:.6rem 1.5rem;border-radius:var(--wku-i-radius);border:2px solid var(--wku-i-akzent);background:var(--wku-i-akzent);color:var(--wku-i-akzent-text);cursor:pointer;line-height:1.25;width:100%}',
'.wku-root .wku-btn:hover{filter:brightness(1.08)}.wku-root .wku-btn[disabled]{opacity:.6;cursor:default}',
'.wku-root .wku-danke{margin:0;font-size:1.05rem;font-weight:600}',
'.wku-root .wku-hp{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden}',
'@container wku (min-width:520px){.wku-root .wku-reihe{grid-template-columns:1fr 1fr}}',
'@container wku (min-width:760px){.wku-root .wku-haupt{grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);align-items:start}}',
'@container wku (min-width:1100px){.wku-root .wku-zwei{display:block}.wku-root .wku-monate{grid-template-columns:1fr 1fr}.wku-root .wku-weiter-ein{display:none}}',
'@media (prefers-reduced-motion:no-preference){.wku-root .wku-btn,.wku-root .wku-tag>span{transition:background-color .15s}}'
].join('')
function ensureStyle() {
if (document.getElementById(STYLE_ID)) return
var st = document.createElement('style')
st.id = STYLE_ID
st.textContent = CSS
;(document.head || document.documentElement).appendChild(st)
}
/* ------------------------------------------------------------------------ Helfer --- */
function h(tag, attrs, kinder) {
var el = document.createElement(tag)
var k
attrs = attrs || {}
for (k in attrs) {
if (!Object.prototype.hasOwnProperty.call(attrs, k) || attrs[k] === null || attrs[k] === undefined || attrs[k] === false) continue
if (k === 'text') el.textContent = attrs[k]
else if (k === 'class') el.className = attrs[k]
else el.setAttribute(k, attrs[k] === true ? '' : String(attrs[k]))
}
;(kinder || []).forEach(function (c) { if (c) el.appendChild(c) })
return el
}
function apiBasis(el) {
var a = el.getAttribute('data-api')
if (!a && SELF && SELF.src) { var m = /^(https?:\/\/[^\/]+)/.exec(SELF.src); a = m ? m[1] : '' }
return (a || '').replace(/\/+$/, '')
}
function holen(url, opt) {
var ctl = typeof AbortController === 'function' ? new AbortController() : null
var t = ctl ? setTimeout(function () { ctl.abort() }, TIMEOUT_MS) : null
var o = opt || {}
if (ctl) o.signal = ctl.signal
return fetch(url, o).then(function (r) {
if (t) clearTimeout(t)
return r.json().then(function (body) { return { status: r.status, ok: r.ok, body: body || {} } }, function () { return { status: r.status, ok: r.ok, body: {} } })
}, function (e) { if (t) clearTimeout(t); throw e })
}
function textFarbe(hex) {
var s = hex.slice(1)
if (s.length === 3) s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2]
var r = parseInt(s.slice(0, 2), 16); var g = parseInt(s.slice(2, 4), 16); var b = parseInt(s.slice(4, 6), 16)
return (0.299 * r + 0.587 * g + 0.114 * b) > 160 ? '#111827' : '#ffffff'
}
function ausweich(el) {
el.textContent = ''
el.appendChild(h('p', { class: 'wku-root wku-ausweich', text: AUSWEICH }))
}
/* ------------------------------------------------------------------------ Widget --- */
function starten(el) {
if (el.getAttribute('data-wku-fertig')) return
el.setAttribute('data-wku-fertig', '1')
var siteKey = el.getAttribute('data-webklar-unterkunft')
if (!/^[A-Za-z0-9_-]{8,64}$/.test(siteKey || '')) return
ensureStyle()
var basis = apiBasis(el) + API_PFAD + encodeURIComponent(siteKey)
holen(basis, { headers: { Accept: 'application/json' } }).then(function (r) {
if (!r.ok || !r.body || r.body.aktiv !== true || !r.body.unterkuenfte || !r.body.unterkuenfte.length) return ausweich(el)
bauen(el, basis, r.body)
}, function () { ausweich(el) })
}
function bauen(el, basis, cfg) {
var id = 'wku' + (zaehler += 1) + '-'
var heute = null
var bisMax = null
var belegt = []
var sel = { von: null, bis: null }
var hinweis = ''
var unterk = cfg.unterkuenfte[0]
var token = cfg.formToken
var ansicht = null // { j, m } erster angezeigter Monat
var fokusTag = null
var sendet = false
var root = h('div', { class: 'wku-root' })
if (cfg.akzentfarbe && FARBE.test(cfg.akzentfarbe) && root.style) {
root.style.setProperty('--wku-vorgabe-akzent', cfg.akzentfarbe)
root.style.setProperty('--wku-vorgabe-akzent-text', textFarbe(cfg.akzentfarbe))
}
var haupt = h('div', { class: 'wku-haupt' })
var kal = h('div', { class: 'wku-karte' })
var formBox = h('div', { class: 'wku-karte' })
var zus = h('p', { class: 'wku-zus', 'aria-live': 'polite' })
var hint = h('p', { class: 'wku-hinweis', 'aria-live': 'polite' })
var monate = h('div', { class: 'wku-monate' })
kal.appendChild(h('h2', { class: 'wku-titel', text: 'Zeitraum wählen' }))
kal.appendChild(monate)
kal.appendChild(h('div', { class: 'wku-legende' }, [h('span', { text: 'Grau durchgestrichen: belegt' }), h('span', { text: 'Umrandet: heute' })]))
kal.appendChild(zus)
kal.appendChild(hint)
haupt.appendChild(kal)
haupt.appendChild(formBox)
root.appendChild(haupt)
el.textContent = ''
el.appendChild(root)
/* ---- Formular ---- */
var feld = {}
var fehlerBox = h('p', { class: 'wku-fehler', role: 'alert', hidden: true })
var form = h('form', { class: 'wku-form', novalidate: true })
var selU = null
var selP = h('select', { class: 'wku-eingabe', id: id + 'personen', name: 'personen' })
function eingabe(name, label, opt) {
opt = opt || {}
var inp = h(opt.mehrzeilig ? 'textarea' : 'input', { class: 'wku-eingabe', id: id + name, name: name, type: opt.mehrzeilig ? null : (opt.type || 'text'), autocomplete: opt.auto || 'off', maxlength: opt.max || 200, required: opt.pflicht ? true : null, rows: opt.mehrzeilig ? 4 : null })
var fe = h('p', { class: 'wku-feldfehler', id: id + name + '-f', hidden: true })
var box = h('div', { class: 'wku-feld' }, [h('label', { class: 'wku-label', for: id + name }, [h('span', { text: label }), opt.pflicht ? h('span', { class: 'wku-stern', 'aria-hidden': 'true', text: ' *' }) : null]), inp, fe])
feld[name] = { inp: inp, fe: fe }
return box
}
function fuellePersonen() {
selP.textContent = ''
var max = Math.max(1, parseInt(unterk.maxPersonen, 10) || 1)
for (var i = 1; i <= max; i += 1) selP.appendChild(h('option', { value: i, text: i === 1 ? '1 Person' : i + ' Personen' }))
}
if (cfg.unterkuenfte.length > 1) {
selU = h('select', { class: 'wku-eingabe', id: id + 'unterkunft', name: 'unterkunft' })
cfg.unterkuenfte.forEach(function (u, i) { selU.appendChild(h('option', { value: i, text: u.name })) })
form.appendChild(h('div', { class: 'wku-feld' }, [h('label', { class: 'wku-label', for: id + 'unterkunft', text: 'Unterkunft' }), selU]))
}
form.appendChild(h('div', { class: 'wku-feld' }, [h('label', { class: 'wku-label', for: id + 'personen', text: 'Personen' }), selP]))
form.appendChild(eingabe('firma', 'Firma (optional)', { auto: 'organization' }))
form.appendChild(eingabe('name', 'Name', { pflicht: true, auto: 'name' }))
form.appendChild(h('div', { class: 'wku-reihe' }, [eingabe('email', 'E-Mail', { pflicht: true, type: 'email', auto: 'email' }), eingabe('telefon', 'Telefon (optional)', { type: 'tel', auto: 'tel', max: 50 })]))
form.appendChild(eingabe('nachricht', 'Nachricht (optional)', { mehrzeilig: true, max: 2000 }))
var hp = h('input', { type: 'text', name: 'kontakt_url', id: id + 'hp', tabindex: '-1', autocomplete: 'off' })
form.appendChild(h('div', { class: 'wku-hp', 'aria-hidden': 'true' }, [hp]))
var ds = h('p', { class: 'wku-datenschutz' })
var anb = cfg.anbieter || {}
ds.appendChild(document.createTextNode('Ihre Angaben werden zur Bearbeitung Ihrer Anfrage verwendet' + (anb.name ? ' (' + anb.name + ')' : '') + '. Mehr dazu in der '))
if (anb.datenschutzUrl && /^https?:\/\//i.test(anb.datenschutzUrl)) ds.appendChild(h('a', { href: anb.datenschutzUrl, target: '_blank', rel: 'noopener noreferrer', text: 'Datenschutzerklärung' }))
else ds.appendChild(document.createTextNode('Datenschutzerklärung'))
ds.appendChild(document.createTextNode('.'))
form.appendChild(ds)
var btn = h('button', { class: 'wku-btn', type: 'submit', text: 'Anfrage senden' })
form.appendChild(fehlerBox)
form.appendChild(btn)
formBox.appendChild(h('h2', { class: 'wku-titel', text: 'Unverbindlich anfragen' }))
formBox.appendChild(form)
fuellePersonen()
function zeigeFehler(text) { fehlerBox.textContent = text || ''; fehlerBox.hidden = !text }
function feldFehler(name, text) {
var f = feld[name]
if (!f) return false
f.fe.textContent = text || ''
f.fe.hidden = !text
if (text) { f.inp.setAttribute('aria-invalid', 'true'); f.inp.setAttribute('aria-describedby', f.fe.id) } else { f.inp.removeAttribute('aria-invalid'); f.inp.removeAttribute('aria-describedby') }
return true
}
/* ---- Kalender ---- */
function monatKey(j, m) { return j * 12 + (m - 1) }
function zweiSichtbar() { return (root.offsetWidth || root.clientWidth || 0) >= 1100 }
function aktualisiereZus() {
if (sel.von && sel.bis) { zus.textContent = WKU_LOGIK.zusammenfassung(sel.von, sel.bis); hint.textContent = '' }
else if (sel.von) { zus.textContent = 'Anreise ' + langesDatum(sel.von) + '. Bitte Abreise wählen.'; hint.textContent = hinweis }
else { zus.textContent = ''; hint.textContent = '' }
if (hinweis && !sel.bis) hint.textContent = hinweis
}
function tageBisFrei(von, bis) {
for (var d = von; d < bis; d = plusTage(d, 1)) if (WKU_LOGIK.nachtBelegt(d, belegt)) return false
return true
}
function tagKnopf(tag) {
var p = zerlege(tag)
var vergangen = tag < heute
var zu = tag > bisMax
var nb = WKU_LOGIK.nachtBelegt(tag, belegt)
var kl = ['wku-tag']
if (tag === heute) kl.push('wku-heute')
if (vergangen) kl.push('wku-vergangen')
if (zu) kl.push('wku-zu')
if (nb && !vergangen && !zu) kl.push('wku-belegt')
var istAn = sel.von === tag
var istAb = sel.bis === tag
if (istAn && sel.bis) kl.push('wku-an')
else if (istAn) kl.push('wku-an', 'wku-ab')
if (istAb) kl.push('wku-ab')
if (sel.von && sel.bis && tag > sel.von && tag < sel.bis) kl.push('wku-bereich')
var status = (nb ? (sel.von && !sel.bis && tag > sel.von && !vergangen && !zu && !WKU_LOGIK.nachtBelegt(sel.von, belegt) && tageBisFrei(sel.von, tag) ? 'belegt – als Abreisetag möglich' : 'belegt') : 'frei') + (istAn ? ', Anreise' : '') + (istAb ? ', Abreise' : '')
var b = h('button', { class: kl.join(' '), type: 'button', 'data-tag': tag, 'aria-label': langesDatum(tag) + ', ' + status, 'aria-pressed': (istAn || istAb) ? 'true' : 'false', tabindex: tag === fokusTag ? '0' : '-1' }, [h('span', { text: String(p[2]) })])
if (vergangen || zu) { b.setAttribute('aria-disabled', 'true') }
b.addEventListener('click', function () { tagKlick(tag) })
return b
}
function monatBauen(j, m, erster) {
var kopf = h('div', { class: 'wku-monat-kopf' })
var min = monatKey(heute.slice(0, 4) * 1, heute.slice(5, 7) * 1)
var max = monatKey(bisMax.slice(0, 4) * 1, bisMax.slice(5, 7) * 1)
var zur
if (erster) {
zur = h('button', { class: 'wku-pfeil', type: 'button', 'data-nav': 'zur', 'aria-label': 'Vorheriger Monat', text: '‹' })
if (monatKey(j, m) <= min) zur.setAttribute('disabled', '')
zur.addEventListener('click', function () { blaettern(-1) })
kopf.appendChild(zur)
} else kopf.appendChild(h('span', { class: 'wku-pfeil-leer' }))
kopf.appendChild(h('h3', { class: 'wku-monat-name', text: MONAT_LANG[m - 1] + ' ' + j }))
// Erster Monat: immer ein echter Weiter-Knopf (bei zwei sichtbaren Monaten per CSS ausgeblendet, dann sitzt er im zweiten Monat).
var vor = h('button', { class: 'wku-pfeil' + (erster ? ' wku-weiter-ein' : ''), type: 'button', 'data-nav': erster ? 'vor1' : 'vor2', 'aria-label': 'Nächster Monat', text: '›' })
if (monatKey(j, m) >= max) vor.setAttribute('disabled', '')
vor.addEventListener('click', function () { blaettern(1) })
kopf.appendChild(vor)
var grid = h('div', { class: 'wku-raster', role: 'group', 'aria-label': MONAT_LANG[m - 1] + ' ' + j })
WOCHE_KURZ.forEach(function (w, i) { grid.appendChild(h('div', { class: 'wku-wt', 'aria-hidden': 'true', text: w, title: WOCHE_LANG[i] })) })
WKU_LOGIK.monatsRaster(j, m).forEach(function (z) { grid.appendChild(z.tag ? tagKnopf(z.tag) : h('div', { class: 'wku-leer' })) })
return h('div', { class: 'wku-monat' + (erster ? '' : ' wku-zwei') }, [kopf, grid])
}
function zeichne(fokus) {
if (!heute) return
var a = ansicht
var b = a.m === 12 ? { j: a.j + 1, m: 1 } : { j: a.j, m: a.m + 1 }
monate.textContent = ''
monate.appendChild(monatBauen(a.j, a.m, true))
monate.appendChild(monatBauen(b.j, b.m, false))
aktualisiereZus()
if (fokus && fokusTag) {
var k = monate.querySelector('[data-tag="' + fokusTag + '"]')
if (k && k.focus) k.focus()
}
}
function blaettern(d) {
var m = ansicht.m + d
var j = ansicht.j
if (m < 1) { m = 12; j -= 1 } else if (m > 12) { m = 1; j += 1 }
var min = monatKey(heute.slice(0, 4) * 1, heute.slice(5, 7) * 1)
var max = monatKey(bisMax.slice(0, 4) * 1, bisMax.slice(5, 7) * 1)
if (monatKey(j, m) < min || monatKey(j, m) > max) return
ansicht = { j: j, m: m }
var erster = iso(j, m, 1)
fokusTag = erster < heute ? heute : erster
zeichne(false)
var zwei = zweiSichtbar()
var wunsch = d > 0 ? (zwei ? 'vor2' : 'vor1') : 'zur'
var knopf = monate.querySelector('[data-nav="' + wunsch + '"]')
if (!knopf || knopf.hasAttribute('disabled')) knopf = monate.querySelector(d > 0 ? '[data-nav="zur"]' : (zwei ? '[data-nav="vor2"]' : '[data-nav="vor1"]'))
if (knopf && knopf.focus) knopf.focus()
}
function tagKlick(tag) {
if (tag < heute || tag > bisMax) return
var neu = WKU_LOGIK.waehle(sel, tag, belegt, parseInt(unterk.mindestNaechte, 10) || 1)
hinweis = neu.fehler === 'mindestnaechte' ? 'Mindestaufenthalt: ' + (unterk.mindestNaechte === 1 ? '1 Nacht' : unterk.mindestNaechte + ' Nächte') + '.' : ''
sel = { von: neu.von || null, bis: neu.bis || null }
fokusTag = tag
zeichne(true)
}
monate.addEventListener('keydown', function (ev) {
var t = ev.target && ev.target.getAttribute ? ev.target.getAttribute('data-tag') : null
if (!t) return
var d = ev.key === 'ArrowRight' ? 1 : ev.key === 'ArrowLeft' ? -1 : ev.key === 'ArrowDown' ? 7 : ev.key === 'ArrowUp' ? -7 : 0
if (!d) return
ev.preventDefault()
var ziel = plusTage(t, d)
if (ziel < heute || ziel > bisMax) return
fokusTag = ziel
var p = zerlege(ziel)
var sichtbar = monatKey(p[0], p[1]) >= monatKey(ansicht.j, ansicht.m) && monatKey(p[0], p[1]) <= monatKey(ansicht.j, ansicht.m) + (zweiSichtbar() ? 1 : 0)
if (!sichtbar) ansicht = { j: p[0], m: p[1] }
zeichne(true)
})
function pad(n) { return zwei(n) }
var ladeNr = 0
function laden() {
var nr = (ladeNr += 1)
var jetzt = new Date()
var von = jetzt.getUTCFullYear() + '-' + pad(jetzt.getUTCMonth() + 1) + '-' + pad(jetzt.getUTCDate())
von = plusTage(von, -1)
var bis = plusTage(von, 600)
return holen(basis + '/belegung?unterkunft=' + encodeURIComponent(unterk.id) + '&von=' + von + '&bis=' + bis, { headers: { Accept: 'application/json' } }).then(function (r) {
if (nr !== ladeNr) return
if (!r.ok || !r.body.heute || !r.body.bisMax) throw new Error('belegung')
belegt = r.body.belegt || []
heute = r.body.heute
bisMax = r.body.bisMax
if (!ansicht) { var p = zerlege(heute); ansicht = { j: p[0], m: p[1] } }
if (!fokusTag) fokusTag = heute
sel = { von: null, bis: null }
hinweis = ''
zeichne(false)
})
}
function ladenOderAusweichen() { laden().then(null, function () { ausweich(el) }) }
if (selU) selU.addEventListener('change', function () {
unterk = cfg.unterkuenfte[parseInt(selU.value, 10) || 0]
fuellePersonen()
ladenOderAusweichen()
})
form.addEventListener('submit', function (ev) {
ev.preventDefault()
if (sendet) return
zeigeFehler('')
Object.keys(feld).forEach(function (k) { feldFehler(k, '') })
if (!sel.von || !sel.bis) { zeigeFehler('Bitte wählen Sie Anreise und Abreise im Kalender.'); return }
var wert = function (n) { return String(feld[n].inp.value || '').trim() }
var fehler = 0
if (!wert('name')) { feldFehler('name', 'Bitte geben Sie Ihren Namen an.'); fehler += 1 }
if (!EMAIL_FORM.test(wert('email'))) { feldFehler('email', 'Bitte geben Sie eine gültige E-Mail-Adresse an.'); fehler += 1 }
if (fehler) { var ersteFehl = form.querySelector('[aria-invalid="true"]'); if (ersteFehl && ersteFehl.focus) ersteFehl.focus(); return }
sendet = true
btn.setAttribute('disabled', '')
var body = { unterkunft: unterk.id, von: sel.von, bis: sel.bis, personen: parseInt(selP.value, 10) || 1, name: wert('name'), firma: wert('firma'), email: wert('email'), telefon: wert('telefon'), nachricht: wert('nachricht'), formToken: token, kontakt_url: hp.value }
holen(basis + '/anfrage', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify(body) }).then(function (r) {
sendet = false
btn.removeAttribute('disabled')
if (r.ok && r.body.nummer) {
formBox.textContent = ''
formBox.appendChild(h('p', { class: 'wku-danke', role: 'status', tabindex: '-1', text: 'Danke! Ihre Anfrage ' + r.body.nummer + ' ist eingegangen. Sie erhalten gleich eine Bestätigung per E-Mail.' }))
var d = formBox.querySelector('.wku-danke'); if (d && d.focus) d.focus()
laden().then(null, function () {})
return
}
var b = r.body || {}
var gezeigt = false
var uebrig = []
if (b.felder) Object.keys(b.felder).forEach(function (k) {
if (!Object.prototype.hasOwnProperty.call(b.felder, k)) return
if (feldFehler(k, String(b.felder[k]))) gezeigt = true
else if (k === 'zeitraum' || k === 'personen') uebrig.push(String(b.felder[k]))
})
if (uebrig.length) { zeigeFehler(uebrig.join(' ')); gezeigt = true }
var fehlerFokus = form.querySelector('[aria-invalid="true"]')
if (fehlerFokus && fehlerFokus.focus) fehlerFokus.focus()
if (gezeigt) return
if (b.fehler === 'neu_laden') {
zeigeFehler('Das Formular war zu lange geöffnet. Bitte senden Sie Ihre Anfrage noch einmal ab.')
holen(basis, { headers: { Accept: 'application/json' } }).then(function (c) { if (c.ok && c.body.formToken) token = c.body.formToken }, function () {})
return
}
if (b.fehler === 'mindestnaechte') {
var mn = parseInt((b.felder && b.felder.mindestNaechte) || unterk.mindestNaechte, 10) || 1
zeigeFehler('Mindestaufenthalt: ' + (mn === 1 ? '1 Nacht' : mn + ' Nächte') + '. Bitte wählen Sie einen längeren Zeitraum.')
return
}
if (b.fehler === 'belegt' || r.status === 409) { zeigeFehler('Dieser Zeitraum ist leider nicht mehr frei. Bitte wählen Sie einen anderen.'); laden().then(null, function () {}) }
else if (r.status === 429) zeigeFehler('Zu viele Anfragen. Bitte versuchen Sie es später noch einmal.')
else {
zeigeFehler('Die Anfrage konnte nicht gesendet werden. Bitte versuchen Sie es erneut oder melden Sie sich per Telefon oder E-Mail.')
holen(basis, { headers: { Accept: 'application/json' } }).then(function (c) { if (c.ok && c.body.formToken) token = c.body.formToken }, function () {})
}
}, function () {
sendet = false
btn.removeAttribute('disabled')
zeigeFehler('Die Anfrage konnte nicht gesendet werden. Bitte versuchen Sie es erneut oder melden Sie sich per Telefon oder E-Mail.')
})
})
ladenOderAusweichen()
}
function alleStarten() {
var liste = document.querySelectorAll('[data-webklar-unterkunft]')
for (var i = 0; i < liste.length; i += 1) starten(liste[i])
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', alleStarten)
else alleStarten()
})()