Passed
Push — main ( 9be5ed...9944b1 )
by Jochen
05:46
created

byceps/static/behavior/seating.js (2 issues)

1
/**
2
 * Make tooltips appear on hover over seats.
3
 */
4
function init_seat_tooltips() {
5
  document.querySelectorAll('.seat-with-tooltip')
6
    .forEach(seatContainer => {
7
      seatContainer.addEventListener('mouseover', () => {
8
        const dataset = seatContainer.dataset;
9
10
        let tooltipHTML = '<div class="seat-label">' + dataset.label + '</div>';
11
12
        const ticketId = dataset.ticketId;
13
        if (ticketId !== undefined) {
14
          if (seatContainer.querySelector('.seat').classList.contains('seat--managed')) {
15
            const ticketCode = document
16
              .getElementById('ticket-selection')
17
              .querySelector('.ticket[data-id="' + ticketId + '"]')
18
              .dataset.code;
19
20
            tooltipHTML += '<div class="seat-ticket-code"><span class="dimmed">Ticket:</span> <strong>' + ticketCode + '</strong></div>';
21
          }
22
23
          const occupierName = dataset.occupierName;
24
          if (occupierName !== undefined) {
25
            tooltipHTML += '<div class="seat-occupier">'
26
                         + '<div class="seat-occupier-avatar"><div class="avatar size-48"><img src="' + dataset.occupierAvatar + '"></div></div>'
27
                         + '<div class="seat-occupier-name"><span class="dimmed">reserviert von</span><br><strong>' + escape_html(occupierName) + '</strong></div>'
28
                         + '</div>';
29
          }
30
        }
31
32
        const tooltipNode = document.createElement('div');
33
        tooltipNode.classList.add('seat-tooltip');
34
        tooltipNode.innerHTML = tooltipHTML;
35
        seatContainer.appendChild(tooltipNode);
36
      });
37
38
      seatContainer.addEventListener('mouseout', () => {
39
        const tooltip = seatContainer.querySelector('.seat-tooltip');
40
        // Tooltip element won't exist at this point if the mouse
41
        // cursor is above a seat when the page is reloaded, so the
42
        // 'mouseout' event will fire upon mouse move even though no
43
        // 'mouseover' has occurred, thus no tooltip has been
44
        // generated that could be removed.
45
        if (tooltip !== null) {
46
          tooltip.remove();
47
        }
48
      });
49
    });
50
}
51
52
53
/**
54
 * Initialize the seat management system.
55
 */
56
function init_seat_management(selected_ticket_id) {
57
  init_ticket_selector();
58
59
  const tickets = document.querySelectorAll('.ticket');
60
  if (tickets.length > 0) {
61
    // Pre-select ticket.
62
    let ticket_to_preselect;
63
    if (selected_ticket_id !== null) {
64
      ticket_to_preselect = find_ticket_by_id(selected_ticket_id);
65
    } else {
66
      // Select first ticket by default.
67
      ticket_to_preselect = tickets[0];
68
    }
69
    select_ticket(ticket_to_preselect);
70
71
    mark_seats_as_occupiable();
72
    init_occupiable_seats();
73
    mark_managed_seats();
74
    mark_seat_for_selected_managed_ticket();
75
    wire_seat_release_button();
76
  }
77
}
78
79
80
/**
81
 * Make ticket selector work.
82
 */
83
function init_ticket_selector() {
84
  _make_ticket_selector_open_on_click();
85
  _select_ticket_on_click();
86
}
87
88
89
/**
90
 * Make ticket selector open on click.
91
 */
92
function _make_ticket_selector_open_on_click() {
93
  const ticket_selector = document.querySelector('.ticket-selector');
94
  if (ticket_selector !== null) {
95
    ticket_selector
96
      .addEventListener('click', () => ticket_selector.classList.toggle('ticket-selector--open'));
97
  }
98
}
99
100
101
/**
102
 * Select ticket on click on ticket in ticket selector.
103
 */
104
function _select_ticket_on_click() {
105
  document.querySelectorAll('.ticket')
106
    .forEach(ticket => ticket.addEventListener('click', () => select_ticket(ticket)));
107
}
108
109
110
/**
111
 * Return the ticket node in the selector with that ticket ID,
112
 * or `null` if not found.
113
 */
114
function find_ticket_by_id(ticket_id) {
115
  const ticket = document
116
    .getElementById('ticket-selection')
117
    .querySelector('.ticket[data-id="' + ticket_id + '"]');
118
119
  if (ticket === null) {
120
    return null;
121
  }
122
123
  return ticket;
124
}
125
126
127
/**
128
 * Set the ticket as the selected one in the ticket selector (both
129
 * visually and regarding its internal state).
130
 */
