ReserveITDokumentácia

Rezervačný widget: vlastné CSS a JavaScript

Dokumentácia pre vývojárov — vloženie, atribúty, JavaScript API, udalosti a úpravy vzhľadu z vášho webu.

Obsah
  1. Rýchly štart
  2. Spôsoby vloženia
  3. Atribúty
  4. JavaScript API
  5. Udalosti
  6. Vzhľad z vášho webu
  7. Kompletné príklady
  8. Bezpečnosť a kompatibilita

1Rýchly štart

Rezervačný widget ReserveIT vložíte na akýkoľvek web dvoma riadkami HTML. Všetko ostatné — načítanie služieb, voľné termíny, overenie a uloženie rezervácie — zabezpečí widget sám priamo voči nášmu API. Nepotrebujete žiadny backend, knižnicu ani framework.

Kľúč widgetu v ukážkach: VAS-KLIC Svoj kľúč nájdete v administrácii v sekcii Widgety (pri každom widgete je aj hotový kód na vloženie).
html
<div data-reserveit="VAS-KLIC"></div>
<script src="http://sk.localhost:8103/widget/reserveit.js" async></script>

Skript stačí na stránke raz, aj keď vkladáte viac widgetov. Widget sa vykreslí do Shadow DOM, takže štýly vášho webu ho nerozbijú a jeho štýly neovplyvnia váš web. Prispôsobí sa šírke kontajnera (mobil aj desktop).

2Spôsoby vloženia

A) Kontajner + skript (odporúčané)

Kontajner <div data-reserveit="…"> umiestnite tam, kde sa má formulár zobraziť. Skript vložte kamkoľvek na stránku (ideálne pred </body>) — nájde všetky kontajnery a každý widget pripojí samostatne.

html
<section id="rezervace">
  <h2>Rezervace</h2>
  <div data-reserveit="VAS-KLIC"></div>
</section>

<!-- jednou na stránce, i pro více widgetů -->
<script src="http://sk.localhost:8103/widget/reserveit.js" async></script>

B) Jedným tagom

Hodí sa do redakčných systémov, kam možno vložiť len jeden kus kódu. Widget sa vloží hneď za skript do nového prvku s atribútom data-reserveit-host (podľa neho ho môžete štýlovať). Ak ho chcete inde, pridajte data-reserveit-target s CSS selektorom cieľového prvku. Ostatné atribúty (data-reserveit-service, -locale, -prefill) zapíšte na tag skriptu.

html
<script src="http://sk.localhost:8103/widget/reserveit.js" data-reserveit="VAS-KLIC" async></script>

<!-- vykreslit do jiného prvku -->
<script src="http://sk.localhost:8103/widget/reserveit.js" data-reserveit="VAS-KLIC"
        data-reserveit-target="#rezervace" async></script>

C) Iframe

Pre systémy, ktoré nedovolia vložiť skript. Hostovaná stránka /b/KĽÚČ?embed=1 má priehľadné pozadie a rodičovskej stránke posiela svoju výšku správou postMessage v tvare {type: 'reserveit:height', key, height}. Pri iframe nefungujú CSS premenné, ::part() ani udalosti — tie sú len pri variante so skriptom.

html
<iframe id="reserveit-frame" src="http://sk.localhost:8103/b/VAS-KLIC?embed=1" title="Rezervace"
        style="width:100%;border:0;height:900px" loading="lazy"></iframe>
<script>
  window.addEventListener('message', function (e) {
    if (e.origin !== 'http://sk.localhost:8103' || !e.data || e.data.type !== 'reserveit:height') return;
    document.getElementById('reserveit-frame').style.height = e.data.height + 'px';
  });
</script>

D) Rezervačná stránka

Nemáte web alebo chcete odkaz do Instagramu, Google profilu či e-mailu? V administrácii (Widgety → Nová rezervačná stránka) vytvoríte samostatnú stránku na našej doméne s adresou /p/nazov-xxxxxxxxxx. Nastavíte rozloženie, pozadie, logo, texty nad a pod formulárom a SEO. Stránka používa rovnaký widget, takže platí všetko z tejto dokumentácie okrem vlastného CSS/JS webu.

3Atribúty

