1/*global H5P*/ 2H5P.ConfirmationDialog = (function (EventDispatcher) { 3 "use strict"; 4 5 /** 6 * Create a confirmation dialog 7 * 8 * @param [options] Options for confirmation dialog 9 * @param [options.instance] Instance that uses confirmation dialog 10 * @param [options.headerText] Header text 11 * @param [options.dialogText] Dialog text 12 * @param [options.cancelText] Cancel dialog button text 13 * @param [options.confirmText] Confirm dialog button text 14 * @param [options.hideCancel] Hide cancel button 15 * @param [options.hideExit] Hide exit button 16 * @param [options.skipRestoreFocus] Skip restoring focus when hiding the dialog 17 * @param [options.classes] Extra classes for popup 18 * @constructor 19 */ 20 function ConfirmationDialog(options) { 21 EventDispatcher.call(this); 22 var self = this; 23 24 // Make sure confirmation dialogs have unique id 25 H5P.ConfirmationDialog.uniqueId += 1; 26 var uniqueId = H5P.ConfirmationDialog.uniqueId; 27 28 // Default options 29 options = options || {}; 30 options.headerText = options.headerText || H5P.t('confirmDialogHeader'); 31 options.dialogText = options.dialogText || H5P.t('confirmDialogBody'); 32 options.cancelText = options.cancelText || H5P.t('cancelLabel'); 33 options.confirmText = options.confirmText || H5P.t('confirmLabel'); 34 35 /** 36 * Handle confirming event 37 * @param {Event} e 38 */ 39 function dialogConfirmed(e) { 40 self.hide(); 41 self.trigger('confirmed'); 42 e.preventDefault(); 43 } 44 45 /** 46 * Handle dialog canceled 47 * @param {Event} e 48 */ 49 function dialogCanceled(e) { 50 self.hide(); 51 self.trigger('canceled'); 52 e.preventDefault(); 53 } 54 55 /** 56 * Flow focus to element 57 * @param {HTMLElement} element Next element to be focused 58 * @param {Event} e Original tab event 59 */ 60 function flowTo(element, e) { 61 element.focus(); 62 e.preventDefault(); 63 } 64 65 // Offset of exit button 66 var exitButtonOffset = 2 * 16; 67 var shadowOffset = 8; 68 69 // Determine if we are too large for our container and must resize 70 var resizeIFrame = false; 71 72 // Create background 73 var popupBackground = document.createElement('div'); 74 popupBackground.classList 75 .add('h5p-confirmation-dialog-background', 'hidden', 'hiding'); 76 77 // Create outer popup 78 var popup = document.createElement('div'); 79 popup.classList.add('h5p-confirmation-dialog-popup', 'hidden'); 80 if (options.classes) { 81 options.classes.forEach(function (popupClass) { 82 popup.classList.add(popupClass); 83 }); 84 } 85 86 popup.setAttribute('role', 'dialog'); 87 popup.setAttribute('aria-labelledby', 'h5p-confirmation-dialog-dialog-text-' + uniqueId); 88 popupBackground.appendChild(popup); 89 popup.addEventListener('keydown', function (e) { 90 if (e.which === 27) {// Esc key 91 // Exit dialog 92 dialogCanceled(e); 93 } 94 }); 95 96 // Popup header 97 var header = document.createElement('div'); 98 header.classList.add('h5p-confirmation-dialog-header'); 99 popup.appendChild(header); 100 101 // Header text 102 var headerText = document.createElement('div'); 103 headerText.classList.add('h5p-confirmation-dialog-header-text'); 104 headerText.innerHTML = options.headerText; 105 header.appendChild(headerText); 106 107 // Popup body 108 var body = document.createElement('div'); 109 body.classList.add('h5p-confirmation-dialog-body'); 110 popup.appendChild(body); 111 112 // Popup text 113 var text = document.createElement('div'); 114 text.classList.add('h5p-confirmation-dialog-text'); 115 text.innerHTML = options.dialogText; 116 text.id = 'h5p-confirmation-dialog-dialog-text-' + uniqueId; 117 body.appendChild(text); 118 119 // Popup buttons 120 var buttons = document.createElement('div'); 121 buttons.classList.add('h5p-confirmation-dialog-buttons'); 122 body.appendChild(buttons); 123 124 // Cancel button 125 var cancelButton = document.createElement('button'); 126 cancelButton.classList.add('h5p-core-cancel-button'); 127 cancelButton.textContent = options.cancelText; 128 129 // Confirm button 130 var confirmButton = document.createElement('button'); 131 confirmButton.classList.add('h5p-core-button'); 132 confirmButton.classList.add('h5p-confirmation-dialog-confirm-button'); 133 confirmButton.textContent = options.confirmText; 134 135 // Exit button 136 var exitButton = document.createElement('button'); 137 exitButton.classList.add('h5p-confirmation-dialog-exit'); 138 exitButton.setAttribute('aria-hidden', 'true'); 139 exitButton.tabIndex = -1; 140 exitButton.title = options.cancelText; 141 142 // Cancel handler 143 cancelButton.addEventListener('click', dialogCanceled); 144 cancelButton.addEventListener('keydown', function (e) { 145 if (e.which === 32) { // Space 146 dialogCanceled(e); 147 } 148 else if (e.which === 9 && e.shiftKey) { // Shift-tab 149 flowTo(confirmButton, e); 150 } 151 }); 152 153 if (!options.hideCancel) { 154 buttons.appendChild(cancelButton); 155 } 156 else { 157 // Center buttons 158 buttons.classList.add('center'); 159 } 160 161 // Confirm handler 162 confirmButton.addEventListener('click', dialogConfirmed); 163 confirmButton.addEventListener('keydown', function (e) { 164 if (e.which === 32) { // Space 165 dialogConfirmed(e); 166 } 167 else if (e.which === 9 && !e.shiftKey) { // Tab 168 const nextButton = !options.hideCancel ? cancelButton : confirmButton; 169 flowTo(nextButton, e); 170 } 171 }); 172 buttons.appendChild(confirmButton); 173 174 // Exit handler 175 exitButton.addEventListener('click', dialogCanceled); 176 exitButton.addEventListener('keydown', function (e) { 177 if (e.which === 32) { // Space 178 dialogCanceled(e); 179 } 180 }); 181 if (!options.hideExit) { 182 popup.appendChild(exitButton); 183 } 184 185 // Wrapper element 186 var wrapperElement; 187 188 // Focus capturing 189 var focusPredator; 190 191 // Maintains hidden state of elements 192 var wrapperSiblingsHidden = []; 193 var popupSiblingsHidden = []; 194 195 // Element with focus before dialog 196 var previouslyFocused; 197 198 /** 199 * Set parent of confirmation dialog 200 * @param {HTMLElement} wrapper 201 * @returns {H5P.ConfirmationDialog} 202 */ 203 this.appendTo = function (wrapper) { 204 wrapperElement = wrapper; 205 return this; 206 }; 207 208 /** 209 * Capture the focus element, send it to confirmation button 210 * @param {Event} e Original focus event 211 */ 212 var captureFocus = function (e) {
213 if (!popupBackground.contains(e.target)) { 214 e.preventDefault(); 215 confirmButton.focus(); 216 } 217 }; 218 219 /** 220 * Hide siblings of element from assistive technology 221 * 222 * @param {HTMLElement} element 223 * @returns {Array} The previous hidden state of all siblings 224 */ 225 var hideSiblings = function (element) { 226 var hiddenSiblings = []; 227 var siblings = element.parentNode.children; 228 var i; 229 for (i = 0; i < siblings.length; i += 1) { 230 // Preserve hidden state 231 hiddenSiblings[i] = siblings[i].getAttribute('aria-hidden') ? 232 true : false; 233 234 if (siblings[i] !== element) { 235 siblings[i].setAttribute('aria-hidden', true); 236 } 237 } 238 return hiddenSiblings; 239 }; 240 241 /** 242 * Restores assistive technology state of element's siblings 243 * 244 * @param {HTMLElement} element 245 * @param {Array} hiddenSiblings Hidden state of all siblings 246 */ 247 var restoreSiblings = function (element, hiddenSiblings) { 248 var siblings = element.parentNode.children; 249 var i; 250 for (i = 0; i < siblings.length; i += 1) { 251 if (siblings[i] !== element && !hiddenSiblings[i]) { 252 siblings[i].removeAttribute('aria-hidden'); 253 } 254 } 255 }; 256 257 /** 258 * Start capturing focus of parent and send it to dialog 259 */ 260 var startCapturingFocus = function () { 261 focusPredator = wrapperElement.parentNode || wrapperElement; 262 focusPredator.addEventListener('focus', captureFocus, true); 263 }; 264 265 /** 266 * Clean up event listener for capturing focus 267 */ 268 var stopCapturingFocus = function () { 269 focusPredator.removeAttribute('aria-hidden'); 270 focusPredator.removeEventListener('focus', captureFocus, true); 271 }; 272 273 /** 274 * Hide siblings in underlay from assistive technologies 275 */ 276 var disableUnderlay = function () { 277 wrapperSiblingsHidden = hideSiblings(wrapperElement); 278 popupSiblingsHidden = hideSiblings(popupBackground); 279 }; 280 281 /** 282 * Restore state of underlay for assistive technologies 283 */ 284 var restoreUnderlay = function () { 285 restoreSiblings(wrapperElement, wrapperSiblingsHidden); 286 restoreSiblings(popupBackground, popupSiblingsHidden); 287 }; 288 289 /** 290 * Fit popup to container. Makes sure it doesn't overflow. 291 * @params {number} [offsetTop] Offset of popup 292 */ 293 var fitToContainer = function (offsetTop) { 294 var popupOffsetTop = parseInt(popup.style.top, 10); 295 if (offsetTop !== undefined) { 296 popupOffsetTop = offsetTop; 297 } 298 299 if (!popupOffsetTop) { 300 popupOffsetTop = 0; 301 } 302 303 // Overflows height 304 if (popupOffsetTop + popup.offsetHeight > wrapperElement.offsetHeight) { 305 popupOffsetTop = wrapperElement.offsetHeight - popup.offsetHeight - shadowOffset; 306 } 307 308 if (popupOffsetTop - exitButtonOffset <= 0) { 309 popupOffsetTop = exitButtonOffset + shadowOffset; 310 311 // We are too big and must resize 312 resizeIFrame = true; 313 } 314 popup.style.top = popupOffsetTop + 'px'; 315 }; 316 317 /** 318 * Show confirmation dialog 319 * @params {number} offsetTop Offset top 320 * @returns {H5P.ConfirmationDialog} 321 */ 322 this.show = function (offsetTop) { 323 // Capture focused item 324 previouslyFocused = document.activeElement; 325 wrapperElement.appendChild(popupBackground); 326 startCapturingFocus(); 327 disableUnderlay(); 328 popupBackground.classList.remove('hidden'); 329 fitToContainer(offsetTop); 330 setTimeout(function () { 331 popup.classList.remove('hidden'); 332 popupBackground.classList.remove('hiding'); 333 334 setTimeout(function () { 335 // Focus confirm button 336 confirmButton.focus(); 337 338 // Resize iFrame if necessary 339 if (resizeIFrame && options.instance) { 340 var minHeight = parseInt(popup.offsetHeight, 10) + 341 exitButtonOffset + (2 * shadowOffset); 342 self.setViewPortMinimumHeight(minHeight); 343 options.instance.trigger('resize'); 344 resizeIFrame = false;
345 } 346 }, 100); 347 }, 0); 348 349 return this; 350 }; 351 352 /** 353 * Hide confirmation dialog 354 * @returns {H5P.ConfirmationDialog} 355 */ 356 this.hide = function () { 357 popupBackground.classList.add('hiding'); 358 popup.classList.add('hidden'); 359 360 // Restore focus 361 stopCapturingFocus(); 362 if (!options.skipRestoreFocus) { 363 previouslyFocused.focus(); 364 } 365 restoreUnderlay(); 366 setTimeout(function () { 367 popupBackground.classList.add('hidden'); 368 wrapperElement.removeChild(popupBackground); 369 self.setViewPortMinimumHeight(null); 370 }, 100); 371 372 return this; 373 }; 374 375 /** 376 * Retrieve element 377 * 378 * @return {HTMLElement} 379 */ 380 this.getElement = function () { 381 return popup; 382 }; 383 384 /** 385 * Get previously focused element 386 * @return {HTMLElement} 387 */ 388 this.getPreviouslyFocused = function () { 389 return previouslyFocused; 390 }; 391 392 /** 393 * Sets the minimum height of the view port 394 * 395 * @param {number|null} minHeight 396 */ 397 this.setViewPortMinimumHeight = function (minHeight) { 398 var container = document.querySelector('.h5p-container') || document.body; 399 container.style.minHeight = (typeof minHeight === 'number') ? (minHeight + 'px') : minHeight; 400 }; 401 } 402 403 ConfirmationDialog.prototype = Object.create(EventDispatcher.prototype); 404 ConfirmationDialog.prototype.constructor = ConfirmationDialog; 405 406 return ConfirmationDialog; 407 408}(H5P.EventDispatcher)); 409 410H5P.ConfirmationDialog.uniqueId = -1;
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.