/** * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch> */ /* global $ */ /* global EventEmitter */ /* @depend QfqEvents.js */ var QfqNS = QfqNS || {}; (function (n) { 'use strict'; /** * Display a message. * * Display one message on a page. Several instances can be used per page, which results in messages being * stacked, with the latest message being at the bottom. * * The first instance displaying a message will append an `alert container` to the body. The last message being * dismissed will remove the `alert container`. A typical call sequence might look like: * * var alert = new QfqNS.Alert("Text being displayed", "info"); * alert.show(); * * Messages may have different background colors (severity levels), controlled by the second argument * `messageType` of the constructor. The possible values are * * * `"info"` * * `"warning"` * * `"error"` * * 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 to the constructor. The attributes 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("Text being displayed", "info", [{ label: "OK", eventName: "ok" }]); * alert.on('alert.ok', function(...) { ... }); * * Events are named according to `alert.<eventname>`. * * @param message {string} message to be displayed * @param messageType {string} type of message, can either be `"info"`, `"warning"`, or `"error"`. * @param buttons {list} buttons to be displayed * When `"none"` is provided, clicking anywhere on the message will dismiss it. * @constructor */ n.Alert = function (message, messageType, buttons) { this.message = message; this.messageType = messageType || "info"; this.buttons = buttons || []; this.$alertDiv = null; this.shown = false; // this.timeout < 1 means forever this.timeout = 0; this.fadeInDuration = 400; this.fadeOutDuration = 400; this.timerId = null; this.eventEmitter = new EventEmitter(); }; n.Alert.prototype.on = n.EventEmitter.onMixin; /** * * @private */ n.Alert.prototype.makeAlertContainerSingleton = function () { var alertContainer = $("#qfqAlertContainer"); if (alertContainer.length === 0) { // No container so far, create one alertContainer = $("<div>").attr("id", "qfqAlertContainer"); $("body").append(alertContainer); } return alertContainer; }; /** * * @returns {number|jQuery} * @private */ n.Alert.prototype.countAlertsInAlertContainer = function () { return $("#qfqAlertContainer > div").length; }; /** * @private */ n.Alert.prototype.removeAlertContainer = function () { $("#qfqAlertContainer").remove(); }; /** * @private */ n.Alert.prototype.getAlertClassBasedOnMessageType = function () { switch (this.messageType) { case "warning": return "alert-warning"; case "error": return "alert-danger"; /* jshint -W086 */ default: n.Log.warning("Message type '" + this.messageType + "' unknown. Use default type."); case "info": return "alert-success"; /* jshint +W086 */ } }; /** * @private */ n.Alert.prototype.getButtons = function () { var $buttons = null; var numberOfButtons = this.buttons.length; var index; var buttonConfiguration; for (index = 0; index < numberOfButtons; index++) { buttonConfiguration = this.buttons[index]; if (!$buttons) { $buttons = $("<div>").addClass("alert-buttons"); } 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))); } return $buttons; }; n.Alert.prototype.show = function () { if (this.shown) { // We only allow showing once return; } var $alertContainer = this.makeAlertContainerSingleton(); this.$alertDiv = $("<div>") .hide() .addClass("alert") .addClass(this.getAlertClassBasedOnMessageType()) .attr("role", "alert") .append(this.message); 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)); } $alertContainer.append(this.$alertDiv); this.$alertDiv.slideDown(this.fadeInDuration, this.afterFadeIn.bind(this)); 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; this.$alertDiv.slideUp(this.fadeOutDuration, function () { that.$alertDiv.remove(); that.$alertDiv = null; that.shown = false; if (that.countAlertsInAlertContainer() === 0) { that.removeAlertContainer(); } }); }; /** * * @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);