Atribúty sa zapisujú na kontajner (pri variante B na tag <script>). Všetky začínajú data-reserveit, takže nekolidujú s ničím na vašom webe.

AtribútHodnotaVýznam
data-reserveitkľúč widgetuPovinný. Určuje, ktorý widget (nastavenia z administrácie) sa zobrazí.
data-reserveit-serviceID službyPredvyberie službu a preskočí krok výberu služby. ID nájdete v administrácii v detaile služby.
data-reserveit-localecs | sk | en | deJazyk widgetu (prebije jazyk nastavený v administrácii).
data-reserveit-prefillJSONPredvyplní údaje zákazníka: name, email, phone, note. Hodí sa pre prihlásených používateľov vášho webu.
data-reserveit-targetCSS selektorLen pri variante jedným tagom — kam widget vykresliť.
html
<div data-reserveit="VAS-KLIC"
     data-reserveit-service="12"
     data-reserveit-locale="en"
     data-reserveit-prefill='{"name":"Jana Nováková","email":"jana@example.com","phone":"+420 777 123 456"}'>
</div>
Tip: JSON v data-reserveit-prefill píšte do jednoduchých apostrofov (atribút) a vnútri používajte dvojité úvodzovky. Ak údaje skladáte na serveri, escapujte ich ako HTML atribút (napr. htmlspecialchars(json_encode($data)) v PHP).

4JavaScript API

Skript vytvára jediný globálny objekt window.ReserveIT. Nič ďalšie do stránky nepridáva. Keďže sa načítava s async, volajte API až po jeho načítaní (pozri onload nižšie) alebo použite deklaratívne kontajnery, ktoré sa pripoja samy.

VolaniePopis
ReserveIT.mount(el, config)Pripojí widget do prvku (element alebo CSS selektor) a vráti inštanciu.
ReserveIT.get(el)Vráti inštanciu widgetu v danom prvku (aj pri deklaratívne vložených), inak null.
ReserveIT.scan()Pripojí kontajnery pridané do stránky neskôr. Deje sa aj automaticky (MutationObserver), ručné volanie je len poistka.
ReserveIT.versionVerzia skriptu (reťazec, napr. „2.0.0“).

ReserveIT.mount()

html
<div id="booking"></div>
<script src="http://sk.localhost:8103/widget/reserveit.js" async onload="initBooking()"></script>
<script>
  function initBooking() {
    const widget = ReserveIT.mount('#booking', {
      key: 'VAS-KLIC',
      locale: 'cs',                 // cs | sk | en | de
      service: 12,                  // předvybraná služba (ID)
      prefill: { name: 'Jana Nováková', email: 'jana@example.com', phone: '+420777123456', note: '' },
      theme:   { primary: '#0f766e', primary2: '#14b8a6', radius: 6, buttonStyle: 'solid' },
      options: { title: 'Objednejte se', showPrices: false, redirectUrl: 'https://example.com/dekujeme' }
    });
  }
</script>

theme a options odovzdané v JS prebijú nastavenia z administrácie len pre tento jeden widget na tejto stránke — nič sa neukladá. Kľúče sú rovnaké ako v administrácii (tabuľka nižšie). Neuvedené kľúče zostanú podľa administrácie.

Metódy inštancie

MetódaPopis
update({theme, options, mode, template})Zmení vzhľad či správanie za behu (napr. prepnutie svetlý/tmavý režim webu). mode: form | calendar | schedule.
reset()Vráti widget na začiatok (výber služby) a vymaže rozpracovaný formulár.
reload()Znova načíta konfiguráciu a voľné termíny zo servera.
destroy()Odpojí widget, zruší poslucháče a vyprázdni kontajner.
js
const widget = ReserveIT.get(document.querySelector('[data-reserveit]'));

// tmavý režim webu
document.querySelector('#dark-toggle').addEventListener('change', function (e) {
  widget.update({ theme: e.target.checked
    ? { background: '#0f172a', surface: '#1e293b', text: '#e2e8f0', muted: '#94a3b8', border: '#334155' }
    : { background: '#ffffff', surface: '#f5f6fb', text: '#1f2333', muted: '#6b7088', border: '#e3e5f0' } });
});

