ReserveITDokumentace

Rezervační widget: vlastní CSS a JavaScript

Dokumentace pro vývojáře — vložení, atributy, JavaScript API, události a úpravy vzhledu z vašeho webu.

Obsah
  1. Rychlý start
  2. Způsoby vložení
  3. Atributy
  4. JavaScript API
  5. Události
  6. Vzhled z vašeho webu
  7. Kompletní příklady
  8. Bezpečnost a kompatibilita

1Rychlý start

Rezervační widget ReserveIT vložíte na jakýkoli web dvěma řádky HTML. Vše ostatní — načtení služeb, volné termíny, ověření a uložení rezervace — obstará widget sám přímo proti našemu API. Nepotřebujete žádný backend, knihovnu ani framework.

Klíč widgetu v ukázkách: VAS-KLIC Svůj klíč najdete v administraci v sekci Widgety (u každého widgetu je i hotový kód pro vložení).
html
<div data-reserveit="VAS-KLIC"></div>
<script src="http://localhost:8103/widget/reserveit.js" async></script>

Skript stačí na stránce jednou, i když vkládáte víc widgetů. Widget se vykreslí do Shadow DOM, takže styly vašeho webu ho nerozbijí a jeho styly neovlivní váš web. Přizpůsobí se šířce kontejneru (mobil i desktop).

2Způsoby vložení

A) Kontejner + skript (doporučeno)

Kontejner <div data-reserveit="…"> umístěte tam, kde se má formulář zobrazit. Skript vložte kamkoli na stránku (ideálně před </body>) — najde všechny kontejnery a každý widget připojí samostatně.

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://localhost:8103/widget/reserveit.js" async></script>

B) Jedním tagem

Hodí se do redakčních systémů, kam lze vložit jen jeden kus kódu. Widget se vloží hned za skript do nového prvku s atributem data-reserveit-host (podle něj ho můžete stylovat). Pokud ho chcete jinde, přidejte data-reserveit-target s CSS selektorem cílového prvku. Ostatní atributy (data-reserveit-service, -locale, -prefill) zapište na tag skriptu.

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

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

C) Iframe

Pro systémy, které nedovolí vložit skript. Hostovaná stránka /b/KLÍČ?embed=1 má průhledné pozadí a rodičovské stránce posílá svou výšku zprávou postMessage ve tvaru {type: 'reserveit:height', key, height}. Na iframe nefungují CSS proměnné, ::part() ani události — ty jsou jen u varianty se skriptem.

html
<iframe id="reserveit-frame" src="http://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://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, nebo chcete odkaz do Instagramu, Google profilu či e-mailu? V administraci (Widgety → Nová rezervační stránka) vytvoříte samostatnou stránku na naší doméně s adresou /p/nazev-xxxxxxxxxx. Nastavíte rozvržení, pozadí, logo, texty nad a pod formulářem a SEO. Stránka používá stejný widget, takže platí vše z této dokumentace kromě vlastního CSS/JS webu.

3Atributy

Atributy se zapisují na kontejner (u varianty B na tag <script>). Všechny začínají data-reserveit, takže nekolidují s ničím na vašem webu.

AtributHodnotaVýznam
data-reserveitklíč widgetuPovinný. Určuje, který widget (nastavení z administrace) se zobrazí.
data-reserveit-serviceID službyPředvybere službu a přeskočí krok výběru služby. ID najdete v administraci v detailu služby.
data-reserveit-localecs | sk | en | deJazyk widgetu (přebije jazyk nastavený v administraci).
data-reserveit-prefillJSONPředvyplní údaje zákazníka: name, email, phone, note. Hodí se pro přihlášené uživatele vašeho webu.
data-reserveit-targetCSS selektorJen u varianty jedním tagem — kam widget vykreslit.
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 pište do jednoduchých apostrofů (atribut) a uvnitř používejte dvojité uvozovky. Pokud údaje skládáte na serveru, escapujte je jako HTML atribut (např. htmlspecialchars(json_encode($data)) v PHP).

4JavaScript API

Skript vytváří jediný globální objekt window.ReserveIT. Nic dalšího do stránky nepřidává. Protože se načítá s async, volejte API až po jeho načtení (viz onload níže) nebo použijte deklarativní kontejnery, které se připojí samy.

VoláníPopis
ReserveIT.mount(el, config)Připojí widget do prvku (element nebo CSS selektor) a vrátí instanci.
ReserveIT.get(el)Vrátí instanci widgetu v daném prvku (i u deklarativně vložených), jinak null.
ReserveIT.scan()Připojí kontejnery přidané do stránky později. Děje se i automaticky (MutationObserver), ruční volání je jen pojistka.
ReserveIT.versionVerze skriptu (řetězec, např. „2.0.0“).

ReserveIT.mount()

