byceps/static/behavior/seating.js
/**
* Make tooltips appear on hover over seats.
*/
function init_seat_tooltips() {
document.querySelectorAll('.seat-with-tooltip')
.forEach(seatContainer => {
seatContainer.addEventListener('mouseover', () => {
const dataset = seatContainer.dataset;
let tooltipHTML = '<div class="seat-label">' + dataset.label + '</div>';
const ticketId = dataset.ticketId;
if (ticketId !== undefined) {
if (seatContainer.querySelector('.seat').classList.contains('seat--managed')) {
const ticketCode = document
.getElementById('ticket-selection')
.querySelector('.ticket[data-id="' + ticketId + '"]')
.dataset.code;
tooltipHTML += '<div class="seat-ticket-code"><span class="dimmed">Ticket:</span> <strong>' + ticketCode + '</strong></div>';
}
const occupierName = dataset.occupierName;
if (occupierName !== undefined) {
tooltipHTML += '<div class="seat-occupier">'
+ '<div class="seat-occupier-avatar"><div class="avatar size-48"><img src="' + dataset.occupierAvatar + '"></div></div>'
+ '<div class="seat-occupier-name"><span class="dimmed">reserviert von</span><br><strong>' + escape_html(occupierName) + '</strong></div>'
+ '</div>';
}
}
const tooltipNode = document.createElement('div');
tooltipNode.classList.add('seat-tooltip');
tooltipNode.innerHTML = tooltipHTML;
seatContainer.appendChild(tooltipNode);
});
seatContainer.addEventListener('mouseout', () => {
const tooltip = seatContainer.querySelector('.seat-tooltip');
// Tooltip element won't exist at this point if the mouse
// cursor is above a seat when the page is reloaded, so the
// 'mouseout' event will fire upon mouse move even though no
// 'mouseover' has occurred, thus no tooltip has been
// generated that could be removed.
if (tooltip !== null) {
tooltip.remove();
}
});
});
}
/**
* Initialize the seat management system.
*/
function init_seat_management(selected_ticket_id) {
init_ticket_selector();
const tickets = document.querySelectorAll('.ticket');
if (tickets.length > 0) {
// Pre-select ticket.
let ticket_to_preselect;
if (selected_ticket_id !== null) {
ticket_to_preselect = find_ticket_by_id(selected_ticket_id);
} else {
// Select first ticket by default.
ticket_to_preselect = tickets[0];
}
select_ticket(ticket_to_preselect);
mark_seats_as_occupiable();
init_occupiable_seats();
mark_managed_seats();
mark_seat_for_selected_managed_ticket();
wire_seat_release_button();
}
}
/**
* Make ticket selector work.
*/
function init_ticket_selector() {
_make_ticket_selector_open_on_click();
_select_ticket_on_click();
}
/**
* Make ticket selector open on click.
*/
function _make_ticket_selector_open_on_click() {
const ticket_selector = document.querySelector('.ticket-selector');
if (ticket_selector !== null) {
ticket_selector
.addEventListener('click', () => ticket_selector.classList.toggle('ticket-selector--open'));
}
}
/**
* Select ticket on click on ticket in ticket selector.
*/
function _select_ticket_on_click() {
document.querySelectorAll('.ticket')
.forEach(ticket => ticket.addEventListener('click', () => select_ticket(ticket)));
}
/**
* Return the ticket node in the selector with that ticket ID,
* or `null` if not found.
*/
function find_ticket_by_id(ticket_id) {
const ticket = document
.getElementById('ticket-selection')
.querySelector('.ticket[data-id="' + ticket_id + '"]');
if (ticket === null) {
return null;
}
return ticket;
}
/**
* Set the ticket as the selected one in the ticket selector (both
* visually and regarding its internal state).
*/
function select_ticket(ticket) {
const ticket_id = ticket.dataset.id;
const ticket_code = ticket.dataset.code;
const ticket_selection = document.getElementById('ticket-selection');
ticket_selection.dataset.selectedId = ticket_id;
ticket_selection.dataset.selectedCode = ticket_code;
ticket_selection.querySelectorAll('.ticket--current')
.forEach(ticket => ticket.classList.remove('ticket--current'));
ticket.classList.add('ticket--current');
set_current_managed_seat(ticket_id);
document.getElementById('release-seat-trigger').disabled = !is_ticket_occupying_seat(ticket);
}
function set_current_managed_seat(ticket_id) {
document.querySelectorAll('.area .seat--managed-current')
.forEach(seat => seat.classList.remove('seat--managed-current'));
document.querySelectorAll('.seat-with-tooltip[data-ticket-id="' + ticket_id + '"] .seat--managed')
.forEach(seat => seat.classList.add('seat--managed-current'));
}
function wire_seat_release_button() {
const release_seat_trigger = document.getElementById('release-seat-trigger');
if (release_seat_trigger !== null) {
release_seat_trigger.addEventListener('click', () => {
const seat_label = get_selected_seat_label();
const confirmation_label = seat_label + ' (belegt durch Ticket ' + get_selected_ticket_code() + ') freigeben?';
if (confirm(confirmation_label)) {
const ticket_id = get_selected_ticket_id();
const request_url = '/seating/ticket/' + ticket_id + '/seat';
fetch(request_url, {method: 'DELETE'})
.then(response => reload_with_selected_ticket(ticket_id));
};
return false;
});
}
}
function get_selected_ticket_id() {
return document.getElementById('ticket-selection').dataset.selectedId;
}
function get_selected_ticket_code() {
return document.getElementById('ticket-selection').dataset.selectedCode;
}
function is_ticket_occupying_seat(ticket) {
return ticket.dataset.seatLabel !== undefined;
}
function get_selected_seat_label() {
const ticket_selection = document.getElementById('ticket-selection');
const ticket_code = ticket_selection.dataset.selectedCode;
const ticket = ticket_selection.querySelector('.ticket[data-code="' + ticket_code + '"]');
return ticket.dataset.seatLabel;
}
function get_managed_ticket_ids() {
const managed_ticket_ids = new Set();
document.getElementById('ticket-selection')
.querySelectorAll('.ticket')
.forEach(ticket => managed_ticket_ids.add(ticket.dataset.id));
return managed_ticket_ids;
}
function get_seats_with_ticket_code(ticket_id) {
return document.querySelectorAll('.seat-with-tooltip[data-ticket-id="' + ticket_id + '"] .seat');
}
function mark_managed_seats() {
const managed_ticket_ids = get_managed_ticket_ids();
for (const ticket_id of managed_ticket_ids.values()) {
get_seats_with_ticket_code(ticket_id)
.forEach(seat => seat.classList.add('seat--managed'));
}
}
function mark_seat_for_selected_managed_ticket() {
const ticket_id = get_selected_ticket_id();
get_seats_with_ticket_code(ticket_id)
.forEach(seat => seat.classList.add('seat--managed-current'));
}
function mark_seats_as_occupiable() {
document.querySelectorAll('.seat')
.forEach(seat => {
const ticket_id = seat.parentNode.dataset.ticketId;
if (ticket_id === undefined) {
seat.classList.add('seat--occupiable');
}
}
);
}
function init_occupiable_seats() {
document.querySelectorAll('.seat--occupiable')
.forEach(seat => {
seat.addEventListener('click', () => {
const seat_label = seat.parentNode.dataset.label;
const confirmation_label = seat_label + ' mit Ticket ' + get_selected_ticket_code() + ' reservieren?';
if (confirm(confirmation_label)) {
const seat_id = seat.parentNode.dataset.seatId;
const ticket_id = get_selected_ticket_id();
const request_url = '/seating/ticket/' + ticket_id + '/seat/' + seat_id;
fetch(request_url, {method: 'POST'})
.then(response => reload_with_selected_ticket(ticket_id));
};
return false;
});
});
}
function reload_with_selected_ticket(ticket_id) {
location.href = location.href.split('?')[0] + '?ticket_id=' + ticket_id;
}
/**
* Use browser's built-in functionality to quickly and safely escape
* HTML in the given string.
*/
function escape_html(str) {
const elem = document.createElement('span');
elem.appendChild(document.createTextNode(str));
return elem.innerHTML;
}