Kľúče theme a options

theme

KľúčHodnotyCSS premennáVýznam
primary#rrggbb--rit-primaryHlavná farba (tlačidlá, výber)
primary2#rrggbb--rit-primary-2Druhá farba prechodu
primaryText#rrggbb--rit-primary-textText na hlavnej farbe
background#rrggbb | transparent--rit-bgPozadie widgetu
surface#rrggbb | transparent--rit-surfacePozadie kariet a polí
text#rrggbb--rit-textText
muted#rrggbb--rit-mutedVedľajší text
border#rrggbb--rit-borderRámčeky a linky
radius0–32 (px)--rit-radiusZaoblenie rohov
fontinherit | inter | montserrat | system | sans | rounded | serif | mono--rit-fontPísmo (inherit = prevziať z webu)
fontSize12–20 (px)--rit-sizeZákladná veľkosť písma
shadownone | soft | strong--rit-shadowTieň kariet
maxWidth300–1400 (px)--rit-maxMaximálna šírka widgetu
densitycompact | normal | comfortable--rit-gapHustota rozloženia (medzery)
buttonStylesolid | gradient | outline—Štýl hlavných tlačidiel

options

KľúčHodnotyVýznam
titletextNadpis widgetu
introtextÚvodný text pod nadpisom
showTitletrue | falseZobraziť nadpis
showPricestrue | falseZobraziť ceny služieb
showDurationtrue | falseZobraziť dĺžku služieb
showDescriptiontrue | falseZobraziť popisy služieb
serviceColorstrue | falseFarebná bodka služby a pracovníka (farba z administrácie)
resourceChoicetrue | falseZákazník si vyberá pracovníka / miesto
phonerequired | optional | hiddenPole telefón
noterequired | optional | hiddenPole poznámka
consenttrue | falseVyžadovať súhlas so spracovaním údajov
consentTexttextText súhlasu
consentUrlURLOdkaz na zásady ochrany osobných údajov
submitLabeltextText odosielacieho tlačidla
successTitletextNadpis po úspešnej rezervácii
successTexttextText po úspešnej rezervácii
redirectUrlURLPo rezervácii presmerovať na túto adresu (ďakovacia stránka)
localecs | sk | en | deJazyk widgetu
weekStart0 | 1Prvý deň týždňa (0 = nedeľa, 1 = pondelok)
scheduleDays5 | 7Počet dní v týždennom rozvrhu
suggestAlternativestrue | falsePonúkať náhradné termíny, keď je vybraný obsadený
poweredBytrue | falseZobrazit odznak „Rezervace přes ReserveIT“ (v tarifu Start vždy zapnuto)
customCssCSSVlastné CSS vložené do Shadow DOM (pozri Vzhľad)

SPA a dynamický obsah

Kontajnery pridané do stránky neskôr (AJAX, modálne okno, React/Vue/Angular) skript nájde sám vďaka MutationObserveru. V komponentoch je však čistejšie widget pripojiť a odpojiť ručne:

jsx
// React
import { useEffect, useRef } from 'react';

export function Booking({ serviceId, user }) {
  const ref = useRef(null);
  useEffect(() => {
    const widget = window.ReserveIT.mount(ref.current, {
      key: 'VAS-KLIC',
      service: serviceId,
      prefill: user ? { name: user.name, email: user.email } : undefined,
    });
    return () => widget.destroy();
  }, [serviceId, user]);
  return <div ref={ref} />;
}
vue
<!-- Vue 3 -->
<template><div ref="box"></div></template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
const box = ref(null);
let widget;
onMounted(() => { widget = window.ReserveIT.mount(box.value, { key: 'VAS-KLIC' }); });
onBeforeUnmount(() => widget && widget.destroy());
</script>

5Udalosti

Widget vyvoláva DOM udalosti (CustomEvent) na svojom kontajneri. Udalosti prebublávajú (bubbles) a prejdú aj zo Shadow DOM (composed), takže ich môžete zachytiť na kontajneri aj na document. Dáta sú v event.detail.

