// this file should contain a class that gets info from /api/showings/{id} // and renders the seat chooser // there may be multiple seats selected at once // the input is a matrix with null OR a seat object class Seat { constructor(id, row, col, linked, reserved, type) { this.id = id; // database id this.row = row; this.col = col; this.linked = linked; // linked seat this.reserved = reserved; this.type = type; // seat type } } class SeatChooser { setLoadingStatus(msg) { // if no status element exists, create one if (!this.container.querySelector('#seat-chooser-status')) { let status = document.createElement('div'); // h2 for "Loading..." // span for message status.innerHTML = `

Loading...

`; status.id = 'seat-chooser-status'; status.style.textAlign = 'center'; this.container.appendChild(status); } // set the message this.container.querySelector('#seat-chooser-status-message').innerText = msg; } constructor(showingId, container, submitCallback) { this.showingId = showingId; // id of the showing this.container = container; // the container to render the seat chooser in this.submitCallback = submitCallback; // callback function to call when the user submits the form this.setLoadingStatus("Initializing..."); this.seats = []; // array of nulls and Seat objects this.selectedSeats = []; // array of Seat objects this.init(); } async init() { this.setLoadingStatus("Fetching showing data..."); let showingJson = await (await fetch(`/api/showings/${this.showingId}`)).json(); this.setLoadingStatus("Fetching seats..."); let roomMatrix = await (await fetch(`/api/showings/${this.showingId}/seatMatrix`)).json(); // Matrix -> Matrix this.setLoadingStatus("Processing seats..."); console.log("Matrix -> Matrix"); this.seats = roomMatrix.map((row, rowIndex) => { return row.map((seat, colIndex) => { if (seat === null) { return null; } this.setLoadingStatus("Processing seats... " + Math.round((rowIndex * row.length + colIndex) / (row.length * row.length) * 100) + "%"); return new Seat( seat['seat_id'], seat['seat_row'], seat['seat_column'], seat['seat_linked_id'], seat['reserved'], seat['seat_type'] ); }); }); console.log(this.seats); await this.fixCoronaSeating(); this.render(); } async fixCoronaSeating() { // the seat left or right of a set of reserved seats are not available this.seats.forEach((row, rowIndex) => { row.forEach((seat, colIndex) => { if (seat === null) return; if (seat.reserved) { if (row[colIndex - 1] && !row[colIndex - 1].reserved) row[colIndex - 1].type = 'not_available'; if (row[colIndex + 1] && !row[colIndex + 1].reserved) row[colIndex + 1].type = 'not_available'; } }); }); } render() { this.setLoadingStatus("Rendering..."); let linkedCache = false; // render the seat chooser // this -> row -> seat this.container.innerHTML = ''; this.seats.forEach((row, rowIndex) => { let rowDiv = document.createElement('div'); rowDiv.classList.add('row'); row.forEach((seat, colIndex) => { let seatDiv = document.createElement('div'); if (seat) { seatDiv.classList.add('seat'); if (seat.reserved) { seatDiv.classList.add('reserved'); seatDiv.title = 'This seat is reserved'; } if (seat.linked) { // if cache is false, then this is the first seat in the linked pair if (!linkedCache) { seatDiv.classList.add('linked-left'); linkedCache = true; } else { seatDiv.classList.add('linked-right'); linkedCache = false; } } else { linkedCache = false; } seatDiv.innerText = `${seat.row}-${seat.col}`; switch (seat.type) { case 'not_available': seatDiv.title = 'This seat is not available'; seatDiv.classList.add('disabled'); break; case 'wheelchair': seatDiv.title = 'This seat is wheelchair accessible'; seatDiv.classList.add('wheelchair'); break; case 'loveseat': seatDiv.title = 'This seat is a loveseat'; seatDiv.classList.add('loveseat'); break; case 'standard': seatDiv.title = 'This is a standard seat'; break; } seatDiv.dataset.id = seat.id; seatDiv.dataset.row = seat.row; seatDiv.dataset.col = seat.col; seatDiv.addEventListener('click', (e) => { this.toggleSeat(seat, e); }); } else { seatDiv.classList.add('no-seat'); } rowDiv.appendChild(seatDiv); }); this.container.appendChild(rowDiv); }); // button container let buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; // clear button let clearButton = document.createElement('button'); clearButton.classList.add('button'); clearButton.style.margin = '1rem'; clearButton.innerText = 'Clear'; clearButton.addEventListener('click', () => { this.clearSelection(); }); buttonContainer.appendChild(clearButton); // submit button let submitButton = document.createElement('button'); submitButton.classList.add('button'); submitButton.style.margin = '1rem'; submitButton.innerText = 'Submit'; submitButton.addEventListener('click', () => { this.submitCallback(this); }); buttonContainer.appendChild(submitButton); this.container.appendChild(buttonContainer); } toggleSeat(seat, e) { // toggle the seat // because of linked seats, we need to toggle both seats if (seat.reserved || seat.type == 'not_available') return; e.target.classList.toggle('selected'); if (seat.linked) { // beware that you can click on the left or right seat // the left seat has class linked-left and the right seat has class linked-right let leftbool = e.target.classList.contains('linked-left'); let linkedSeat = this.seats[seat.row - 1][leftbool ? seat.col - 1 : seat.col + 1]; if (leftbool) e.target.nextElementSibling.classList.toggle('selected'); else e.target.previousElementSibling.classList.toggle('selected'); if (this.selectedSeats.includes(linkedSeat)) { this.selectedSeats.splice(this.selectedSeats.indexOf(linkedSeat), 1); this.selectedSeats.splice(this.selectedSeats.indexOf(seat), 1); } else { this.selectedSeats.push(linkedSeat); this.selectedSeats.push(seat); } } else { if (this.selectedSeats.includes(seat)) { this.selectedSeats.splice(this.selectedSeats.indexOf(seat), 1); } else { this.selectedSeats.push(seat); } } } clearSelection() { this.selectedSeats.forEach(seat => { let seatDiv = this.container.querySelector(`[data-id="${seat.id}"]`); seatDiv.classList.remove('selected'); }); this.selectedSeats = []; } } let seatChooser = new SeatChooser(1, document.getElementById('seat-chooser'), (sc) => { // gray overlay and show selected seats let overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100vw'; overlay.style.height = '100vh'; overlay.style.backgroundColor = 'rgba(0,0,0,0.5)'; overlay.style.display = 'flex'; overlay.style.justifyContent = 'center'; overlay.innerText = 'You selected the following seats: ' + sc.selectedSeats.map(seat => `${seat.row}-${seat.col}`).join(', '); overlay.addEventListener('click', () => { overlay.remove(); }); sc.container.appendChild(overlay); });