Alert.js 10.80 KiB
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* global $ */
/* global EventEmitter */
/* @depend QfqEvents.js */
/* @depend AlertManager.js */
/**
* Qfq Namespace
*
* @namespace QfqNS
*/
var QfqNS = QfqNS || {};
(function (n) {
'use strict';
/**
* Display a message.
*
* A typical call sequence might look like:
*
* var alert = new QfqNS.Alert({
* message: "Text being displayed",
* type: "info"
* });
* alert.show();
*
* Messages may have different background colors (severity levels), controlled by the `type` property. Possible
* values are
*
* * `"success"`
* * `"info"`
* * `"warning"`
* * `"error"`, `"danger"`
*
* The values are translated into Bootstrap `alert-*` classes internally.
*
* If no buttons are configured, a click anywhere on the alert will close it.
*
* Buttons are configured by passing an array of objects in the `buttons` property. The properties of the object
* are as follows
*
* {
* label: <button label>,
* focus: true | false,
* eventName: <eventname>
* }
*
* You can connect to the button events by using
*
* var alert = new QfqNS.Alert({
* message: "Text being displayed",
* type: "info",
* buttons: [
* { label: "OK", eventName: "ok" }
* ]
* });
* alert.on('alert.ok', function(...) { ... });
*
* Events are named according to `alert.<eventname>`.
*
* If the property `modal` is set to `true`, a kind-of modal alert will be displayed, preventing clicks
* anywhere but the alert.
*
* For compatibility reasons, the old constructor signature is still supported but deprecated
*
* var alert = new QfqNS.Alert(message, type, buttons)
*
* @param {object} options option object has following properties
* @param {string} options.message message to be displayed
* @param {string} [options.type] type of message, can be `"info"`, `"warning"`, or `"error"`. Default is `"info"`.
* @param {number} [options.timeout] timeout in milliseconds. If timeout is less than or equal to 0, the alert
* won't timeout and stay open until dismissed by the user. Default `n.Alert.constants.NO_TIMEOUT`.
* @param {boolean} [options.modal] whether or not alert is modal, i.e. prevent clicks anywhere but the dialog.
* Default is `false`.
* @param {object[]} options.buttons what buttons to display on alert. If empty array is provided, no buttons are
* displayed and a click anywhere in the alert will dismiss it.
* @param {string} options.buttons.label label of the button
* @param {string} options.buttons.eventName name of the event when button is clicked.
* @param {boolean} [options.buttons.focus] whether or not button has focus by default. Default is `false`.
*
* @constructor
*/
n.Alert = function (options) {
// Emulate old behavior of method signature
// function(message, messageType, buttons)
if (typeof options === "string") {
this.message = arguments[0];
this.messageType = arguments[1] || "info";
this.buttons = arguments[2] || [];
this.modal = false;
// this.timeout < 1 means forever
this.timeout = n.Alert.constants.NO_TIMEOUT;
} else {
// new style
this.message = options.message || "MESSAGE";
this.messageType = options.type || "info";
this.messageTitle = options.title || false;
this.errorCode = options.code || false;
this.buttons = options.buttons || [];
this.modal = options.modal || false;
this.timeout = options.timeout || n.Alert.constants.NO_TIMEOUT;
}
this.$alertDiv = null;
this.$modalDiv = null;
this.shown = false;
this.fadeInDuration = 400;
this.fadeOutDuration = 400;
this.timerId = null;
this.parent = {};
this.identifier = false;
this.eventEmitter = new EventEmitter();
};
n.Alert.prototype.on = n.EventEmitter.onMixin;
n.Alert.constants = {
alertContainerId: "alert-interactive",
alertContainerSelector: "#qfqAlertContainer",
jQueryAlertRemoveEventName: "qfqalert.remove:",
NO_TIMEOUT: 0
};
/**
*
* @private
*/
n.Alert.prototype.makeAlertContainerSingleton = function () {
if (!n.QfqPage.alertManager) {
n.QfqPage.alertManager = new n.AlertManager({});
}
return n.QfqPage.alertManager;
};
n.Alert.prototype.setIdentifier = function (i) {
this.identifier = i;
};
/**
*
* @returns {number|jQuery}
* @private
*/
n.Alert.prototype.countAlertsInAlertContainer = function () {
return $(n.Alert.constants.alertContainerSelector + " > div").length;
};
/**
* @private
*/
n.Alert.prototype.removeAlertContainer = function () {
if (this.modal) {
this.shown = false;
this.parent.removeModalAlert();
}
};
/**
* @private
*/
n.Alert.prototype.getAlertClassBasedOnMessageType = function () {
switch (this.messageType) {
case "warning":
return "border-warning";
case "danger":
case "error":
return "border-error";
case "info":
return "border-info";
case "success":
return "border-success";
/* jshint -W086 */
default:
n.Log.warning("Message type '" + this.messageType + "' unknown. Use default type.");
/* jshint +W086 */
}
};
/**
* @private
*/
n.Alert.prototype.getButtons = function () {
var $buttons = null;
var $container = $("<p>").addClass("buttons");
var numberOfButtons = this.buttons.length;
var index;
var buttonConfiguration;
for (index = 0; index < numberOfButtons; index++) {
buttonConfiguration = this.buttons[index];
if (!$buttons) {
if (numberOfButtons > 1) {
$buttons = $("<div>").addClass("btn-group");
} else {
$buttons = $container;
}
}
var focus = buttonConfiguration.focus ? buttonConfiguration.focus : false;
$buttons.append($("<button>").append(buttonConfiguration.label)
.attr('type', 'button')
.addClass("btn btn-default" + (focus ? " wants-focus" : ""))
.click(buttonConfiguration, this.buttonHandler.bind(this)));
}
if (numberOfButtons > 1) {
$container.append($buttons);
$buttons = $container;
}
return $buttons;
};
/**
* @public
*/
n.Alert.prototype.show = function () {
$(".removeMe").remove();
var alertContainer;
if (this.shown) {
// We only allow showing once
return;
}
this.parent = this.makeAlertContainerSingleton();
this.parent.addAlert(this);
if (this.modal) {
this.$modalDiv = $("<div>", {
class: "removeMe"
});
this.parent.createBlockScreen(this);
}
if (this.messageTitle) {
this.$titleWrap = $("<p>")
.addClass("title")
.append(this.messageTitle);
}
this.$messageWrap = $("<p>")
.addClass("body")
.append(this.message);
this.$alertDiv = $("<div>")
.hide()
.addClass(this.getAlertClassBasedOnMessageType())
.attr("role", "alert")
.append(this.$messageWrap);
if (this.$titleWrap) {
this.$alertDiv.prepend(this.$titleWrap);
}
if (this.modal) {
this.$alertDiv.addClass("alert-interactive");
this.$alertDiv.css('z-index', 1000);
} else {
this.$alertDiv.addClass("alert-side");
}
this.$alertDiv.addClass("removeMe");
var buttons = this.getButtons();
if (buttons) {
// Buttons will take care of removing the message
this.$alertDiv.append(buttons);
} else {
// Click on the message anywhere will remove the message
this.$alertDiv.click(this.removeAlert.bind(this));
// Allows to remove all alerts that do not require user interaction, programmatically. Yes, we could send
// the "click" event, but we want to communicate our intention clearly.
this.$alertDiv.on(n.Alert.constants.jQueryAlertRemoveEventName, this.removeAlert.bind(this));
}
this.parent.$container.append(this.$alertDiv);
//this.$alertDiv.slideDown(this.fadeInDuration, this.afterFadeIn.bind(this));
if (!this.modal) {
this.$alertDiv.animate({width:'show'}, this.fadeInDuration, this.afterFadeIn.bind(this));
} else {
this.$alertDiv.fadeIn(this.fadeInDuration);
}
this.$alertDiv.find(".wants-focus").focus();
this.shown = true;
};
/**
* @private
*/
n.Alert.prototype.afterFadeIn = function () {
if (this.timeout > 0) {
this.timerId = window.setTimeout(this.removeAlert.bind(this), this.timeout);
}
};
/**
*
*
* @private
*/
n.Alert.prototype.removeAlert = function () {
// In case we have an armed timer (or expired timer, for that matter), disarm it.
if (this.timerId) {
window.clearTimeout(this.timerId);
this.timerId = null;
}
var that = this;
if (!this.modal) {
this.$alertDiv.animate({width:'hide'}, this.fadeOutDuration, function () {
that.$alertDiv.remove();
that.$alertDiv = null;
that.shown = false;
that.parent.removeOutdatedAlerts();
});
} else {
this.$alertDiv.fadeOut(this.fadeOutDuration, function(){
that.$alertDiv.remove();
that.$alertDiv = null;
that.shown = false;
that.removeAlertContainer();
});
}
this.parent.removeAlert(this.identifier);
};
/**
*
* @param handler
*
* @private
*/
n.Alert.prototype.buttonHandler = function (event) {
this.removeAlert();
this.eventEmitter.emitEvent('alert.' + event.data.eventName, n.EventEmitter.makePayload(this, null));
};
n.Alert.prototype.isShown = function () {
return this.shown;
};
})(QfqNS);