Udalosťevent.detailKedy
reserveit:ready{key, mode}Widget načítal konfiguráciu a je zobrazený.
reserveit:step{key, step}Zákazník prešiel na krok: service | term | details | done.
reserveit:booked{key, code, status, start, end, service, resource, persons, price, manageUrl}Rezervácia bola vytvorená. status = confirmed alebo pending (čaká na potvrdenie prevádzkou).
reserveit:error{key, code, message}Chyba (nenačítaná konfigurácia, obsadený termín, výpadok siete…).

Meranie konverzií (GA4, Meta Pixel, Sklik)

js
document.addEventListener('reserveit:booked', function (e) {
  const b = e.detail;

  // Google Analytics 4 / Google Tag Manager
  if (window.gtag) {
    gtag('event', 'purchase', {
      transaction_id: b.code,
      value: b.price || 0,
      currency: 'CZK',
      items: [{ item_name: b.service, item_category: 'rezervace', quantity: b.persons }]
    });
  }
  window.dataLayer && window.dataLayer.push({ event: 'reserveit_booked', booking: b });

  // Meta (Facebook) Pixel
  if (window.fbq) fbq('track', 'Schedule', { content_name: b.service, value: b.price || 0, currency: 'CZK' });
});

// průchod kroky jako trychtýř
document.addEventListener('reserveit:step', function (e) {
  window.gtag && gtag('event', 'reserveit_step', { step: e.detail.step });
});

Presmerovanie a modálne okno

js
// po rezervaci přesměrovat na děkovací stránku s kódem rezervace
document.addEventListener('reserveit:booked', function (e) {
  setTimeout(function () {
    location.href = '/dekujeme?rezervace=' + encodeURIComponent(e.detail.code);
  }, 1500);
});
html
<button type="button" id="open-booking">Objednat se</button>
<dialog id="booking-dialog" style="width:min(760px,95vw);border:0;border-radius:16px;padding:0">
  <div id="booking-box"></div>
</dialog>

<script src="http://sk.localhost:8103/widget/reserveit.js" async></script>
<script>
  const dialog = document.getElementById('booking-dialog');
  let widget = null;
  document.getElementById('open-booking').addEventListener('click', function () {
    widget = widget || ReserveIT.mount('#booking-box', { key: 'VAS-KLIC' });
    widget.reset();
    dialog.showModal();
  });
  dialog.addEventListener('click', function (e) { if (e.target === dialog) dialog.close(); });
  document.getElementById('booking-box').addEventListener('reserveit:booked', function () {
    setTimeout(function () { dialog.close(); }, 4000);
  });
</script>
Živá ukážka: udalostiKlikajte vo widgete — udalosti sa vypisujú nižšie.
  1. Zatiaľ žiadne udalosti…

6Vzhľad z vášho webu

Widget žije v Shadow DOM — bežné CSS vášho webu (napr. button { … }) doň neprenikne. To je zámer: formulár sa nerozbije ani na webe s agresívnymi štýlmi. Vzhľad však môžete riadiť štyrmi bezpečnými spôsobmi (od najviac odporúčaného):

a) CSS premenné

CSS custom properties nastavené na kontajneri (alebo ktoromkoľvek predkovi) prebijú farby a rozmery z administrácie. Hodí sa na zladenie s dizajnovým systémom webu — stačí odkázať na vaše vlastné premenné.

PremennáVýznamPríklad
--rit-primaryHlavná farba (tlačidlá, výber)#e11d48
--rit-primary-2Druhá farba prechodu#f97316
--rit-primary-textText na hlavnej farbe#ffffff
--rit-bgPozadie widgetutransparent
--rit-surfacePozadie kariet a polí#ffffff
--rit-textText#111827
--rit-mutedVedľajší text#6b7280
--rit-borderRámčeky a linky#e5e7eb
--rit-radiusZaoblenie rohov0px
--rit-fontPísmo (font-family)var(--font-body)
--rit-sizeZákladná veľkosť písma16px
--rit-shadowTieň kariet (box-shadow)none
--rit-maxMaximálna šírka100%
--rit-gapZákladná medzera12px
css
#rezervace {
  --rit-primary: #e11d48;
  --rit-primary-2: #f97316;
  --rit-radius: 0px;
  --rit-font: var(--font-body, Georgia, serif);
}

