Commit f92c3372 by Andy Armstrong

Add a loading indicator to the Files & Uploads page.

parent ca685028
......@@ -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")
......
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
};
});
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);
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')
});
},
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);
}
});
},
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();
}
});
......
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();
}
});
},
nextPage: function() {
var collection = this.collection,
currentPage = collection.currentPage,
lastPage = collection.totalPages - 1;
if (currentPage < lastPage) {
this.setPage(currentPage + 1);
}
},
onError: function() {
// Do nothing by default
},
previousPage: function() {
var collection = this.collection,
currentPage = collection.currentPage;
if (currentPage > 0) {
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
};
},
/**
* 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;
},
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;
},
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");
}
},
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;
},
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;
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);
}
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&hellip;")}</span></p>
</div>
</article>
<aside class="content-supplementary" role="complimentary">
<div class="bit">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment