Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
337 lines
10 KiB
JavaScript
Executable file
337 lines
10 KiB
JavaScript
Executable file
/* global IACHandler, SimpleKeyNavigation, KeyEvent, focusManager */
|
|
|
|
(function(exports) {
|
|
'use strict';
|
|
|
|
function $(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
var TYPE = {
|
|
'NORMAL': 'notification',
|
|
'ALERT': 'alert-notification'
|
|
};
|
|
|
|
// =============== IAC Connector ====================================
|
|
|
|
/**
|
|
* IACConnector bridges notifications from IAC to InteractiveNotifications.
|
|
* It only handles bridge staffes. All queuing features and priority features
|
|
* are handled by InteractiveNotifications.
|
|
**/
|
|
function IACConnector() {
|
|
}
|
|
|
|
var iacProto = IACConnector.prototype;
|
|
|
|
iacProto.start = function iac_start(notifier) {
|
|
window.addEventListener('iac-interactivenotifications', this);
|
|
this.notifier = notifier;
|
|
};
|
|
|
|
iacProto.stop = function iac_start() {
|
|
window.removeEventListener('iac-interactivenotifications', this);
|
|
};
|
|
|
|
iacProto.handleEvent = function iac_handleEvent(evt) {
|
|
var port = IACHandler.getPort('interactivenotifications');
|
|
var detail = evt.detail;
|
|
detail.message.onClosed = (function(button) {
|
|
port.postMessage({
|
|
'action': 'notification-closed',
|
|
'id': detail.id,
|
|
'type': detail.type,
|
|
'message': detail.message,
|
|
'button': button
|
|
});
|
|
}).bind(this);
|
|
|
|
this.notifier.showNotification(detail.type, detail.message);
|
|
};
|
|
|
|
// =============== Interacive Notifications ==========================
|
|
|
|
function InteractiveNotifications() {
|
|
this._activeMessage = null;
|
|
this._activeType = null;
|
|
this._activeTimeout = 0;
|
|
this._pendingMessages = {};
|
|
this._pendingMessages[TYPE.NORMAL] = [];
|
|
this._pendingMessages[TYPE.ALERT] = [];
|
|
|
|
this._banner = $('notification-container');
|
|
this._banner.setAttribute('aria-hidden', 'true');
|
|
this._banner.tabIndex = -1;
|
|
focusManager.addUI(this);
|
|
}
|
|
|
|
InteractiveNotifications.TYPE = Object.freeze(TYPE);
|
|
|
|
var proto = InteractiveNotifications.prototype;
|
|
|
|
proto.start = function in_start() {
|
|
this._iacConnector = new IACConnector();
|
|
this._iacConnector.start(this);
|
|
// This handles left and right. We start this module only when we really
|
|
// need it.
|
|
this._keyNavigator = new SimpleKeyNavigation();
|
|
|
|
this.initEvents();
|
|
};
|
|
|
|
proto.stop = function in_stop() {
|
|
this._iacConnector.stop();
|
|
};
|
|
|
|
proto.getAutoHideTimeout = function in_getAutoHideTimeout(type, hasButtons) {
|
|
if (TYPE.NORMAL === type) {
|
|
return hasButtons ? 8000 : 5000;
|
|
} else {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
proto.initEvents = function in_initEvents() {
|
|
var self = this;
|
|
['notification-button-0', 'notification-button-1'].forEach(function(b) {
|
|
$(b).addEventListener('click', self);
|
|
});
|
|
|
|
// We don't need mozbrowserkeyXXX here because we change the focus back to
|
|
// system app while a notification is shown. And there is no need to handle
|
|
// back key in other cases.
|
|
window.addEventListener('keyup', this);
|
|
|
|
this._banner.addEventListener('hidden', this);
|
|
this._banner.on('opened', this._focusNotification.bind(this));
|
|
};
|
|
|
|
proto.handleEvent = function in_handleEvent(e) {
|
|
switch(e.type) {
|
|
case 'click':
|
|
switch(e.target.id) {
|
|
case 'notification-button-0':
|
|
case 'notification-button-1':
|
|
this.hideNotification(this._activeType, this._activeMessage,
|
|
e.target.dataset.buttonId);
|
|
break;
|
|
}
|
|
break;
|
|
case 'keyup':
|
|
this._handleKeyEvent(e);
|
|
break;
|
|
case 'hidden':
|
|
this.onHide();
|
|
this._banner.classList.add('hidden');
|
|
this._showPendings();
|
|
break;
|
|
}
|
|
};
|
|
|
|
proto._handleKeyEvent = function in_handleKeyEvent(e) {
|
|
switch(e.keyCode) {
|
|
case KeyEvent.DOM_VK_BACK_SPACE:
|
|
if (this._activeMessage) {
|
|
this.hideNotification(this._activeType, this._activeMessage);
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
proto._updateNotificationUI = function in_updateNotificationUI(type, msg) {
|
|
var banner = this._banner;
|
|
var title = $('notification-title');
|
|
var text = $('notification-body');
|
|
var buttonGroup = $('notification-button-group');
|
|
var buttonImg;
|
|
var buttons = [$('notification-button-0'), $('notification-button-1')];
|
|
|
|
banner.dataset.type = type;
|
|
// XXX: We don't have default icon for notification. Change to correct one
|
|
// once we have it.
|
|
banner.style.backgroundImage = msg.icon ? 'url("' + msg.icon + '")':
|
|
'url("/style/icons/system_84.png")';
|
|
title.textContent = msg.title ? msg.title : '';
|
|
|
|
if (msg.title && msg.title.id) {
|
|
navigator.mozL10n.setAttributes(
|
|
title,
|
|
msg.title.id,
|
|
msg.title.args
|
|
);
|
|
} else {
|
|
title.removeAttribute('data-l10n-id');
|
|
title.removeAttribute('data-l10n-args');
|
|
title.textContent = msg.title || '';
|
|
}
|
|
|
|
if (msg.text && msg.text.id) {
|
|
navigator.mozL10n.setAttributes(
|
|
text,
|
|
msg.text.id,
|
|
msg.text.args
|
|
);
|
|
} else {
|
|
text.removeAttribute('data-l10n-id');
|
|
text.removeAttribute('data-l10n-args');
|
|
text.textContent = msg.text || '';
|
|
}
|
|
|
|
banner.classList[msg.title ? 'add' : 'remove']('has-title');
|
|
|
|
if (msg.buttons && msg.buttons.length) {
|
|
buttonGroup.classList.remove('hidden');
|
|
// We only have two buttons at most.
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
if (msg.buttons[i]) {
|
|
buttons[i].dataset.buttonId = msg.buttons[i].id;
|
|
buttons[i].innerHTML = '';
|
|
if (msg.buttons[i].icon) {
|
|
buttonImg = document.createElement('img');
|
|
buttonImg.src = msg.buttons[i].icon;
|
|
buttons[i].appendChild(buttonImg);
|
|
}
|
|
if (msg.buttons[i].label) {
|
|
buttons[i].textContent = msg.buttons[i].label;
|
|
}
|
|
buttons[i].classList.remove('hidden');
|
|
} else {
|
|
buttons[i].classList.add('hidden');
|
|
}
|
|
}
|
|
} else {
|
|
buttonGroup.classList.add('hidden');
|
|
}
|
|
|
|
banner.classList.remove('hidden');
|
|
banner.flyOpen();
|
|
};
|
|
|
|
proto._focusNotification = function in_focusNotification() {
|
|
this._banner.removeAttribute('aria-hidden');
|
|
if (this._activeMessage.buttons && this._activeMessage.buttons.length > 0) {
|
|
// KeyNavigator will auto-focus the first item at the start. It calls
|
|
// focus() for us when the focus switches to another button.
|
|
this._keyNavigator.start([$('notification-button-0'),
|
|
$('notification-button-1')],
|
|
SimpleKeyNavigation.DIRECTION.HORIZONTAL,
|
|
{target: this._banner});
|
|
}
|
|
focusManager.focus();
|
|
};
|
|
|
|
proto.focus = function in_focus() {
|
|
if (this.isFocusable()) {
|
|
document.activeElement.blur();
|
|
this._activeMessage.buttons ?
|
|
this._keyNavigator.focus() : this._banner.focus();
|
|
}
|
|
};
|
|
|
|
proto.isFocusable = function in_isFocusable() {
|
|
return !!this._activeMessage &&
|
|
this._activeMessage.buttons && this._activeMessage.buttons.length;
|
|
};
|
|
|
|
proto.getElement = function in_getElement() {
|
|
if (this.isFocusable()) {
|
|
return this._banner;
|
|
}
|
|
};
|
|
|
|
proto.showNotification = function in_showNotification(type, msg) {
|
|
if (type === TYPE.ALERT &&
|
|
this._activeType === TYPE.ALERT) {
|
|
// already have one, just pending it.
|
|
this._pendingMessages[TYPE.ALERT].push(msg);
|
|
} else if (type === TYPE.NORMAL &&
|
|
(this._activeType === TYPE.NORMAL ||
|
|
this._activeType === TYPE.ALERT)) {
|
|
// already have one, just pending it.
|
|
this._pendingMessages[TYPE.NORMAL].push(msg);
|
|
} else if (type === TYPE.ALERT && this._activeMessage) {
|
|
// type === alert and _activeType is null or normal.
|
|
// We show alert anyway and hide the normal one if one is shown.
|
|
window.clearTimeout(this._activeTimeout);
|
|
this._activeTimeout = 0;
|
|
this._pendingMessages[TYPE.ALERT].push(msg);
|
|
this.hideNotification(this._activeType, this._activeMessage);
|
|
} else {
|
|
var timeout = msg.timeout;
|
|
if (!timeout) {
|
|
timeout = this.getAutoHideTimeout(type, msg.buttons &&
|
|
msg.buttons.length > 0);
|
|
}
|
|
this._activeType = type;
|
|
this._activeMessage = msg;
|
|
this._updateNotificationUI(type, msg);
|
|
if (timeout > 0) {
|
|
this._activeTimeout = window.setTimeout((function autoHide() {
|
|
this._activeTimeout = 0;
|
|
this.hideNotification(type, msg);
|
|
}).bind(this), timeout);
|
|
}
|
|
}
|
|
};
|
|
|
|
proto._showPendings = function in_showPendings() {
|
|
var type, msg;
|
|
if (this._pendingMessages[TYPE.ALERT].length) {
|
|
type = TYPE.ALERT;
|
|
msg = this._pendingMessages[type].shift();
|
|
} else if (this._pendingMessages[TYPE.NORMAL].length) {
|
|
type = TYPE.NORMAL;
|
|
msg = this._pendingMessages[type].shift();
|
|
}
|
|
if (type && msg) {
|
|
this.showNotification(type, msg);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
proto.hasPendings = function in_hasPendings() {
|
|
return this._pendingMessages[TYPE.ALERT].length ||
|
|
this._pendingMessages[TYPE.NORMAL].length;
|
|
};
|
|
|
|
proto.onHide = function in_onHide() {
|
|
if (this._activeMessage.onClosed) {
|
|
this._activeMessage.onClosed(this._activeMessage.closedBy);
|
|
}
|
|
|
|
if (this._activeMessage.buttons &&
|
|
this._activeMessage.buttons.length > 0) {
|
|
// We need to stop KeyNavigator while we don't need it.
|
|
this._keyNavigator.stop();
|
|
}
|
|
|
|
if (!this.hasPendings()) {
|
|
focusManager.focus();
|
|
}
|
|
|
|
this._activeType = null;
|
|
this._activeMessage = null;
|
|
};
|
|
|
|
proto.hideNotification = function in_hideNotification(type, msg, button) {
|
|
if (this._activeType === type && this._activeMessage === msg) {
|
|
this._banner.setAttribute('aria-hidden', 'true');
|
|
this._activeMessage.closedBy = button;
|
|
this._banner.hide();
|
|
} else {
|
|
var queue = this._pendingMessages[type];
|
|
var idx = queue.indexOf(msg);
|
|
if (idx > -1) {
|
|
var removed = queue.splice(idx, 1)[0];
|
|
if (removed.onClosed) {
|
|
removed.onClosed(button);
|
|
}
|
|
}
|
|
}
|
|
focusManager.focus();
|
|
};
|
|
|
|
exports.InteractiveNotifications = InteractiveNotifications;
|
|
}(window));
|