Merge pull request #3027 from edx/andya/asset-loading-indicator
Add a loading indicator to the Files & Uploads page.
This commit is contained in:
@@ -236,6 +236,20 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
|
||||
create_sinon.respondWithJson(requests, @mockAssetsResponse)
|
||||
return requests
|
||||
|
||||
it "should show a status indicator while loading", ->
|
||||
appendSetFixtures('<div class="ui-loading"/>')
|
||||
expect($('.ui-loading').is(':visible')).toBe(true)
|
||||
setup.call(this)
|
||||
expect($('.ui-loading').is(':visible')).toBe(false)
|
||||
|
||||
it "should hide the status indicator if an error occurs while loading", ->
|
||||
requests = create_sinon.requests(this)
|
||||
appendSetFixtures('<div class="ui-loading"/>')
|
||||
expect($('.ui-loading').is(':visible')).toBe(true)
|
||||
@view.setPage(0)
|
||||
create_sinon.respondWithError(requests)
|
||||
expect($('.ui-loading').is(':visible')).toBe(false)
|
||||
|
||||
it "should render both assets", ->
|
||||
requests = setup.call(this)
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
define(["sinon"], function(sinon) {
|
||||
var fakeServer, fakeRequests, respondWithJson, respondWithError;
|
||||
|
||||
/* These utility methods are used by Jasmine tests to create a mock server or
|
||||
* get reference to mock requests. In either case, the cleanup (restore) is done with
|
||||
* an after function.
|
||||
@@ -15,7 +17,7 @@ define(["sinon"], function(sinon) {
|
||||
* Get a reference to the mocked server, and respond
|
||||
* to all requests with the specified statusCode.
|
||||
*/
|
||||
var fakeServer = function (statusCode, that) {
|
||||
fakeServer = function (statusCode, that) {
|
||||
var server = sinon.fakeServer.create();
|
||||
that.after(function() {
|
||||
server.restore();
|
||||
@@ -29,9 +31,9 @@ define(["sinon"], function(sinon) {
|
||||
* return a reference to the Array. This allows tests
|
||||
* to respond for individual requests.
|
||||
*/
|
||||
var fakeRequests = function (that) {
|
||||
var requests = [];
|
||||
var xhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeRequests = function (that) {
|
||||
var requests = [],
|
||||
xhr = sinon.useFakeXMLHttpRequest();
|
||||
xhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
@@ -43,16 +45,24 @@ define(["sinon"], function(sinon) {
|
||||
return requests;
|
||||
};
|
||||
|
||||
var respondWithJson = function(requests, jsonResponse, requestIndex) {
|
||||
respondWithJson = function(requests, jsonResponse, requestIndex) {
|
||||
requestIndex = requestIndex || requests.length - 1;
|
||||
requests[requestIndex].respond(200,
|
||||
{ "Content-Type": "application/json" },
|
||||
JSON.stringify(jsonResponse));
|
||||
};
|
||||
|
||||
respondWithError = function(requests, requestIndex) {
|
||||
requestIndex = requestIndex || requests.length - 1;
|
||||
requests[requestIndex].respond(500,
|
||||
{ "Content-Type": "application/json" },
|
||||
JSON.stringify({ }));
|
||||
};
|
||||
|
||||
return {
|
||||
"server": fakeServer,
|
||||
"requests": fakeRequests,
|
||||
"respondWithJson": respondWithJson
|
||||
"respondWithJson": respondWithJson,
|
||||
"respondWithError": respondWithError
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,80 +1,98 @@
|
||||
define(["js/views/paging", "js/views/asset", "js/views/paging_header", "js/views/paging_footer"],
|
||||
function(PagingView, AssetView, PagingHeader, PagingFooter) {
|
||||
define(["jquery", "underscore", "gettext", "js/views/paging", "js/views/asset", "js/views/paging_header", "js/views/paging_footer"],
|
||||
function($, _, gettext, PagingView, AssetView, PagingHeader, PagingFooter) {
|
||||
|
||||
var AssetsView = PagingView.extend({
|
||||
// takes AssetCollection as model
|
||||
var AssetsView = PagingView.extend({
|
||||
// takes AssetCollection as model
|
||||
|
||||
events : {
|
||||
"click .column-sort-link": "onToggleColumn"
|
||||
},
|
||||
events : {
|
||||
"click .column-sort-link": "onToggleColumn"
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
PagingView.prototype.initialize.call(this);
|
||||
var collection = this.collection;
|
||||
this.template = _.template($("#asset-library-tpl").text());
|
||||
this.listenTo(collection, 'destroy', this.handleDestroy);
|
||||
this.registerSortableColumn('js-asset-name-col', gettext('Name'), 'display_name', 'asc');
|
||||
this.registerSortableColumn('js-asset-date-col', gettext('Date Added'), 'date_added', 'desc');
|
||||
this.setInitialSortColumn('js-asset-date-col');
|
||||
},
|
||||
initialize : function() {
|
||||
PagingView.prototype.initialize.call(this);
|
||||
var collection = this.collection;
|
||||
this.template = _.template($("#asset-library-tpl").text());
|
||||
this.listenTo(collection, 'destroy', this.handleDestroy);
|
||||
this.registerSortableColumn('js-asset-name-col', gettext('Name'), 'display_name', 'asc');
|
||||
this.registerSortableColumn('js-asset-date-col', gettext('Date Added'), 'date_added', 'desc');
|
||||
this.setInitialSortColumn('js-asset-date-col');
|
||||
this.showLoadingIndicator();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.template());
|
||||
this.tableBody = this.$('#asset-table-body');
|
||||
this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')});
|
||||
this.pagingFooter = new PagingFooter({view: this, el: $('#asset-paging-footer')});
|
||||
this.pagingHeader.render();
|
||||
this.pagingFooter.render();
|
||||
render: function() {
|
||||
// Wait until the content is loaded the first time to render
|
||||
return this;
|
||||
},
|
||||
|
||||
// Hide the contents until the collection has loaded the first time
|
||||
this.$('.asset-library').hide();
|
||||
this.$('.no-asset-content').hide();
|
||||
getTableBody: function() {
|
||||
var tableBody = this.tableBody;
|
||||
if (!tableBody) {
|
||||
this.hideLoadingIndicator();
|
||||
|
||||
return this;
|
||||
},
|
||||
// Create the table
|
||||
this.$el.html(this.template());
|
||||
tableBody = this.$('#asset-table-body');
|
||||
this.tableBody = tableBody;
|
||||
this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')});
|
||||
this.pagingFooter = new PagingFooter({view: this, el: $('#asset-paging-footer')});
|
||||
this.pagingHeader.render();
|
||||
this.pagingFooter.render();
|
||||
|
||||
renderPageItems: function() {
|
||||
var self = this,
|
||||
assets = this.collection,
|
||||
hasAssets = assets.length > 0;
|
||||
self.tableBody.empty();
|
||||
if (hasAssets) {
|
||||
assets.each(
|
||||
function(asset) {
|
||||
var view = new AssetView({model: asset});
|
||||
self.tableBody.append(view.render().el);
|
||||
// Hide the contents until the collection has loaded the first time
|
||||
this.$('.asset-library').hide();
|
||||
this.$('.no-asset-content').hide();
|
||||
}
|
||||
return tableBody;
|
||||
},
|
||||
|
||||
renderPageItems: function() {
|
||||
var self = this,
|
||||
assets = this.collection,
|
||||
hasAssets = assets.length > 0,
|
||||
tableBody = this.getTableBody();
|
||||
tableBody.empty();
|
||||
if (hasAssets) {
|
||||
assets.each(
|
||||
function(asset) {
|
||||
var view = new AssetView({model: asset});
|
||||
tableBody.append(view.render().el);
|
||||
}
|
||||
);
|
||||
}
|
||||
self.$('.asset-library').toggle(hasAssets);
|
||||
self.$('.no-asset-content').toggle(!hasAssets);
|
||||
return this;
|
||||
},
|
||||
|
||||
onError: function() {
|
||||
this.hideLoadingIndicator();
|
||||
},
|
||||
|
||||
handleDestroy: function(model) {
|
||||
this.collection.fetch({reset: true}); // reload the collection to get a fresh page full of items
|
||||
analytics.track('Deleted Asset', {
|
||||
'course': course_location_analytics,
|
||||
'id': model.get('url')
|
||||
});
|
||||
}
|
||||
self.$('.asset-library').toggle(hasAssets);
|
||||
self.$('.no-asset-content').toggle(!hasAssets);
|
||||
return this;
|
||||
},
|
||||
},
|
||||
|
||||
handleDestroy: function(model, collection, options) {
|
||||
this.collection.fetch({reset: true}); // reload the collection to get a fresh page full of items
|
||||
analytics.track('Deleted Asset', {
|
||||
'course': course_location_analytics,
|
||||
'id': model.get('url')
|
||||
addAsset: function (model) {
|
||||
// Switch the sort column back to the default (most recent date added) and show the first page
|
||||
// so that the new asset is shown at the top of the page.
|
||||
this.setInitialSortColumn('js-asset-date-col');
|
||||
this.setPage(0);
|
||||
|
||||
analytics.track('Uploaded a File', {
|
||||
'course': course_location_analytics,
|
||||
'asset_url': model.get('url')
|
||||
});
|
||||
},
|
||||
|
||||
onToggleColumn: function(event) {
|
||||
var columnName = event.target.id;
|
||||
this.toggleSortOrder(columnName);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
addAsset: function (model) {
|
||||
// Switch the sort column back to the default (most recent date added) and show the first page
|
||||
// so that the new asset is shown at the top of the page.
|
||||
this.setInitialSortColumn('js-asset-date-col');
|
||||
this.setPage(0);
|
||||
|
||||
analytics.track('Uploaded a File', {
|
||||
'course': course_location_analytics,
|
||||
'asset_url': model.get('url')
|
||||
});
|
||||
},
|
||||
|
||||
onToggleColumn: function(event) {
|
||||
var columnName = event.target.id;
|
||||
this.toggleSortOrder(columnName);
|
||||
}
|
||||
});
|
||||
|
||||
return AssetsView;
|
||||
}); // end define();
|
||||
return AssetsView;
|
||||
}); // end define();
|
||||
|
||||
@@ -46,6 +46,14 @@ define(["jquery", "underscore", "backbone", "js/utils/handle_iframe_binding"],
|
||||
event.preventDefault();
|
||||
target.closest('.expand-collapse').toggleClass('expand').toggleClass('collapse');
|
||||
target.closest('.is-collapsible, .window').toggleClass('collapsed');
|
||||
},
|
||||
|
||||
showLoadingIndicator: function() {
|
||||
$('.ui-loading').show();
|
||||
},
|
||||
|
||||
hideLoadingIndicator: function() {
|
||||
$('.ui-loading').hide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,118 +1,121 @@
|
||||
define(["backbone", "js/views/feedback_alert", "gettext"], function(Backbone, AlertView, gettext) {
|
||||
define(["underscore", "js/views/baseview", "js/views/feedback_alert", "gettext"],
|
||||
function(_, BaseView, AlertView, gettext) {
|
||||
|
||||
var PagingView = Backbone.View.extend({
|
||||
// takes a Backbone Paginator as a model
|
||||
var PagingView = BaseView.extend({
|
||||
// takes a Backbone Paginator as a model
|
||||
|
||||
sortableColumns: {},
|
||||
sortableColumns: {},
|
||||
|
||||
initialize: function() {
|
||||
Backbone.View.prototype.initialize.call(this);
|
||||
var collection = this.collection;
|
||||
collection.bind('add', _.bind(this.onPageRefresh, this));
|
||||
collection.bind('remove', _.bind(this.onPageRefresh, this));
|
||||
collection.bind('reset', _.bind(this.onPageRefresh, this));
|
||||
},
|
||||
initialize: function() {
|
||||
BaseView.prototype.initialize.call(this);
|
||||
var collection = this.collection;
|
||||
collection.bind('add', _.bind(this.onPageRefresh, this));
|
||||
collection.bind('remove', _.bind(this.onPageRefresh, this));
|
||||
collection.bind('reset', _.bind(this.onPageRefresh, this));
|
||||
},
|
||||
|
||||
onPageRefresh: function() {
|
||||
var sortColumn = this.sortColumn;
|
||||
this.renderPageItems();
|
||||
this.$('.column-sort-link').removeClass('current-sort');
|
||||
this.$('#' + sortColumn).addClass('current-sort');
|
||||
},
|
||||
onPageRefresh: function() {
|
||||
var sortColumn = this.sortColumn;
|
||||
this.renderPageItems();
|
||||
this.$('.column-sort-link').removeClass('current-sort');
|
||||
this.$('#' + sortColumn).addClass('current-sort');
|
||||
},
|
||||
|
||||
setPage: function(page) {
|
||||
var self = this,
|
||||
collection = self.collection,
|
||||
oldPage = collection.currentPage;
|
||||
collection.goTo(page, {
|
||||
reset: true,
|
||||
success: function() {
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
error: function(collection, response, options) {
|
||||
collection.currentPage = oldPage;
|
||||
setPage: function(page) {
|
||||
var self = this,
|
||||
collection = self.collection,
|
||||
oldPage = collection.currentPage;
|
||||
collection.goTo(page, {
|
||||
reset: true,
|
||||
success: function() {
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
error: function(collection) {
|
||||
collection.currentPage = oldPage;
|
||||
self.onError();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onError: function() {
|
||||
// Do nothing by default
|
||||
},
|
||||
|
||||
nextPage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.currentPage,
|
||||
lastPage = collection.totalPages - 1;
|
||||
if (currentPage < lastPage) {
|
||||
this.setPage(currentPage + 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
nextPage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.currentPage,
|
||||
lastPage = collection.totalPages - 1;
|
||||
if (currentPage < lastPage) {
|
||||
this.setPage(currentPage + 1);
|
||||
previousPage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.currentPage;
|
||||
if (currentPage > 0) {
|
||||
this.setPage(currentPage - 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers information about a column that can be sorted.
|
||||
* @param columnName The element name of the column.
|
||||
* @param displayName The display name for the column in the current locale.
|
||||
* @param fieldName The database field name that is represented by this column.
|
||||
* @param defaultSortDirection The default sort direction for the column
|
||||
*/
|
||||
registerSortableColumn: function(columnName, displayName, fieldName, defaultSortDirection) {
|
||||
this.sortableColumns[columnName] = {
|
||||
displayName: displayName,
|
||||
fieldName: fieldName,
|
||||
defaultSortDirection: defaultSortDirection
|
||||
};
|
||||
},
|
||||
|
||||
sortableColumnInfo: function(sortColumn) {
|
||||
var sortInfo = this.sortableColumns[sortColumn];
|
||||
if (!sortInfo) {
|
||||
throw "Unregistered sort column '" + sortColumn + '"';
|
||||
}
|
||||
return sortInfo;
|
||||
},
|
||||
|
||||
sortDisplayName: function() {
|
||||
var sortColumn = this.sortColumn,
|
||||
sortInfo = this.sortableColumnInfo(sortColumn);
|
||||
return sortInfo.displayName;
|
||||
},
|
||||
|
||||
sortDirectionName: function() {
|
||||
var collection = this.collection,
|
||||
ascending = collection.sortDirection === 'asc';
|
||||
return ascending ? gettext("ascending") : gettext("descending");
|
||||
},
|
||||
|
||||
setInitialSortColumn: function(sortColumn) {
|
||||
var collection = this.collection,
|
||||
sortInfo = this.sortableColumns[sortColumn];
|
||||
collection.sortField = sortInfo.fieldName;
|
||||
collection.sortDirection = sortInfo.defaultSortDirection;
|
||||
this.sortColumn = sortColumn;
|
||||
},
|
||||
|
||||
toggleSortOrder: function(sortColumn) {
|
||||
var collection = this.collection,
|
||||
sortInfo = this.sortableColumnInfo(sortColumn),
|
||||
sortField = sortInfo.fieldName,
|
||||
defaultSortDirection = sortInfo.defaultSortDirection;
|
||||
if (collection.sortField === sortField) {
|
||||
collection.sortDirection = collection.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
collection.sortField = sortField;
|
||||
collection.sortDirection = defaultSortDirection;
|
||||
}
|
||||
this.sortColumn = sortColumn;
|
||||
this.setPage(0);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
previousPage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.currentPage;
|
||||
if (currentPage > 0) {
|
||||
this.setPage(currentPage - 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers information about a column that can be sorted.
|
||||
* @param columnName The element name of the column.
|
||||
* @param displayName The display name for the column in the current locale.
|
||||
* @param fieldName The database field name that is represented by this column.
|
||||
* @param defaultSortDirection The default sort direction for the column
|
||||
*/
|
||||
registerSortableColumn: function(columnName, displayName, fieldName, defaultSortDirection) {
|
||||
this.sortableColumns[columnName] = {
|
||||
displayName: displayName,
|
||||
fieldName: fieldName,
|
||||
defaultSortDirection: defaultSortDirection
|
||||
};
|
||||
},
|
||||
|
||||
sortableColumnInfo: function(sortColumn) {
|
||||
var sortInfo = this.sortableColumns[sortColumn];
|
||||
if (!sortInfo) {
|
||||
throw "Unregistered sort column '" + sortColumn + '"';
|
||||
}
|
||||
return sortInfo;
|
||||
},
|
||||
|
||||
sortDisplayName: function() {
|
||||
var sortColumn = this.sortColumn,
|
||||
sortInfo = this.sortableColumnInfo(sortColumn);
|
||||
return sortInfo.displayName;
|
||||
},
|
||||
|
||||
sortDirectionName: function() {
|
||||
var collection = this.collection;
|
||||
if (collection.sortDirection === 'asc') {
|
||||
return gettext("ascending");
|
||||
} else {
|
||||
return gettext("descending");
|
||||
}
|
||||
},
|
||||
|
||||
setInitialSortColumn: function(sortColumn) {
|
||||
var collection = this.collection,
|
||||
sortInfo = this.sortableColumns[sortColumn];
|
||||
collection.sortField = sortInfo.fieldName;
|
||||
collection.sortDirection = sortInfo.defaultSortDirection;
|
||||
this.sortColumn = sortColumn;
|
||||
},
|
||||
|
||||
toggleSortOrder: function(sortColumn) {
|
||||
var collection = this.collection,
|
||||
sortInfo = this.sortableColumnInfo(sortColumn),
|
||||
sortField = sortInfo.fieldName,
|
||||
defaultSortDirection = sortInfo.defaultSortDirection;
|
||||
if (collection.sortField === sortField) {
|
||||
collection.sortDirection = collection.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
collection.sortField = sortField;
|
||||
collection.sortDirection = defaultSortDirection;
|
||||
}
|
||||
this.sortColumn = sortColumn;
|
||||
this.setPage(0);
|
||||
}
|
||||
});
|
||||
|
||||
return PagingView;
|
||||
}); // end define();
|
||||
return PagingView;
|
||||
}); // end define();
|
||||
|
||||
@@ -27,7 +27,7 @@ require(["domReady", "jquery", "js/models/asset", "js/collections/asset",
|
||||
var assets = new AssetCollection();
|
||||
|
||||
assets.url = "${asset_callback_url}";
|
||||
var assetsView = new AssetsView({collection: assets, el: $('#asset-library')});
|
||||
var assetsView = new AssetsView({collection: assets, el: $('.assets-wrapper')});
|
||||
assetsView.render();
|
||||
assetsView.setPage(0);
|
||||
|
||||
@@ -148,7 +148,12 @@ require(["domReady", "jquery", "js/models/asset", "js/collections/asset",
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<article id="asset-library" class="content-primary" role="main"></article>
|
||||
<article class="content-primary" role="main">
|
||||
<div class="assets-wrapper"/>
|
||||
<div class="ui-loading">
|
||||
<p><span class="spin"><i class="icon-refresh"></i></span> <span class="copy">${_("Loading…")}</span></p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<aside class="content-supplementary" role="complimentary">
|
||||
<div class="bit">
|
||||
|
||||
Reference in New Issue
Block a user