html
<div id="booking"></div>
<script src="http://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 předané v JS přebijí nastavení z administrace jen pro tento jeden widget na této stránce — nic se neukládá. Klíče jsou stejné jako v administraci (tabulka níže). Neuvedené klíče zůstanou podle administrace.

Metody instance

MetodaPopis
update({theme, options, mode, template})Změní vzhled či chování za běhu (např. přepnutí světlý/tmavý režim webu). mode: form | calendar | schedule.
reset()Vrátí widget na začátek (výběr služby) a vymaže rozpracovaný formulář.
reload()Znovu načte konfiguraci a volné termíny ze serveru.
destroy()Odpojí widget, zruší posluchače a vyprázdní kontejner.
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' } });
});

Klíče theme a options

theme

KlíčHodnotyCSS proměnnáVýznam
primary#rrggbb--rit-primaryHlavní barva (tlačítka, výběr)
primary2#rrggbb--rit-primary-2Druhá barva přechodu
primaryText#rrggbb--rit-primary-textText na hlavní barvě
background#rrggbb | transparent--rit-bgPozadí widgetu
surface#rrggbb | transparent--rit-surfacePozadí karet a polí
text#rrggbb--rit-textText
muted#rrggbb--rit-mutedVedlejší text
border#rrggbb--rit-borderRámečky a linky
radius0–32 (px)--rit-radiusZaoblení rohů
fontinherit | inter | montserrat | system | sans | rounded | serif | mono--rit-fontPísmo (inherit = převzít z webu)
fontSize12–20 (px)--rit-sizeZákladní velikost písma
shadownone | soft | strong--rit-shadowStín karet
maxWidth300–1400 (px)--rit-maxMaximální šířka widgetu
densitycompact | normal | comfortable--rit-gapHustota rozložení (mezery)
buttonStylesolid | gradient | outline—Styl hlavních tlačítek

options

KlíčHodnotyVýznam
titletextNadpis widgetu
introtextÚvodní text pod nadpisem
showTitletrue | falseZobrazit nadpis
showPricestrue | falseZobrazit ceny služeb
showDurationtrue | falseZobrazit délku služeb
showDescriptiontrue | falseZobrazit popisy služeb
serviceColorstrue | falseBarevná tečka služby a pracovníka (barva z administrace)
resourceChoicetrue | falseZákazník si vybírá pracovníka / místo
phonerequired | optional | hiddenPole telefon
noterequired | optional | hiddenPole poznámka
consenttrue | falseVyžadovat souhlas se zpracováním údajů
consentTexttextText souhlasu
consentUrlURLOdkaz na zásady ochrany osobních údajů
submitLabeltextText odesílacího tlačítka
successTitletextNadpis po úspěšné rezervaci
successTexttextText po úspěšné rezervaci
redirectUrlURLPo rezervaci přesměrovat na tuto adresu (děkovací stránka)
localecs | sk | en | deJazyk widgetu
weekStart0 | 1První den týdne (0 = neděle, 1 = pondělí)
scheduleDays5 | 7Počet dní v týdenním rozvrhu
suggestAlternativestrue | falseNabízet náhradní termíny, když je vybraný obsazený
poweredBytrue | falseZobrazit odznak „Rezervace přes ReserveIT“ (v tarifu Start vždy zapnuto)
customCssCSSVlastní CSS vložené do Shadow DOM (viz Vzhled)

SPA a dynamický obsah

Kontejnery přidané do stránky později (AJAX, modální okno, React/Vue/Angular) skript najde sám díky MutationObserveru. V komponentách je ale čistší widget připojit a odpojit ručně:

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>

5Události

Widget vyvolává DOM události (CustomEvent) na svém kontejneru. Události probublávají (bubbles) a projdou i ze Shadow DOM (composed), takže je můžete zachytit na kontejneru i na document. Data jsou v event.detail.

Událostevent.detailKdy
reserveit:ready{key, mode}Widget načetl konfiguraci a je zobrazený.
reserveit:step{key, step}Zákazník přešel na krok: service | term | details | done.
reserveit:booked{key, code, status, start, end, service, resource, persons, price, manageUrl}Rezervace byla vytvořena. status = confirmed nebo pending (čeká na potvrzení provozovnou).
reserveit:error{key, code, message}Chyba (nenačtená konfigurace, obsazený termín, výpadek sítě…).

Měření konverzí (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 });
});

Přesměrování a modální 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://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ázka: událostiKlikejte ve widgetu — události se vypisují níže.
  1. Zatím žádné události…

6Vzhled z vašeho webu

Widget žije ve Shadow DOM — běžné CSS vašeho webu (např. button { … }) do něj neproniká. To je záměr: formulář se nerozbije ani na webu s agresivními styly. Vzhled ale můžete řídit čtyřmi bezpečnými způsoby (od nejvíce doporučeného):

