/* global BaseUI */ 'use strict'; (function(exports) { var _id = 0; /** * The ModalDialog of the AppWindow. * * Including **alert**, **prompt**, **confirm**, and * **single select** dialogs. * * @class AppModalDialog * @param {AppWindow} app The app window instance * where this dialog should popup. * @extends BaseUI */ var AppModalDialog = function AppModalDialog(app) { this.app = app; this.containerElement = app.element; this.events = []; // One to one mapping. this.instanceID = _id++; this._injected = false; this._visible = false; app.element.addEventListener('mozbrowsershowmodalprompt', this); return this; }; AppModalDialog.prototype = Object.create(BaseUI.prototype); AppModalDialog.prototype.CLASS_NAME = 'AppModalDialog'; AppModalDialog.prototype.ELEMENT_PREFIX = 'modal-dialog-'; AppModalDialog.prototype.customID = function amd_customID() { if (this.app) { return '[' + this.app.origin + ']'; } else { return ''; } }; AppModalDialog.prototype.handleEvent = function amd_handleEvent(evt) { this.app.debug('handling ' + evt.type); evt.preventDefault(); evt.stopPropagation(); this.events.push(evt); this.menuHeight = 0; if (!this._injected) { this.render(); } this.show(); this._injected = true; }; AppModalDialog.prototype.isVisible = function amd_isVisible() { return this._visible; }; AppModalDialog.prototype._fetchElements = function amd__fetchElements() { this.element = document.getElementById(this.CLASS_NAME + this.instanceID); this.elements = {}; var toCamelCase = function toCamelCase(str) { return str.replace(/\-(.)/g, function replacer(str, p1) { return p1.toUpperCase(); }); }; this.elementClasses = ['alert', 'alert-ok', 'alert-message', 'prompt', 'prompt-ok', 'prompt-cancel', 'prompt-input', 'prompt-message', 'confirm', 'confirm-ok', 'confirm-cancel', 'confirm-message', 'select-one', 'select-one-cancel', 'select-one-menu', 'select-one-title', 'alert-title', 'confirm-title', 'prompt-title', 'custom-prompt', 'custom-prompt-message', 'custom-prompt-buttons', 'custom-prompt-checkbox']; // Loop and add element with camel style name to Modal Dialog attribute. this.elementClasses.forEach(function createElementRef(name) { this.elements[toCamelCase(name)] = this.element.querySelector('.' + this.ELEMENT_PREFIX + name); }, this); this.elements.menu = this.element.querySelector('menu'); }; AppModalDialog.prototype._registerEvents = function amd__registerEvents() { var elements = this.elements; for (var id in elements) { var tagName = elements[id].tagName.toLowerCase(); if (tagName == 'button' || tagName == 'ul') { if (elements[id].classList.contains('confirm')) { elements[id].addEventListener('click', this.confirmHandler.bind(this)); } else if (elements[id].classList.contains('cancel')) { elements[id].addEventListener('click', this.cancelHandler.bind(this)); } } } }; AppModalDialog.prototype.view = function amd_view() { var id = this.CLASS_NAME + this.instanceID; return ``; }; AppModalDialog.prototype.processNextEvent = function amd_processNextEvent() { this.events.splice(0, 1); if (this.events.length) { this.show(); } else { this.hide(); } }; AppModalDialog.prototype.kill = function amd_kill() { this._visible = false; this.containerElement.removeChild(this.element); }; // Show relative dialog and set message/input value well AppModalDialog.prototype.show = function amd_show() { if (!this.events.length) { return; } this._visible = true; var evt = this.events[0]; var message = evt.detail.message || ''; var title = this._getTitle(evt.detail.title); var elements = this.elements; var type = evt.detail.promptType || evt.detail.type; switch (type) { case 'alert': elements.alertTitle.textContent = title; elements.alertMessage.textContent = message; elements.alert.classList.add('visible'); if (evt.yesText) { elements.alertOk.removeAttribute('data-l10n-id'); elements.alertOk.textContent = evt.yesText; } else { elements.alertOk.setAttribute('data-l10n-id', 'ok'); } elements.alert.focus(); this.updateMaxHeight(); break; case 'prompt': elements.prompt.classList.add('visible'); elements.promptInput.value = evt.detail.initialValue; elements.promptTitle.textContent = title; elements.promptMessage.textContent = message; if (evt.yesText) { elements.promptOk.removeAttribute('data-l10n-id'); elements.promptOk.textContent = evt.yesText; } else { elements.promptOk.setAttribute('data-l10n-id', 'ok'); } if (evt.noText) { elements.promptCancel.removeAttribute('data-l10n-id'); elements.promptCancel.textContent = evt.noText; } else { elements.promptCancel.setAttribute('data-l10n-id', 'cancel'); } elements.prompt.focus(); break; case 'confirm': elements.confirm.classList.add('visible'); elements.confirmTitle.textContent = title; elements.confirmMessage.textContent = message; if (evt.yesText) { elements.confirmOk.removeAttribute('data-l10n-id'); elements.confirmOk.textContent = evt.yesText; } else { elements.confirmOk.setAttribute('data-l10n-id', 'ok'); } if (evt.noText) { elements.confirmCancel.removeAttribute('data-l10n-id'); elements.confirmCancel.textContent = evt.noText; } else { elements.confirmCancel.setAttribute('data-l10n-id', 'cancel'); } elements.confirm.focus(); break; case 'selectone': this.buildSelectOneDialog(message); elements.selectOne.classList.add('visible'); elements.selectOne.focus(); break; case 'custom-prompt': var customPrompt = evt.detail; elements.customPrompt.classList.add('visible'); elements.customPromptMessage.textContent = customPrompt.message; // Display custom list of buttons elements.customPromptButtons.innerHTML = ''; elements.customPromptButtons.setAttribute('data-items', customPrompt.buttons.length); var domElement = null; for (var i = customPrompt.buttons.length - 1; i >= 0; i--) { var button = customPrompt.buttons[i]; domElement = document.createElement('button'); domElement.dataset.buttonIndex = i; if (button.messageType === 'builtin') { domElement.setAttribute('data-l10n-id', button.message); } else if (button.messageType === 'custom') { // For custom button, we assume that the text is already translated domElement.textContent = button.message; } else { console.error('Unexpected button type : ' + button.messageType); continue; } domElement.addEventListener('click', this.confirmHandler.bind(this)); elements.customPromptButtons.appendChild(domElement); } domElement.classList.add('affirmative'); // Eventualy display a checkbox: var checkbox = elements.customPromptCheckbox; if (customPrompt.showCheckbox) { if (customPrompt.checkboxCheckedByDefault) { checkbox.checked = true; } else { checkbox.checked = false; } // We assume that checkbox custom message is already translated checkbox.querySelector('label').textContent = customPrompt.checkboxMessage; } else { checkbox.style.display = 'none'; } elements.customPrompt.focus(); break; } this.app.browser.element.setAttribute('aria-hidden', true); this.element.classList.add('visible'); }; AppModalDialog.prototype.hide = function amd_hide() { this._visible = false; this.element.blur(); this.app.browser.element.removeAttribute('aria-hidden'); this.element.classList.remove('visible'); if (this.app) { this.app.focus(); } if (!this.events.length) { return; } var evt = this.events[0]; var type = evt.detail.promptType || evt.detail.type; if (type === 'prompt') { this.elements.promptInput.blur(); } this.elements[type].classList.remove('visible'); }; // When user clicks OK button on alert/confirm/prompt AppModalDialog.prototype.confirmHandler = function amd_confirmHandler(clickEvt) { if (!this.events.length) { return; } clickEvt.preventDefault(); var elements = this.elements; var evt = this.events[0]; var type = evt.detail.promptType || evt.detail.type; switch (type) { case 'alert': elements.alert.classList.remove('visible'); break; case 'prompt': evt.detail.returnValue = elements.promptInput.value; elements.prompt.classList.remove('visible'); break; case 'confirm': evt.detail.returnValue = true; elements.confirm.classList.remove('visible'); break; case 'custom-prompt': var returnValue = { selectedButton: clickEvt.target.dataset.buttonIndex }; if (evt.detail.showCheckbox) { returnValue.checked = elements.customPromptCheckbox.checked; } evt.detail.returnValue = returnValue; elements.customPrompt.classList.remove('visible'); break; } if (evt.detail.unblock) { evt.detail.unblock(); } this.processNextEvent(); }; // When user clicks cancel button on confirm/prompt or // when the user try to escape the dialog with the escape key AppModalDialog.prototype.cancelHandler = function amd_cancelHandler(clickEvt) { if (!this.events.length) { return; } clickEvt.preventDefault(); var evt = this.events[0]; var elements = this.elements; var type = evt.detail.promptType || evt.detail.type; switch (type) { case 'alert': elements.alert.classList.remove('visible'); break; case 'prompt': /* return null when click cancel */ evt.detail.returnValue = null; elements.prompt.classList.remove('visible'); break; case 'confirm': /* return false when click cancel */ evt.detail.returnValue = false; elements.confirm.classList.remove('visible'); break; case 'selectone': /* return null when click cancel */ evt.detail.returnValue = null; elements.selectOne.classList.remove('visible'); break; } if (evt.detail.unblock) { evt.detail.unblock(); } this.processNextEvent(); }; // When user selects an option on selectone dialog AppModalDialog.prototype.selectOneHandler = function amd_selectOneHandler(target) { if (!this.events.length) { return; } var elements = this.elements; var evt = this.events[0]; evt.detail.returnValue = target.id; elements.selectOne.classList.remove('visible'); if (evt.detail.unblock) { evt.detail.unblock(); } this.processNextEvent(); }; AppModalDialog.prototype.updateMaxHeight = function() { // Setting maxHeight for being able to scroll long // texts: formHeight - menuHeight - titleHeight - margin // We should fix this in a common way for all the dialogs // in the building blocks layer: Bug 1096902 this.menuHeight = this.menuHeight || this.elements.menu.offsetHeight; var messageHeight = this.element.offsetHeight - this.menuHeight; messageHeight -= this.elements.alertTitle.offsetHeight; var margin = window.getComputedStyle(this.elements.alertTitle).marginBottom; var messageContainer = this.elements.alert.querySelector('.inner p'); var calc = 'calc(' + messageHeight + 'px - ' + margin + ')'; messageContainer.style.maxHeight = calc; }; AppModalDialog.prototype._getTitle = function amd__getTitle(title) { // // XXX Bug 982006, subsystems like uriloader still report errors with // titles which are important to the user for context in diagnosing // issues. // // However, we will ignore all titles containing a URL using the app // protocol. These types of titles simply indicate that the active // application is prompting and are more confusing to the user than // useful. Instead we will return the application name if there is one // or an empty string. // if (!title || title.includes('app://')) { return this.app.name || ''; } return title; }; exports.AppModalDialog = AppModalDialog; }(window));