define(["jquery", "underscore", "backbone", "gettext", "js/utils/handle_iframe_binding", "js/utils/templates", "js/views/feedback_notification", "js/views/feedback_prompt"], function ($, _, Backbone, gettext, IframeUtils, TemplateUtils, NotificationView, PromptView) { /* This view is extended from backbone to provide useful functionality for all Studio views. This functionality includes: - automatic expand and collapse of elements with the 'ui-toggle-expansion' class specified - additional control of rendering by overriding 'beforeRender' or 'afterRender' Note: the default 'afterRender' function calls a utility function 'iframeBinding' which modifies iframe src urls on a page so that they are rendered as part of the DOM. */ var BaseView = Backbone.View.extend({ events: { "click .ui-toggle-expansion": "toggleExpandCollapse" }, //override the constructor function constructor: function(options) { _.bindAll(this, 'beforeRender', 'render', 'afterRender'); var _this = this; this.render = _.wrap(this.render, function (render, options) { _this.beforeRender(); render(options); _this.afterRender(); return _this; }); //call Backbone's own constructor Backbone.View.prototype.constructor.apply(this, arguments); }, beforeRender: function() { }, render: function() { return this; }, afterRender: function() { IframeUtils.iframeBinding(this); }, toggleExpandCollapse: function(event) { var target = $(event.target); // Don't propagate the event as it is possible that two views will both contain // this element, e.g. clicking on the element of a child view container in a parent. event.stopPropagation(); event.preventDefault(); target.closest('.expand-collapse').toggleClass('expand').toggleClass('collapse'); target.closest('.is-collapsible, .window').toggleClass('collapsed'); target.closest('.is-collapsible').children('article').slideToggle(); }, showLoadingIndicator: function() { $('.ui-loading').show(); }, hideLoadingIndicator: function() { $('.ui-loading').hide(); }, /** * Confirms with the user whether to run an operation or not, and then runs it if desired. */ confirmThenRunOperation: function(title, message, actionLabel, operation) { var self = this; return new PromptView.Warning({ title: title, message: message, actions: { primary: { text: actionLabel, click: function(prompt) { prompt.hide(); operation(); } }, secondary: { text: gettext('Cancel'), click: function(prompt) { return prompt.hide(); } } } }).show(); }, /** * Shows a progress message for the duration of an asynchronous operation. * Note: this does not remove the notification upon failure because an error * will be shown that shouldn't be removed. * @param message The message to show. * @param operation A function that returns a promise representing the operation. */ runOperationShowingMessage: function(message, operation) { var notificationView; notificationView = new NotificationView.Mini({ title: gettext(message) }); notificationView.show(); return operation().done(function() { notificationView.hide(); }); }, /** * Disables a given element when a given operation is running. * @param {jQuery} element: the element to be disabled. * @param operation: the operation during whose duration the * element should be disabled. The operation should return * a JQuery promise. */ disableElementWhileRunning: function(element, operation) { element.addClass("is-disabled"); return operation().always(function() { element.removeClass("is-disabled"); }); }, /** * Loads the named template from the page, or logs an error if it fails. * @param name The name of the template. * @returns The loaded template. */ loadTemplate: function(name) { return TemplateUtils.loadTemplate(name); }, /** * Returns the relative position that the element is scrolled from the top of the view port. * @param element The element in question. */ getScrollOffset: function(element) { var elementTop = element.offset().top; return elementTop - $(window).scrollTop(); }, /** * Scrolls the window so that the element is scrolled down to the specified relative position * from the top of the view port. * @param element The element in question. * @param offset The amount by which the element should be scrolled from the top of the view port. */ setScrollOffset: function(element, offset) { var elementTop = element.offset().top, newScrollTop = elementTop - offset; this.setScrollTop(newScrollTop); }, /** * Performs an animated scroll so that the window has the specified scroll top. * @param scrollTop The desired scroll top for the window. */ setScrollTop: function(scrollTop) { $('html, body').animate({ scrollTop: scrollTop }, 500); } }); return BaseView; });