Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
488 lines
16 KiB
JavaScript
488 lines
16 KiB
JavaScript
/* 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 `<div class="modal-dialog" id="${id}">
|
|
<form class="modal-dialog-alert generic-dialog"
|
|
role="dialog" tabindex="-1">
|
|
<div class="modal-dialog-message-container inner">
|
|
<h3 class="modal-dialog-alert-title"></h3>
|
|
<p>
|
|
<span class="modal-dialog-alert-message"></span>
|
|
</p>
|
|
</div>
|
|
<menu>
|
|
<button type="submit"
|
|
class="modal-dialog-alert-ok confirm affirmative"
|
|
data-l10n-id="ok">Value for Marionette (see bug 1224063)</button>
|
|
</menu>
|
|
</form>
|
|
<form class="modal-dialog-confirm generic-dialog"
|
|
role="dialog" tabindex="-1">
|
|
<div class="modal-dialog-message-container inner">
|
|
<h3 class="modal-dialog-confirm-title"></h3>
|
|
<p>
|
|
<span class="modal-dialog-confirm-message"></span>
|
|
</p>
|
|
</div>
|
|
<menu data-items="2">
|
|
<button type="button" class="modal-dialog-confirm-cancel cancel"
|
|
data-l10n-id="cancel"></button>
|
|
<button type="submit"
|
|
class="modal-dialog-confirm-ok confirm affirmative"
|
|
data-l10n-id="ok"></button>
|
|
</menu>
|
|
</form>
|
|
<form class="modal-dialog-prompt generic-dialog"
|
|
role="dialog" tabindex="-1">
|
|
<div class="modal-dialog-message-container inner">
|
|
<h3 class="modal-dialog-prompt-title"></h3>
|
|
<p>
|
|
<span class="modal-dialog-prompt-message"></span>
|
|
<input class="modal-dialog-prompt-input" />
|
|
</p>
|
|
</div>
|
|
<menu data-items="2">
|
|
<button type="button" class="modal-dialog-prompt-cancel cancel"
|
|
data-l10n-id="cancel"></button>
|
|
<button type="submit"
|
|
class="modal-dialog-prompt-ok confirm affirmative"
|
|
data-l10n-id="ok"></button>
|
|
</menu>
|
|
</form>
|
|
<form class="modal-dialog-select-one generic-dialog"
|
|
role="dialog" tabindex="-1">
|
|
<div class="modal-dialog-message-container inner">
|
|
<h3 class="modal-dialog-select-one-title"></h3>
|
|
<ul class="modal-dialog-select-one-menu"></ul>
|
|
</div>
|
|
<menu>
|
|
<button type="submit"
|
|
class="modal-dialog-select-one-cancel cancel"
|
|
data-l10n-id="cancel"></button>
|
|
</menu>
|
|
</form>
|
|
<form class="modal-dialog-custom-prompt generic-dialog"
|
|
role="dialog" tabindex="-1">
|
|
<div class="modal-dialog-message-container inner">
|
|
<h3 class="modal-dialog-custom-prompt-title"></h3>
|
|
<p class="modal-dialog-custom-prompt-message"></p>
|
|
<gaia-checkbox class="modal-dialog-custom-prompt-checkbox">
|
|
<label></label>
|
|
</gaia-checkbox>
|
|
</div>
|
|
<menu class="modal-dialog-custom-prompt-buttons"></menu>
|
|
</form>
|
|
</div>`;
|
|
};
|
|
|
|
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));
|