1/** 2 * TicketCardPairingCoordinator 3 * ----------------------------- 4 * Reusable, page-agnostic helper that keeps <ticket-card-layout ticket-type="pairing"> 5 * cards (js/Layouts/TicketCard.js) in sync with the "companion" tickets they require. 6 * The card itself stays a dumb presentational primitive (status/quantity/error-message 7 * are just properties it renders); this class owns the actual pairing business rule so 8 * that rule doesn't have to be re-written by every consumer page that uses the card -- 9 * only registered once per page, same as the card itself. 10 * 11 * For each registered pairing ticket, whenever its own quantity or any of its 12 * companions' quantities change, recomputes `status`: 13 * - own qty > 0, a companion is selected -> "added" (card shows the +/- stepper) 14 * - own qty > 0, no companion selected -> "error" (card shows the negative 15 * "Choose options" + a fixed message: 16 * "Ticket must be paired with one of the 17 * following tickets first: <names>") 18 * - own qty == 0, a companion is selected -> "paired" (card shows "Add to cart") 19 * - own qty == 0, no companion selected -> "idle" (card shows "Choose options") 20 * 21 * Auto-listens to each registered card's own `quantity-changed` event (fired by the 22 * card's +/- stepper), so same-page stepper interactions need no extra wiring. For 23 * quantity changes that don't originate from the stepper (e.g. an "add to cart" flow 24 * completing, or removing an item from a cart summary elsewhere on the page), call 25 * notify(ticketId, quantity) manually -- the card's own getQuantity() gives you the 26 * current value to pass along. 27 * 28 * Cost: notify() only touches the pairing tickets that reference the changed id 29 * (typically a handful per page), not every registered ticket. 30 * 31 * @example 32 * const coordinator = new TicketCardPairingCoordinator(); 33 * coordinator.registerTicket('10', generalAdmissionCardEl); 34 * coordinator.registerPairing('20', parkingPassCardEl, [ 35 * { id: '10', name: 'General Admission' }, 36 * ]); 37 * // after your own add-to-cart flow finishes elsewhere on the page: 38 * coordinator.notify('10', generalAdmissionCardEl.getQuantity()); 39 */ 40class TicketCardPairingCoordinator { 41 constructor() { 42 this._tickets = new Map(); // id -> { element, quantity } 43 this._pairings = new Map(); // id -> { element, companions: [{id, name}] } 44 } 45 46 /** 47 * Registers any ticket whose quantity a pairing ticket might depend on 48 * (called automatically by registerPairing() for the pairing ticket itself). 49 * @param {string} id 50 * @param {HTMLElement} element - a <ticket-card-layout> instance. 51 */ 52 registerTicket(id, element) { 53 if (this._tickets.has(id)) return; 54 55 this._tickets.set(id, { element, quantity: element.getQuantity() }); 56 57 element.addEventListener('quantity-changed', (e) => { 58 this.notify(id, e.detail.quantity); 59 }); 60 } 61 62 /** 63 * @param {string} id 64 * @param {HTMLElement} element - a <ticket-card-layout ticket-type="pairing"> instance. 65 * @param {{id: string, name: string}[]} companions - The tickets it must be paired with. 66 */ 67 registerPairing(id, element, companions) { 68 this.registerTicket(id, element); 69 this._pairings.set(id, { element, companions }); 70 this._recompute(id); 71 } 72 73 /** 74 * Call whenever a tracked ticket's quantity changes. The stepper does this 75 * automatically; call it yourself for changes originating elsewhere. 76 * @param {string} ticketId 77 * @param {number} quantity 78 */ 79 notify(ticketId, quantity) { 80 const ticket = this._tickets.get(ticketId); 81 if (!ticket) return; 82 83 ticket.quantity = quantity; 84 85 this._pairings.forEach((pairing, pairingId) => { 86 const isSelf = pairingId === ticketId; 87 const isCompanion = pairing.companions.some(c => c.id === ticketId); 88 if (isSelf || isCompanion) { 89 this._recompute(pairingId); 90 } 91 }); 92 } 93 94 _recompute(pairingId) { 95 const pairing = this._pairings.get(pairingId); 96 if (!pairing) return; 97 98 const selfQty = this._tickets.get(pairingId).quantity; 99 const companionSelected = pairing.companions.some(c => (this._tickets.get(c.id)?.quantity || 0) > 0); 100 101 let status; 102 if (selfQty > 0) { 103 status = companionSelected ? 'added' : 'error'; 104 } else { 105 status = companionSelected ? 'paired' : 'idle'; 106 } 107 108 pairing.element.setAttribute('status', status); 109 110 if (status === 'error') { 111 const names = pairing.companions.map(c => c.name).join(', '); 112 pairing.element.setErrorMessage(`Ticket must be paired with one of the following tickets first: ${names}`); 113 } 114 } 115} 116 117if (typeof TicketCardLayout !== 'undefined') {
118 TicketCardLayout.PairingCoordinator = TicketCardPairingCoordinator; 119}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.