96 lines
3.4 KiB
JavaScript
96 lines
3.4 KiB
JavaScript
/* Demo booking form: keep the time picker in step with the chosen date.
|
|
*
|
|
* Pick a date -> ask /demo/slots what's still free that day -> rebuild the time
|
|
* dropdown from the answer, so a slot someone else has already booked is never
|
|
* offered. Weekends and out-of-range dates come back closed, with the reason.
|
|
*
|
|
* Progressive enhancement: without JS (or if the fetch fails) the select keeps
|
|
* the full server-rendered list and the POST handler still rejects a taken slot
|
|
* — the server is the authority either way, this just stops the customer
|
|
* choosing a time that was never going to be accepted.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var form = document.querySelector('[data-slots-url]');
|
|
if (!form) return;
|
|
|
|
var url = form.getAttribute('data-slots-url');
|
|
var dateEl = document.getElementById('preferred_date');
|
|
var timeEl = document.getElementById('preferred_time');
|
|
var noteEl = document.getElementById('slot-note');
|
|
if (!dateEl || !timeEl || !noteEl) return;
|
|
|
|
var placeholder = 'Select a time…';
|
|
var baseNote = noteEl.textContent; // e.g. "Eastern Time."
|
|
var request = 0; // drops out-of-order responses
|
|
|
|
function note(message, kind) {
|
|
noteEl.textContent = message;
|
|
noteEl.className = 'hint' + (kind ? ' hint--' + kind : '');
|
|
}
|
|
|
|
function fill(slots, keep) {
|
|
timeEl.innerHTML = '';
|
|
var first = document.createElement('option');
|
|
first.value = '';
|
|
first.textContent = slots.length ? placeholder : 'No times available';
|
|
timeEl.appendChild(first);
|
|
|
|
slots.forEach(function (slot) {
|
|
var option = document.createElement('option');
|
|
option.value = slot;
|
|
option.textContent = slot;
|
|
if (slot === keep) option.selected = true;
|
|
timeEl.appendChild(option);
|
|
});
|
|
timeEl.disabled = slots.length === 0;
|
|
}
|
|
|
|
function load() {
|
|
var value = dateEl.value;
|
|
if (!value) {
|
|
note(baseNote);
|
|
return;
|
|
}
|
|
var ticket = ++request;
|
|
var wanted = timeEl.value;
|
|
timeEl.disabled = true;
|
|
note('Checking availability…');
|
|
|
|
fetch(url + '?date=' + encodeURIComponent(value), {
|
|
headers: { 'Accept': 'application/json' }
|
|
}).then(function (res) {
|
|
return res.json().then(function (body) {
|
|
if (!res.ok) throw new Error(body.error || 'Could not load times.');
|
|
return body;
|
|
});
|
|
}).then(function (data) {
|
|
if (ticket !== request) return; // a newer date won the race
|
|
if (!data.open) {
|
|
fill([], null);
|
|
note(data.reason || 'That date is not available.', 'warn');
|
|
return;
|
|
}
|
|
fill(data.slots, wanted);
|
|
if (!data.slots.length) {
|
|
note('Every time on that day is booked — please choose another date.', 'warn');
|
|
} else if (wanted && data.slots.indexOf(wanted) === -1) {
|
|
// The time they had chosen went while they were filling the form in.
|
|
note('That time has just been booked. Please pick another.', 'warn');
|
|
} else {
|
|
note(baseNote);
|
|
}
|
|
}).catch(function () {
|
|
if (ticket !== request) return;
|
|
// Fail open: leave whatever is in the select and let the server decide.
|
|
timeEl.disabled = false;
|
|
note("Couldn't check availability — we'll confirm your time by email.", 'warn');
|
|
});
|
|
}
|
|
|
|
dateEl.addEventListener('change', load);
|
|
// A date can survive a failed submit or a browser restore.
|
|
if (dateEl.value) load();
|
|
})();
|