505 lines
31 KiB
JavaScript
505 lines
31 KiB
JavaScript
/*! 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()
|
||
})()
|