Source: Alert.js

/**
                * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
                */

                /* global $ */
                /* global EventEmitter */
                /* @depend QfqEvents.js */

                /**
                * Qfq Namespace
                *
                * @namespace QfqNS
                */
                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({
                * message: "Text being displayed",
                * type: "info"
                * });
                * alert.show();
                *
                * Messages may have different background colors (severity levels), controlled by the `type` property.
                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 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 having 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 {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 = 0;
                } else {
                // new style
                this.message = options.message || "MESSAGE";
                this.messageType = options.type || "info";
                this.buttons = options.buttons || [];
                this.modal = options.modal || false;
                this.timeout = options.timeout || 0;
                }

                this.$alertDiv = null;
                this.$modalDiv = null;
                this.shown = false;

                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();
                if (this.modal) {
                this.$modalDiv = $("<div>");
                this.$modalDiv.css('width', Math.max(document.documentElement.clientWidth, window.innerWidth || 0));
                this.$modalDiv.css('height', Math.max(document.documentElement.clientHeight, window.innerHeight || 0));
                }

                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));
                }

                if (this.modal) {
                this.$modalDiv.append(this.$alertDiv);
                $alertContainer.append(this.$modalDiv);
                } else {
                $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;
                if (that.modal) {
                that.$modalDiv.remove();
                that.$modalDiv = null;
                }
                that.shown = false;

                // TODO: removeAlert should not have knowledge on how to handle alert container
                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);