/* tmavý režim webu */
@media (prefers-color-scheme: dark) {
  #rezervace { --rit-bg: #0b1120; --rit-surface: #111827; --rit-text: #e5e7eb; --rit-muted: #9ca3af; --rit-border: #1f2937; }
}

b) Selektory ::part()

Dôležité prvky widgetu sú sprístupnené cez ::part(). Môžete im nastaviť ľubovoľné CSS vlastnosti (písmo, veľkosť, ohraničenie, tieň, text-transform…). Je to stabilné verejné rozhranie — názvy partov nemeníme.

  • container
  • card
  • header
  • title
  • intro
  • steps
  • step
  • step-active
  • service
  • service-selected
  • summary
  • chip
  • chip-selected
  • calendar
  • nav
  • day
  • day-open
  • day-selected
  • slot
  • slot-selected
  • slot-taken
  • form
  • label
  • input
  • checkbox
  • button
  • button-primary
  • alert
  • alert-error
  • success
  • footer
css
[data-reserveit]::part(button-primary) {
  text-transform: uppercase;
  letter-spacing: .05em;
}
[data-reserveit]::part(title) {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 2rem;
}
[data-reserveit]::part(slot-selected) {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
[data-reserveit]::part(card) {
  border: 0;
  box-shadow: 0 20px 50px -20px rgba(0, 0, 0, .35);
}
::part() nepodporuje potomkov ani pseudotriedy vnútri (::part(card) .niečo nefunguje), stavy typu :hover a :focus-visible však áno: [data-reserveit]::part(slot):hover.

c) Stavové atribúty kontajnera

Kontajner nesie atribúty data-reserveit-state (loading | ready | error) a data-reserveit-step (service | term | details | done). Podľa nich môžete upravovať okolie widgetu — skryť nadpis sekcie po rezervácii, zobraziť placeholder pri načítavaní a pod.

css
/* místo pro widget, dokud se nenačte (bez poskakování stránky) */
[data-reserveit][data-reserveit-state="loading"] { min-height: 480px; }

/* po dokončení rezervace skrýt úvodní text sekce */
#rezervace:has([data-reserveit-step="done"]) .intro { display: none; }

/* chyba → nabídnout telefon */
.fallback-phone { display: none; }
[data-reserveit-state="error"] + .fallback-phone { display: block; }

d) Vlastné CSS v administrácii

V nastaveniach widgetu (Vzhľad → Vlastné CSS) možno zapísať CSS, ktoré sa vloží dovnútra Shadow DOM. Vnútorné triedy začínajú rit- (napr. .rit-card, .rit-slot). Výhoda: platí všade, kde je widget vložený (aj v iframe a na rezervačnej stránke). Nevýhoda: vnútorná štruktúra sa môže s novými verziami meniť — na trvalé úpravy preto odporúčame CSS premenné a ::part().

