Commit a2eb5c7b by Tim Krones

Refactor to create better separation of concerns in client-side code.

For example, ResultView should not manipulate the status area (but it
should maintain meta info about result sets).
parent 6e6f31cd
...@@ -81,14 +81,17 @@ function InstructorToolBlock(runtime, element) { ...@@ -81,14 +81,17 @@ function InstructorToolBlock(runtime, element) {
initialize: function() { initialize: function() {
this.listenTo(this.collection, 'reset', this.render); this.listenTo(this.collection, 'reset', this.render);
this.listenTo(this, 'rendered', this._show);
this.listenTo(this, 'processing', this._hide);
this.listenTo(this, 'error', this._hide);
this.listenTo(this, 'update', this._updateInfo);
}, },
render: function() { render: function() {
this._insertRecords(); this._insertRecords();
this._updateControls(); this._updateControls();
this.$('#total-pages').text(this.collection.getTotalPages() || 0); this.$('#total-pages').text(this.collection.getTotalPages() || 0);
$('.data-export-status', $element).empty(); this.trigger('rendered');
this.$el.show(700);
return this; return this;
}, },
...@@ -109,6 +112,20 @@ function InstructorToolBlock(runtime, element) { ...@@ -109,6 +112,20 @@ function InstructorToolBlock(runtime, element) {
} }
}, },
_show: function() {
this.$el.show(700);
},
_hide: function() {
this.$el.hide();
},
_updateInfo: function(info) {
var $exportInfo = this.$('.data-export-info');
$exportInfo.empty();
$exportInfo.append($('<p>').text(info));
},
events: { events: {
'click #first-page': '_firstPage', 'click #first-page': '_firstPage',
'click #prev-page': '_prevPage', 'click #prev-page': '_prevPage',
...@@ -162,6 +179,38 @@ function InstructorToolBlock(runtime, element) { ...@@ -162,6 +179,38 @@ function InstructorToolBlock(runtime, element) {
el: $element.find('#results') el: $element.find('#results')
}); });
// Status area
var StatusView = Backbone.View.extend({
initialize: function() {
this.listenTo(this, 'processing', this._showSpinner);
this.listenTo(this, 'notify', this._displayMessage);
this.listenTo(this, 'stopped', this._empty);
this.listenTo(resultsView, 'rendered', this._empty);
},
_showSpinner: function() {
this.$el.empty();
this.$el.append(
$('<i>').addClass('icon fa fa-spinner fa-spin')
).css('text-align', 'center');
},
_displayMessage: function(message) {
this.$el.append($('<p>').text(message));
},
_empty: function() {
this.$el.empty();
}
});
var statusView = new StatusView({
el: $element.find('.data-export-status')
});
// Set up gettext in case it isn't available in the client runtime: // Set up gettext in case it isn't available in the client runtime:
if (typeof gettext == "undefined") { if (typeof gettext == "undefined") {
window.gettext = function gettext_stub(string) { return string; }; window.gettext = function gettext_stub(string) { return string; };
...@@ -198,18 +247,15 @@ function InstructorToolBlock(runtime, element) { ...@@ -198,18 +247,15 @@ function InstructorToolBlock(runtime, element) {
if (statusChanged) updateView(); if (statusChanged) updateView();
} }
function showSpinner() { function disableActions() {
$startButton.prop('disabled', true); $startButton.prop('disabled', true);
$cancelButton.prop('disabled', true); $cancelButton.prop('disabled', true);
$downloadButton.prop('disabled', true); $downloadButton.prop('disabled', true);
$deleteButton.prop('disabled', true); $deleteButton.prop('disabled', true);
$('.data-export-status', $element).empty().append(
$('<i>').addClass('icon fa fa-spinner fa-spin')
).css("text-align", "center");
} }
function hideResults() { function showInfo(info) {
$resultTable.hide(); resultsView.trigger('update', info);
} }
function showResults() { function showResults() {
...@@ -218,6 +264,22 @@ function InstructorToolBlock(runtime, element) { ...@@ -218,6 +264,22 @@ function InstructorToolBlock(runtime, element) {
} }
} }
function hideResults() {
resultsView.trigger('processing');
}
function showSpinner() {
statusView.trigger('processing');
}
function hideSpinner() {
statusView.trigger('stopped');
}
function showStatusMessage(message) {
statusView.trigger('notify', message);
}
function handleError(data) { function handleError(data) {
// Shim to make the XBlock JsonHandlerError response work with our format. // Shim to make the XBlock JsonHandlerError response work with our format.
status = {'last_export_result': JSON.parse(data.responseText), 'export_pending': false}; status = {'last_export_result': JSON.parse(data.responseText), 'export_pending': false};
...@@ -225,25 +287,22 @@ function InstructorToolBlock(runtime, element) { ...@@ -225,25 +287,22 @@ function InstructorToolBlock(runtime, element) {
} }
function updateView() { function updateView() {
var $exportInfo = $('.data-export-info', $element), var startTime;
$statusArea = $('.data-export-status', $element), startTime;
$exportInfo.empty();
$startButton.toggle(!status.export_pending).prop('disabled', false); $startButton.toggle(!status.export_pending).prop('disabled', false);
$cancelButton.toggle(status.export_pending).prop('disabled', false); $cancelButton.toggle(status.export_pending).prop('disabled', false);
$downloadButton.toggle(Boolean(status.download_url)).prop('disabled', false); $downloadButton.toggle(Boolean(status.download_url)).prop('disabled', false);
$deleteButton.toggle(Boolean(status.last_export_result)).prop('disabled', false); $deleteButton.toggle(Boolean(status.last_export_result)).prop('disabled', false);
if (status.last_export_result) { if (status.last_export_result) {
if (status.last_export_result.error) { if (status.last_export_result.error) {
$statusArea.append($('<p>').text( hideResults();
_.template( hideSpinner();
showStatusMessage(_.template(
gettext('Data export failed. Reason: <%= error %>'), gettext('Data export failed. Reason: <%= error %>'),
{'error': status.last_export_result.error} {'error': status.last_export_result.error}
)
)); ));
hideResults();
} else { } else {
startTime = new Date(status.last_export_result.start_timestamp * 1000); startTime = new Date(status.last_export_result.start_timestamp * 1000);
$exportInfo.append($('<p>').text( showInfo(
_.template( _.template(
ngettext( ngettext(
'Results retrieved on <%= creation_time %> (<%= seconds %> second).', 'Results retrieved on <%= creation_time %> (<%= seconds %> second).',
...@@ -254,15 +313,14 @@ function InstructorToolBlock(runtime, element) { ...@@ -254,15 +313,14 @@ function InstructorToolBlock(runtime, element) {
'creation_time': startTime.toString(), 'creation_time': startTime.toString(),
'seconds': status.last_export_result.generation_time_s.toFixed(1) 'seconds': status.last_export_result.generation_time_s.toFixed(1)
} }
)
)); ));
resultsView.collection.getFirstPage(); resultsView.collection.getFirstPage();
} }
} else { } else {
if (status.export_pending) { if (status.export_pending) {
$statusArea.append($('<p>').text( showStatusMessage(gettext('The report is currently being generated…'));
gettext('The report is currently being generated…') } else {
)); hideSpinner();
} }
} }
} }
...@@ -290,6 +348,7 @@ function InstructorToolBlock(runtime, element) { ...@@ -290,6 +348,7 @@ function InstructorToolBlock(runtime, element) {
dataType: 'json' dataType: 'json'
}); });
showSpinner(); showSpinner();
disableActions();
}); });
} }
...@@ -306,6 +365,7 @@ function InstructorToolBlock(runtime, element) { ...@@ -306,6 +365,7 @@ function InstructorToolBlock(runtime, element) {
}); });
showSpinner(); showSpinner();
disableActions();
getStatus(); getStatus();
} }
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