Obsah
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.
VAS-KLIC
Svůj klíč najdete v administraci v sekci Widgety (u každého widgetu je i hotový kód pro vložení). <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ě.
<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.
<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.
<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.
| Atribut | Hodnota | Význam |
|---|---|---|
data-reserveit | klíč widgetu | Povinný. Určuje, který widget (nastavení z administrace) se zobrazí. |
data-reserveit-service | ID služby | Předvybere službu a přeskočí krok výběru služby. ID najdete v administraci v detailu služby. |
data-reserveit-locale | cs | sk | en | de | Jazyk widgetu (přebije jazyk nastavený v administraci). |
data-reserveit-prefill | JSON | Předvyplní údaje zákazníka: name, email, phone, note. Hodí se pro přihlášené uživatele vašeho webu. |
data-reserveit-target | CSS selektor | Jen u varianty jedním tagem — kam widget vykreslit. |
<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>
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.version | Verze skriptu (řetězec, např. „2.0.0“). |
ReserveIT.mount()
<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
| Metoda | Popis |
|---|---|
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. |
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íč | Hodnoty | CSS proměnná | Význam |
|---|---|---|---|
primary | #rrggbb | --rit-primary | Hlavní barva (tlačítka, výběr) |
primary2 | #rrggbb | --rit-primary-2 | Druhá barva přechodu |
primaryText | #rrggbb | --rit-primary-text | Text na hlavní barvě |
background | #rrggbb | transparent | --rit-bg | Pozadí widgetu |
surface | #rrggbb | transparent | --rit-surface | Pozadí karet a polí |
text | #rrggbb | --rit-text | Text |
muted | #rrggbb | --rit-muted | Vedlejší text |
border | #rrggbb | --rit-border | Rámečky a linky |
radius | 0–32 (px) | --rit-radius | Zaoblení rohů |
font | inherit | inter | montserrat | system | sans | rounded | serif | mono | --rit-font | Písmo (inherit = převzít z webu) |
fontSize | 12–20 (px) | --rit-size | Základní velikost písma |
shadow | none | soft | strong | --rit-shadow | Stín karet |
maxWidth | 300–1400 (px) | --rit-max | Maximální šířka widgetu |
density | compact | normal | comfortable | --rit-gap | Hustota rozložení (mezery) |
buttonStyle | solid | gradient | outline | — | Styl hlavních tlačítek |
options
| Klíč | Hodnoty | Význam |
|---|---|---|
title | text | Nadpis widgetu |
intro | text | Úvodní text pod nadpisem |
showTitle | true | false | Zobrazit nadpis |
showPrices | true | false | Zobrazit ceny služeb |
showDuration | true | false | Zobrazit délku služeb |
showDescription | true | false | Zobrazit popisy služeb |
serviceColors | true | false | Barevná tečka služby a pracovníka (barva z administrace) |
resourceChoice | true | false | Zákazník si vybírá pracovníka / místo |
phone | required | optional | hidden | Pole telefon |
note | required | optional | hidden | Pole poznámka |
consent | true | false | Vyžadovat souhlas se zpracováním údajů |
consentText | text | Text souhlasu |
consentUrl | URL | Odkaz na zásady ochrany osobních údajů |
submitLabel | text | Text odesílacího tlačítka |
successTitle | text | Nadpis po úspěšné rezervaci |
successText | text | Text po úspěšné rezervaci |
redirectUrl | URL | Po rezervaci přesměrovat na tuto adresu (děkovací stránka) |
locale | cs | sk | en | de | Jazyk widgetu |
weekStart | 0 | 1 | První den týdne (0 = neděle, 1 = pondělí) |
scheduleDays | 5 | 7 | Počet dní v týdenním rozvrhu |
suggestAlternatives | true | false | Nabízet náhradní termíny, když je vybraný obsazený |
poweredBy | true | false | Zobrazit odznak „Rezervace přes ReserveIT“ (v tarifu Start vždy zapnuto) |
customCss | CSS | Vlastní 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ě:
// 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 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álost | event.detail | Kdy |
|---|---|---|
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)
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
// 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);
});
<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>
- 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ýznam | Příklad |
|---|---|---|
--rit-primary | Hlavní barva (tlačítka, výběr) | #e11d48 |
--rit-primary-2 | Druhá barva přechodu | #f97316 |
--rit-primary-text | Text na hlavní barvě | #ffffff |
--rit-bg | Pozadí widgetu | transparent |
--rit-surface | Pozadí karet a polí | #ffffff |
--rit-text | Text | #111827 |
--rit-muted | Vedlejší text | #6b7280 |
--rit-border | Rámečky a linky | #e5e7eb |
--rit-radius | Zaoblení rohů | 0px |
--rit-font | Písmo (font-family) | var(--font-body) |
--rit-size | Základní velikost písma | 16px |
--rit-shadow | Stín karet (box-shadow) | none |
--rit-max | Maximální šířka | 100% |
--rit-gap | Základní mezera | 12px |
#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.
containercardheadertitleintrostepsstepstep-activeserviceservice-selectedsummarychipchip-selectedcalendarnavdayday-openday-selectedslotslot-selectedslot-takenformlabelinputcheckboxbuttonbutton-primaryalertalert-errorsuccessfooter
[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.
/* 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().
.rit-card { border-width: 2px; }
.rit-slot { font-variant-numeric: tabular-nums; }
.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
<!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 $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).
<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 politikureserveit, skript a API posílajíCross-Origin-Resource-Policy: cross-origin. - Izolace a jmenné prostory. Jediný globál
ReserveIT, atributydata-reserveit*, událostireserveit:*, třídyrit-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.prototypewebu 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.
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.