css Vlastné CSS
.rit-card { border-width: 2px; }
.rit-slot { font-variant-numeric: tabular-nums; }
Živá ukážka: CSS premenné a ::part()
css Štýl použitý v ukážke
.docs-styled [data-reserveit] {
  --rit-primary: #e11d48; --rit-primary-2: #f97316; --rit-radius: 0px; --rit-font: Georgia, serif;
}
.docs-styled [data-reserveit]::part(button-primary) { text-transform: uppercase; letter-spacing: .06em; }
.docs-styled [data-reserveit]::part(slot-selected) { outline: 2px solid #111; outline-offset: 2px; }
.docs-styled [data-reserveit]::part(title) { font-style: italic; }

7Kompletné príklady

Stránka služby s brandingom webu a meraním

html
<!doctype html>
<html lang="cs">
<head>
  <meta charset="utf-8">
  <title>Masáže — objednávka</title>
  <style>
    :root { --brand: #0f766e; --brand-2: #14b8a6; --font-body: "Nunito", system-ui, sans-serif; }
    #rezervace {
      --rit-primary: var(--brand);
      --rit-primary-2: var(--brand-2);
      --rit-font: var(--font-body);
      --rit-radius: 10px;
      --rit-bg: transparent;
      max-width: 820px; margin: 0 auto;
    }
    #rezervace::part(button-primary) { text-transform: uppercase; letter-spacing: .05em; }
    #rezervace[data-reserveit-state="loading"] { min-height: 480px; }
  </style>
</head>
<body>
  <h1>Relaxační masáž</h1>
  <div id="rezervace"
       data-reserveit="VAS-KLIC"
       data-reserveit-service="12"
       data-reserveit-locale="cs"></div>

  <script src="http://sk.localhost:8103/widget/reserveit.js" async></script>
  <script>
    document.getElementById('rezervace').addEventListener('reserveit:booked', function (e) {
      window.gtag && gtag('event', 'purchase', { transaction_id: e.detail.code, value: e.detail.price || 0, currency: 'CZK' });
    });
  </script>
</body>
</html>

Prihlásený zákazník (PHP)

php
<?php $prefill = ['name' => $user->name, 'email' => $user->email, 'phone' => $user->phone]; ?>
<div data-reserveit="VAS-KLIC"
     data-reserveit-prefill="<?= htmlspecialchars(json_encode($prefill), ENT_QUOTES) ?>"></div>
<script src="http://sk.localhost:8103/widget/reserveit.js" async></script>

Viac widgetov na jednej stránke

Každý kontajner je samostatný widget s vlastným stavom, nastaveniami aj udalosťami (rozlíšite ich podľa event.detail.key alebo podľa prvku, na ktorom udalosť zachytíte).

html
<div data-reserveit="VAS-KLIC" data-reserveit-service="12"></div>
<div data-reserveit="VAS-KLIC" data-reserveit-service="15" style="--rit-primary:#7c3aed"></div>
<script src="http://sk.localhost:8103/widget/reserveit.js" async></script>
<script>
  document.addEventListener('reserveit:booked', function (e) {
    console.log('Widget', e.detail.key, 'v prvku', e.target, '→ rezervace', e.detail.code);
  });
</script>

8Bezpečnosť a kompatibilita

  • Žiadne cookies ani localStorage. Widget nič neukladá do prehliadača, takže nepotrebuje súhlas v cookie lište a neprekáža mu blokovanie cookies tretích strán.
  • CORS bez preflightu. Požiadavky na API idú bez prihlasovacích údajov (credentials: omit), server odpovedá CORS hlavičkami aj pri chybách.
  • Striktné CSP, Trusted Types, COEP. Žiadne inline skripty ani eval, štýly cez Constructable Stylesheets, HTML cez Trusted Types politiku reserveit, skript a API posielajú Cross-Origin-Resource-Policy: cross-origin.
  • Izolácia a menné priestory. Jediný globál ReserveIT, atribúty data-reserveit*, udalosti reserveit:*, triedy rit- len vnútri Shadow DOM, písma pod názvami „ReserveIT …“ — nič nekoliduje s vaším webom ani inými knižnicami.
  • Viac widgetov na jednej stránke funguje nezávisle (aj s rovnakým kľúčom): každá inštancia má vlastný stav a požiadavky, ID vnútri majú predponu inštancie. Opakované načítanie skriptu nič neprepíše a „znečistený“ Object.prototype webu neprekáža.
  • Povolené domény. V administrácii možno widget obmedziť na domény vášho webu — inde rezerváciu server odmietne.
  • Prehliadače: aktuálny Chrome, Edge, Firefox, Safari (aj iOS) a Samsung Internet. Kódovanie stránky nehrá rolu (funguje aj na weboch vo windows-1250).

Čo povoliť v Content-Security-Policy webu

Len ak váš web posiela hlavičku CSP. font-src je potrebný kvôli písmam Inter/Montserrat (widget ich načítava z našej domény ako „ReserveIT Inter“ a „ReserveIT Montserrat“) — bez neho widget použije systémové písmo. style-src ani img-src povoľovať nie je potrebné.

csp
script-src    'self' http://sk.localhost:8103;
connect-src   'self' http://sk.localhost:8103;
font-src      'self' http://sk.localhost:8103;
trusted-types reserveit;          # jen pokud web používá direktivu trusted-types
frame-src     http://sk.localhost:8103;            # jen varianta s iframe

Pre české účty platí to isté s doménou https://reserveit.cz.

Platby kartou zabezpečuje GoPay Česko platí kartou