a) CSS proměnné

CSS custom properties nastavené na kontejneru (nebo kterémkoli předkovi) přebijí barvy a rozměry z administrace. Hodí se pro sladění s designovým systémem webu — stačí odkázat na vaše vlastní proměnné.

ProměnnáVýznamPříklad
--rit-primaryHlavní barva (tlačítka, výběr)#e11d48
--rit-primary-2Druhá barva přechodu#f97316
--rit-primary-textText na hlavní barvě#ffffff
--rit-bgPozadí widgetutransparent
--rit-surfacePozadí karet a polí#ffffff
--rit-textText#111827
--rit-mutedVedlejší text#6b7280
--rit-borderRámečky a linky#e5e7eb
--rit-radiusZaoblení rohů0px
--rit-fontPísmo (font-family)var(--font-body)
--rit-sizeZákladní velikost písma16px
--rit-shadowStín karet (box-shadow)none
--rit-maxMaximální šířka100%
--rit-gapZákladní mezera12px
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 jsou zpřístupněné přes ::part(). Můžete jim nastavit libovolné CSS vlastnosti (písmo, velikost, ohraničení, stín, text-transform…). Je to stabilní veřejné rozhraní — názvy partů nemění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 potomky ani pseudotřídy uvnitř (::part(card) .něco nefunguje), stavy typu :hover a :focus-visible ale ano: [data-reserveit]::part(slot):hover.

c) Stavové atributy kontejneru

Kontejner nese atributy data-reserveit-state (loading | ready | error) a data-reserveit-step (service | term | details | done). Podle nich můžete upravovat okolí widgetu — skrýt nadpis sekce po rezervaci, zobrazit placeholder při načítání apod.

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 administraci

V nastavení widgetu (Vzhled → Vlastní CSS) lze zapsat CSS, které se vloží dovnitř Shadow DOM. Vnitřní třídy začínají rit- (např. .rit-card, .rit-slot). Výhoda: platí všude, kde je widget vložený (i v iframe a na rezervační stránce). Nevýhoda: vnitřní struktura se může s novými verzemi měnit — pro trvalé úpravy proto doporučujeme CSS proměnné a ::part().

css Vlastní CSS
.rit-card { border-width: 2px; }
.rit-slot { font-variant-numeric: tabular-nums; }
Živá ukázka: CSS proměnné a ::part()
css Styl použitý v ukázce
.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í příklady

Stránka služby s brandingem webu a měření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://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>

Přihlášený 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://localhost:8103/widget/reserveit.js" async></script>

Více widgetů na jedné stránce

Každý kontejner je samostatný widget s vlastním stavem, nastavením i událostmi (rozlišíte je podle event.detail.key nebo podle prvku, na kterém událost 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://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čnost a kompatibilita

  • Žádné cookies ani localStorage. Widget nic neukládá do prohlížeče, takže nepotřebuje souhlas v cookie liště a nevadí mu blokování cookies třetích stran.
  • CORS bez preflightu. Požadavky na API jdou bez přihlašovacích údajů (credentials: omit), server odpovídá CORS hlavičkami i u chyb.
  • Striktní CSP, Trusted Types, COEP. Žádné inline skripty ani eval, styly přes Constructable Stylesheets, HTML přes Trusted Types politiku reserveit, skript a API posílají Cross-Origin-Resource-Policy: cross-origin.
  • Izolace a jmenné prostory. Jediný globál ReserveIT, atributy data-reserveit*, události reserveit:*, třídy rit- jen uvnitř Shadow DOM, písma pod názvy „ReserveIT …“ — nic nekoliduje s vaším webem ani jinými knihovnami.
  • Více widgetů na jedné stránce funguje nezávisle (i se stejným klíčem): každá instance má vlastní stav a požadavky, ID uvnitř mají předponu instance. Opakované načtení skriptu nic nepřepíše a „znečištěný“ Object.prototype webu nevadí.
  • Povolené domény. V administraci lze widget omezit na domény vašeho webu — jinde rezervaci server odmítne.
  • Prohlížeče: aktuální Chrome, Edge, Firefox, Safari (i iOS) a Samsung Internet. Kódování stránky nehraje roli (funguje i na webech ve windows-1250).

Co povolit v Content-Security-Policy webu

Jen pokud váš web posílá hlavičku CSP. font-src je potřeba kvůli písmům Inter/Montserrat (widget je načítá z naší domény jako „ReserveIT Inter“ a „ReserveIT Montserrat“) — bez něj widget použije systémové písmo. style-src ani img-src povolovat není potřeba.

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

Pro slovenské účty platí totéž s doménou https://reserveit.sk.

Platby kartou zajišťuje GoPay Česko platí kartou