You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
398 lines
13 KiB
398 lines
13 KiB
/**
|
|
* Kontaktní formulář - JavaScript (script.js)
|
|
* ==============================================
|
|
* Klientská validace formuláře a UX vylepšení.
|
|
*
|
|
* DŮLEŽITÉ: Klientská validace (JS) je jen pro pohodlí uživatele!
|
|
* Serverová validace (PHP) je nezbytná pro bezpečnost.
|
|
* Uživatel může JS vypnout nebo obejít - PHP validaci obejít nemůže.
|
|
*
|
|
* Obsah:
|
|
* 1. Inicializace
|
|
* 2. Validace v reálném čase (při psaní)
|
|
* 3. Validace při odeslání
|
|
* 4. Počítadlo znaků
|
|
* 5. Vizuální vylepšení
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
// ============================================================
|
|
// KONFIGURACE PRAVIDEL VALIDACE
|
|
// ============================================================
|
|
|
|
/**
|
|
* Objekt s validačními pravidly pro každé pole.
|
|
* Každé pole má funkci validate() která vrátí string s chybou nebo null.
|
|
*/
|
|
const validators = {
|
|
|
|
firstName: {
|
|
validate(value) {
|
|
if (!value.trim()) return 'Jméno je povinné.';
|
|
if (value.trim().length < 2) return 'Jméno musí mít alespoň 2 znaky.';
|
|
if (value.trim().length > 50) return 'Jméno nesmí být delší než 50 znaků.';
|
|
return null; // null = validní
|
|
}
|
|
},
|
|
|
|
lastName: {
|
|
validate(value) {
|
|
if (!value.trim()) return 'Příjmení je povinné.';
|
|
if (value.trim().length < 2) return 'Příjmení musí mít alespoň 2 znaky.';
|
|
if (value.trim().length > 50) return 'Příjmení nesmí být delší než 50 znaků.';
|
|
return null;
|
|
}
|
|
},
|
|
|
|
email: {
|
|
validate(value) {
|
|
if (!value.trim()) return 'Email je povinný.';
|
|
// Regulární výraz pro validaci emailu
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
if (!emailRegex.test(value.trim())) return 'Zadejte platnou emailovou adresu.';
|
|
return null;
|
|
}
|
|
},
|
|
|
|
phone: {
|
|
validate(value) {
|
|
if (!value.trim()) return null; // Telefon je volitelný
|
|
// Povolené formáty: +420777123456, 00420777123456, 777 123 456
|
|
const phoneRegex = /^(\+|00)?[0-9\s\-]{9,15}$/;
|
|
if (!phoneRegex.test(value.trim())) return 'Zadejte platné telefonní číslo.';
|
|
return null;
|
|
}
|
|
},
|
|
|
|
subject: {
|
|
validate(value) {
|
|
if (!value) return 'Vyberte předmět zprávy.';
|
|
if (value.length > 100) return 'Předmět nesmí být delší než 100 znaků.';
|
|
return null;
|
|
}
|
|
},
|
|
|
|
message: {
|
|
validate(value) {
|
|
if (!value.trim()) return 'Zpráva je povinná.';
|
|
if (value.trim().length < 10) return 'Zpráva musí mít alespoň 10 znaků.';
|
|
if (value.trim().length > 2000) return 'Zpráva nesmí být delší než 2000 znaků.';
|
|
return null;
|
|
}
|
|
},
|
|
|
|
agreement: {
|
|
validate(checked) {
|
|
if (!checked) return 'Musíte souhlasit se zpracováním osobních údajů.';
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
|
|
// ============================================================
|
|
// INICIALIZACE
|
|
// ============================================================
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const form = document.getElementById('contactForm');
|
|
if (!form) return; // Formulář neexistuje (např. na stránce s úspěchem)
|
|
|
|
// Inicializace všech funkcí
|
|
initRealtimeValidation(); // Validace při psaní
|
|
initFormSubmit(); // Validace při odeslání
|
|
initCharCounter(); // Počítadlo znaků pro textarea
|
|
initFloatingLabels(); // Vizuální efekty
|
|
});
|
|
|
|
// ============================================================
|
|
// 1. VALIDACE V REÁLNÉM ČASE
|
|
// ============================================================
|
|
|
|
/**
|
|
* Přidá posluchače na všechna pole - validuje při odchodu z pole (blur).
|
|
* Uživatel dostane zpětnou vazbu ihned po vyplnění každého pole.
|
|
*/
|
|
function initRealtimeValidation() {
|
|
|
|
// Textová a email pole - validace při ztrátě focusu (blur)
|
|
['firstName', 'lastName', 'email', 'phone', 'subject', 'message'].forEach(function (fieldName) {
|
|
const field = document.getElementById(fieldName);
|
|
if (!field) return;
|
|
|
|
// blur = uživatel opustil pole (přesunul focus jinam)
|
|
field.addEventListener('blur', function () {
|
|
validateField(this);
|
|
});
|
|
|
|
// input = uživatel píše - pokud pole bylo označeno jako nevalidní,
|
|
// začneme znovu validovat při každém stiku klávesy (okamžitá oprava)
|
|
field.addEventListener('input', function () {
|
|
if (this.classList.contains('is-invalid')) {
|
|
validateField(this);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Checkbox - validace při změně
|
|
const agreement = document.getElementById('agreement');
|
|
if (agreement) {
|
|
agreement.addEventListener('change', function () {
|
|
validateCheckbox(this);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validuje jedno textové/email/select pole.
|
|
* Přidá nebo odebere CSS třídy is-valid / is-invalid.
|
|
*
|
|
* @param {HTMLElement} field - Pole formuláře
|
|
* @returns {boolean} - True = validní, False = nevalidní
|
|
*/
|
|
function validateField(field) {
|
|
const name = field.id;
|
|
const validator = validators[name];
|
|
if (!validator) return true; // Neznámé pole považujeme za validní
|
|
|
|
// Získání hodnoty
|
|
const value = field.value;
|
|
|
|
// Spuštění validační funkce
|
|
const error = validator.validate(value);
|
|
|
|
if (error) {
|
|
// Pole je NEVALIDNÍ
|
|
setFieldInvalid(field, error);
|
|
return false;
|
|
} else {
|
|
// Pole je VALIDNÍ
|
|
setFieldValid(field);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validuje checkbox souhlas.
|
|
*
|
|
* @param {HTMLInputElement} checkbox - Checkbox element
|
|
* @returns {boolean}
|
|
*/
|
|
function validateCheckbox(checkbox) {
|
|
const error = validators.agreement.validate(checkbox.checked);
|
|
|
|
if (error) {
|
|
checkbox.classList.add('is-invalid');
|
|
// Vložíme nebo aktualizujeme chybovou zprávu
|
|
updateFeedback(checkbox, error);
|
|
return false;
|
|
} else {
|
|
checkbox.classList.remove('is-invalid');
|
|
checkbox.classList.add('is-valid'); // Zelená barva pro zaškrtnutý checkbox
|
|
clearFeedback(checkbox);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 2. VALIDACE PŘI ODESLÁNÍ
|
|
// ============================================================
|
|
|
|
/**
|
|
* Přidá posluchač na odeslání formuláře.
|
|
* Zkontroluje všechna pole a zobrazí souhrn chyb.
|
|
*/
|
|
function initFormSubmit() {
|
|
const form = document.getElementById('contactForm');
|
|
if (!form) return;
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
// Zastavíme výchozí odeslání - nejprve validujeme JS
|
|
// Pokud je vše ok, necháme formulář odeslat na server (PHP validace)
|
|
let isFormValid = true;
|
|
|
|
// Validace všech textových polí
|
|
['firstName', 'lastName', 'email', 'phone', 'subject', 'message'].forEach(function (fieldName) {
|
|
const field = document.getElementById(fieldName);
|
|
if (field && !validateField(field)) {
|
|
isFormValid = false;
|
|
}
|
|
});
|
|
|
|
// Validace checkboxu
|
|
const agreement = document.getElementById('agreement');
|
|
if (agreement && !validateCheckbox(agreement)) {
|
|
isFormValid = false;
|
|
}
|
|
|
|
if (!isFormValid) {
|
|
// CHYBA: Zastavíme odeslání
|
|
e.preventDefault();
|
|
|
|
// Přidáme animaci třesení na chybná pole
|
|
document.querySelectorAll('.is-invalid').forEach(function (field) {
|
|
field.classList.add('shake');
|
|
setTimeout(function () {
|
|
field.classList.remove('shake');
|
|
}, 300);
|
|
});
|
|
|
|
// Scrollování na první chybné pole
|
|
const firstError = document.querySelector('.is-invalid');
|
|
if (firstError) {
|
|
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
firstError.focus();
|
|
}
|
|
} else {
|
|
// VŠE OK: Zobrazíme stav načítání na tlačítku
|
|
setButtonLoading(true);
|
|
// Formulář se odešle - PHP zpracuje dál
|
|
}
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// 3. POČÍTADLO ZNAKŮ
|
|
// ============================================================
|
|
|
|
/**
|
|
* Sleduje počet znaků v textarea a aktualizuje počítadlo.
|
|
* Mění barvu počítadla při přiblížení k limitu.
|
|
*/
|
|
function initCharCounter() {
|
|
const textarea = document.getElementById('message');
|
|
const counter = document.getElementById('charCount');
|
|
if (!textarea || !counter) return;
|
|
|
|
const maxLength = 2000; // Maximální počet znaků
|
|
const warnAt = 1800; // Varování při 1800 znacích (90%)
|
|
|
|
function updateCounter() {
|
|
const current = textarea.value.length;
|
|
counter.textContent = `${current}/${maxLength}`;
|
|
|
|
// Změna barvy podle počtu znaků
|
|
counter.classList.remove('near-limit', 'at-limit');
|
|
if (current >= maxLength) {
|
|
counter.classList.add('at-limit'); // Červená - dosažen limit
|
|
} else if (current >= warnAt) {
|
|
counter.classList.add('near-limit'); // Oranžová - blíží se limit
|
|
}
|
|
}
|
|
|
|
// Okamžitá aktualizace při psaní
|
|
textarea.addEventListener('input', updateCounter);
|
|
|
|
// Inicializace (pro případ, že textarea má předvyplněný text z PHP)
|
|
updateCounter();
|
|
}
|
|
|
|
// ============================================================
|
|
// 4. VIZUÁLNÍ VYLEPŠENÍ
|
|
// ============================================================
|
|
|
|
/**
|
|
* Inicializuje efekty pro vstupní pole:
|
|
* - Zvýraznění řádku při focusu
|
|
* - Animace ikonek
|
|
*/
|
|
function initFloatingLabels() {
|
|
// Přidání efektu zaměření na skupiny s ikonou
|
|
document.querySelectorAll('.input-group').forEach(function (group) {
|
|
const input = group.querySelector('.form-control, .form-select');
|
|
if (!input) return;
|
|
|
|
// Focus = zaměření na pole
|
|
input.addEventListener('focus', function () {
|
|
group.classList.add('focused');
|
|
});
|
|
|
|
// Blur = ztráta focusu
|
|
input.addEventListener('blur', function () {
|
|
group.classList.remove('focused');
|
|
});
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// POMOCNÉ FUNKCE
|
|
// ============================================================
|
|
|
|
/**
|
|
* Označí pole jako NEVALIDNÍ a zobrazí chybovou zprávu.
|
|
*
|
|
* @param {HTMLElement} field - Vstupní pole
|
|
* @param {string} message - Chybová zpráva
|
|
*/
|
|
function setFieldInvalid(field, message) {
|
|
field.classList.remove('is-valid');
|
|
field.classList.add('is-invalid');
|
|
updateFeedback(field, message);
|
|
}
|
|
|
|
/**
|
|
* Označí pole jako VALIDNÍ a skryje chybovou zprávu.
|
|
*
|
|
* @param {HTMLElement} field - Vstupní pole
|
|
*/
|
|
function setFieldValid(field) {
|
|
field.classList.remove('is-invalid');
|
|
field.classList.add('is-valid');
|
|
clearFeedback(field);
|
|
}
|
|
|
|
/**
|
|
* Aktualizuje nebo vytvoří element s chybovou zprávou pod polem.
|
|
*
|
|
* @param {HTMLElement} field - Vstupní pole
|
|
* @param {string} message - Text chybové zprávy
|
|
*/
|
|
function updateFeedback(field, message) {
|
|
// Hledáme existující zprávu
|
|
let feedback = field.parentElement.querySelector('.invalid-feedback');
|
|
|
|
if (!feedback) {
|
|
// Vytvoříme nový element pro zprávu
|
|
feedback = document.createElement('div');
|
|
feedback.className = 'invalid-feedback';
|
|
field.parentElement.appendChild(feedback);
|
|
}
|
|
|
|
feedback.textContent = message;
|
|
}
|
|
|
|
/**
|
|
* Odstraní chybovou zprávu pod polem.
|
|
*
|
|
* @param {HTMLElement} field - Vstupní pole
|
|
*/
|
|
function clearFeedback(field) {
|
|
const feedback = field.parentElement.querySelector('.invalid-feedback');
|
|
if (feedback) {
|
|
feedback.remove(); // Smazání z DOM
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Nastaví stav načítání tlačítka Odeslat.
|
|
*
|
|
* @param {boolean} loading - True = načítání, False = normální stav
|
|
*/
|
|
function setButtonLoading(loading) {
|
|
const btn = document.getElementById('submitBtn');
|
|
if (!btn) return;
|
|
|
|
if (loading) {
|
|
btn.disabled = true;
|
|
btn.classList.add('loading');
|
|
// Spinner (Bootstrap) + text
|
|
btn.innerHTML = `
|
|
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
|
Odesílám...
|
|
`;
|
|
} else {
|
|
btn.disabled = false;
|
|
btn.classList.remove('loading');
|
|
btn.innerHTML = '<i class="bi bi-send me-2"></i>Odeslat zprávu';
|
|
}
|
|
}
|
|
|