define(["jquery", "underscore", "underscore.string", "backbone", "js/utils/templates"], function($, _, str, Backbone, TemplateUtils) { var SystemFeedback = Backbone.View.extend({ options: { title: "", message: "", intent: null, // "warning", "confirmation", "error", "announcement", "step-required", etc type: null, // "alert", "notification", or "prompt": set by subclass shown: true, // is this view currently being shown? icon: true, // should we render an icon related to the message intent? closeIcon: true, // should we render a close button in the top right corner? minShown: 0, // length of time after this view has been shown before it can be hidden (milliseconds) maxShown: Infinity // length of time after this view has been shown before it will be automatically hidden (milliseconds) /* Could also have an "actions" hash: here is an example demonstrating the expected structure. For each action, by default the framework will call preventDefault on the click event before the function is run; to make it not do that, just pass `preventDefault: false` in the action object. actions: { primary: { "text": "Save", "class": "action-save", "click": function(view) { // do something when Save is clicked } }, secondary: [ { "text": "Cancel", "class": "action-cancel", "click": function(view) {} }, { "text": "Discard Changes", "class": "action-discard", "click": function(view) {} } ] } */ }, initialize: function() { if (!this.options.type) { throw "SystemFeedback: type required (given " + JSON.stringify(this.options) + ")"; } if (!this.options.intent) { throw "SystemFeedback: intent required (given " + JSON.stringify(this.options) + ")"; } this.template = TemplateUtils.loadTemplate("system-feedback"); this.setElement($("#page-" + this.options.type)); // handle single "secondary" action if (this.options.actions && this.options.actions.secondary && !_.isArray(this.options.actions.secondary)) { this.options.actions.secondary = [this.options.actions.secondary]; } return this; }, // public API: show() and hide() show: function() { clearTimeout(this.hideTimeout); this.options.shown = true; this.shownAt = new Date(); this.render(); if ($.isNumeric(this.options.maxShown)) { this.hideTimeout = setTimeout(_.bind(this.hide, this), this.options.maxShown); } return this; }, hide: function() { if (this.shownAt && $.isNumeric(this.options.minShown) && this.options.minShown > new Date() - this.shownAt) { clearTimeout(this.hideTimeout); this.hideTimeout = setTimeout(_.bind(this.hide, this), this.options.minShown - (new Date() - this.shownAt)); } else { this.options.shown = false; delete this.shownAt; this.render(); } return this; }, // the rest of the API should be considered semi-private events: { "click .action-close": "hide", "click .action-primary": "primaryClick", "click .action-secondary": "secondaryClick" }, render: function() { // there can be only one active view of a given type at a time: only // one alert, only one notification, only one prompt. Therefore, we'll // use a singleton approach. var singleton = SystemFeedback["active_" + this.options.type]; if (singleton && singleton !== this) { singleton.stopListening(); singleton.undelegateEvents(); } this.$el.html(this.template(this.options)); SystemFeedback["active_" + this.options.type] = this; return this; }, primaryClick: function(event) { var actions, primary; actions = this.options.actions; if (!actions) { return; } primary = actions.primary; if (!primary) { return; } if (primary.preventDefault !== false) { event.preventDefault(); } if (primary.click) { primary.click.call(event.target, this, event); } }, secondaryClick: function(event) { var actions, secondaryList, secondary, i; actions = this.options.actions; if (!actions) { return; } secondaryList = actions.secondary; if (!secondaryList) { return; } // which secondary action was clicked? i = 0; // default to the first secondary action (easier for testing) if (event && event.target) { i = _.indexOf(this.$(".action-secondary"), event.target); } secondary = secondaryList[i]; if (secondary.preventDefault !== false) { event.preventDefault(); } if (secondary.click) { secondary.click.call(event.target, this, event); } } }); return SystemFeedback; });