131
function select_ticket(ticket) {
132
  const ticket_id = ticket.dataset.id;
133
  const ticket_code = ticket.dataset.code;
134
135
  const ticket_selection = document.getElementById('ticket-selection');
136
  ticket_selection.dataset.selectedId = ticket_id;
137
  ticket_selection.dataset.selectedCode = ticket_code;
138
  ticket_selection.querySelectorAll('.ticket--current')
139
    .forEach(ticket => ticket.classList.remove('ticket--current'));
140
141
  ticket.classList.add('ticket--current');
142
143
  set_current_managed_seat(ticket_id);
144
145
  document.getElementById('release-seat-trigger').disabled = !is_ticket_occupying_seat(ticket);
146
}
147
148
149
function set_current_managed_seat(ticket_id) {
150
  document.querySelectorAll('.area .seat--managed-current')
151
    .forEach(seat => seat.classList.remove('seat--managed-current'));
152
153
  document.querySelectorAll('.seat-with-tooltip[data-ticket-id="' + ticket_id + '"] .seat--managed')
154
    .forEach(seat => seat.classList.add('seat--managed-current'));
155
}
156
157
158
function wire_seat_release_button() {
159
  const release_seat_trigger = document.getElementById('release-seat-trigger');
160
  if (release_seat_trigger !== null) {
161
    release_seat_trigger.addEventListener('click', () => {
162
      const seat_label = get_selected_seat_label();
163
      const confirmation_label = seat_label + ' (belegt durch Ticket ' + get_selected_ticket_code() + ') freigeben?';
164
      if (confirm(confirmation_label)) {
165
        const ticket_id = get_selected_ticket_id();
166
167
        const request_url = '/seating/ticket/' + ticket_id + '/seat';
168
169
        fetch(request_url, {method: 'DELETE'})
170
          .then(response => reload_with_selected_ticket(ticket_id));
1 ignored issue
show
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
171
      };
172
      return false;
173
    });
174
  }
175
}
176
177
178
function get_selected_ticket_id() {
179
  return document.getElementById('ticket-selection').dataset.selectedId;
180
}
181
182
183
function get_selected_ticket_code() {
184
  return document.getElementById('ticket-selection').dataset.selectedCode;
185
}
186
187
188
function is_ticket_occupying_seat(ticket) {
189
  return ticket.dataset.seatLabel !== undefined;
190
}
191
192
193
function get_selected_seat_label() {
194
  const ticket_selection = document.getElementById('ticket-selection');
195
  const ticket_code = ticket_selection.dataset.selectedCode;
196
  const ticket = ticket_selection.querySelector('.ticket[data-code="' + ticket_code + '"]');
197
  return ticket.dataset.seatLabel;
198
}
199
200
201
function get_managed_ticket_ids() {
202
  const managed_ticket_ids = new Set();
203
204
  document.getElementById('ticket-selection')
205
    .querySelectorAll('.ticket')
206
    .forEach(ticket => managed_ticket_ids.add(ticket.dataset.id));
207
208
  return managed_ticket_ids;
209
}
210
211
212
function get_seats_with_ticket_code(ticket_id) {
213
  return document.querySelectorAll('.seat-with-tooltip[data-ticket-id="' + ticket_id + '"] .seat');
214
}
215
216
217
function mark_managed_seats() {
218
  const managed_ticket_ids = get_managed_ticket_ids();
219
220
  for (const ticket_id of managed_ticket_ids.values()) {
221
    get_seats_with_ticket_code(ticket_id)
222
      .forEach(seat => seat.classList.add('seat--managed'));
223
  }
224
}
225
226
227
function mark_seat_for_selected_managed_ticket() {
228
  const ticket_id = get_selected_ticket_id();
229
  get_seats_with_ticket_code(ticket_id)
230
    .forEach(seat => seat.classList.add('seat--managed-current'));
231
}
232
233
234
function mark_seats_as_occupiable() {
235
  document.querySelectorAll('.seat')
236
    .forEach(seat => {
237
      const ticket_id = seat.parentNode.dataset.ticketId;
238
      if (ticket_id === undefined) {
239
        seat.classList.add('seat--occupiable');
240
      }
241
    }
242
  );
243
}
244
245
246
function init_occupiable_seats() {
247
  document.querySelectorAll('.seat--occupiable')
248
    .forEach(seat => {
249
      seat.addEventListener('click', () => {
250
        const seat_label = seat.parentNode.dataset.label;
251
        const confirmation_label = seat_label + ' mit Ticket ' + get_selected_ticket_code() + ' reservieren?';
252
        if (confirm(confirmation_label)) {
253
          const seat_id = seat.parentNode.dataset.seatId;
254
          const ticket_id = get_selected_ticket_id();
255
256
          const request_url = '/seating/ticket/' + ticket_id + '/seat/' + seat_id;
257
258
          fetch(request_url, {method: 'POST'})
259
            .then(response => reload_with_selected_ticket(ticket_id));
1 ignored issue
show
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
260
        };
261
        return false;
262
      });
263
    });
264
}
265
266
267
function reload_with_selected_ticket(ticket_id) {
268
  location.href = location.href.split('?')[0] + '?ticket_id=' + ticket_id;
269
}
270
271
272
/**
273
 * Use browser's built-in functionality to quickly and safely escape
274
 * HTML in the given string.
275
 */
276
function escape_html(str) {
277
  const elem = document.createElement('span');
278
  elem.appendChild(document.createTextNode(str));
279
  return elem.innerHTML;
280
}
281