Commit c2f0b7d4 by Peter Fogg

Fix validation in many cases, and ensure that the notification bar pops up correctly.

parent d9716a72
...@@ -71,7 +71,7 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ ...@@ -71,7 +71,7 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
}, },
validate : function(attrs) { validate : function(attrs) {
var errors = {}; var errors = {};
if (attrs['type']) { if (_.has(attrs, 'type')) {
if (_.isEmpty(attrs['type'])) { if (_.isEmpty(attrs['type'])) {
errors.type = "The assignment type must have a name."; errors.type = "The assignment type must have a name.";
} }
...@@ -83,7 +83,7 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ ...@@ -83,7 +83,7 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
} }
} }
} }
if (attrs['weight']) { if (_.has(attrs, 'weight')) {
if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) { if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) {
errors.weight = "Please enter an integer between 0 and 100."; errors.weight = "Please enter an integer between 0 and 100.";
} }
...@@ -97,19 +97,19 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ ...@@ -97,19 +97,19 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
// errors.weight = "The weights cannot add to more than 100."; // errors.weight = "The weights cannot add to more than 100.";
} }
}} }}
if (attrs['min_count']) { if (_.has(attrs, 'min_count')) {
if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) { if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
errors.min_count = "Please enter an integer."; errors.min_count = "Please enter an integer.";
} }
else attrs.min_count = parseInt(attrs.min_count); else attrs.min_count = parseInt(attrs.min_count);
} }
if (attrs['drop_count']) { if (_.has(attrs, 'drop_count')) {
if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) { if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
errors.drop_count = "Please enter an integer."; errors.drop_count = "Please enter an integer.";
} }
else attrs.drop_count = parseInt(attrs.drop_count); else attrs.drop_count = parseInt(attrs.drop_count);
} }
if (attrs['min_count'] && attrs['drop_count'] && attrs.drop_count > attrs.min_count) { if (_.has(attrs, 'min_count') && _.has(attrs, 'drop_count') && attrs.drop_count > attrs.min_count) {
errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned."; errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned.";
} }
if (!_.isEmpty(errors)) return errors; if (!_.isEmpty(errors)) return errors;
......
...@@ -56,6 +56,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -56,6 +56,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
CodeMirror.fromTextArea(textarea, { CodeMirror.fromTextArea(textarea, {
mode: "application/json", lineNumbers: false, lineWrapping: false, mode: "application/json", lineNumbers: false, lineWrapping: false,
onChange: function(instance, changeobj) { onChange: function(instance, changeobj) {
instance.save()
// this event's being called even when there's no change :-( // this event's being called even when there's no change :-(
if (instance.getValue() !== oldValue) { if (instance.getValue() !== oldValue) {
var message = gettext("Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented."); var message = gettext("Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.");
...@@ -94,8 +95,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -94,8 +95,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
} }
} }
if (JSONValue !== undefined) { if (JSONValue !== undefined) {
self.clearValidationErrors(); self.model.set(key, JSONValue);
self.model.set(key, JSONValue, {validate: true});
} }
} }
}); });
...@@ -115,7 +115,8 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -115,7 +115,8 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
analytics.track('Saved Advanced Settings', { analytics.track('Saved Advanced Settings', {
'course': course_location_analytics 'course': course_location_analytics
}); });
} },
silent: true
}); });
}, },
revertView: function() { revertView: function() {
......
...@@ -89,7 +89,6 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -89,7 +89,6 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
var timefield = $(div).find("input:.time"); var timefield = $(div).find("input:.time");
var cachethis = this; var cachethis = this;
var setfield = function () { var setfield = function () {
cachethis.clearValidationErrors();
var date = datefield.datepicker('getDate'); var date = datefield.datepicker('getDate');
if (date) { if (date) {
var time = timefield.timepicker("getSecondsFromMidnight"); var time = timefield.timepicker("getSecondsFromMidnight");
...@@ -98,14 +97,16 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -98,14 +97,16 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
} }
var newVal = new Date(date.getTime() + time * 1000); var newVal = new Date(date.getTime() + time * 1000);
if (!cacheModel.has(fieldName) || cacheModel.get(fieldName).getTime() !== newVal.getTime()) { if (!cacheModel.has(fieldName) || cacheModel.get(fieldName).getTime() !== newVal.getTime()) {
cacheModel.set(fieldName, newVal, {validate: true}); cachethis.clearValidationErrors();
cachethis.setAndValidate(fieldName, newVal);
} }
} }
else { else {
// Clear date (note that this clears the time as well, as date and time are linked). // Clear date (note that this clears the time as well, as date and time are linked).
// Note also that the validation logic prevents us from clearing the start date // Note also that the validation logic prevents us from clearing the start date
// (start date is required by the back end). // (start date is required by the back end).
cacheModel.set(fieldName, null, {validate: true}); cachethis.clearValidationErrors();
cachethis.setAndValidate(fieldName, null);
} }
}; };
...@@ -142,14 +143,13 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -142,14 +143,13 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
default: // Everything else is handled by datepickers and CodeMirror. default: // Everything else is handled by datepickers and CodeMirror.
break; break;
} }
var self = this;
this.showNotificationBar(this.save_message, this.showNotificationBar(this.save_message,
_.bind(this.saveView, this), _.bind(this.saveView, this),
_.bind(this.revertView, this)); _.bind(this.revertView, this));
}, },
removeSyllabus: function() { removeSyllabus: function() {
if (this.model.has('syllabus')) this.model.set({'syllabus': null}); if (this.model.has('syllabus')) this.setAndValidate('syllabus', null);
}, },
assetSyllabus : function() { assetSyllabus : function() {
...@@ -184,7 +184,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -184,7 +184,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
cachethis.clearValidationErrors(); cachethis.clearValidationErrors();
var newVal = mirror.getValue(); var newVal = mirror.getValue();
if (cachethis.model.get(field) != newVal) { if (cachethis.model.get(field) != newVal) {
cachethis.model.set(field, newVal); cachethis.setAndValidate(field, newVal);
cachethis.showNotificationBar(cachethis.save_message, cachethis.showNotificationBar(cachethis.save_message,
_.bind(cachethis.saveView, cachethis), _.bind(cachethis.saveView, cachethis),
_.bind(cachethis.revertView, cachethis)); _.bind(cachethis.revertView, cachethis));
...@@ -209,6 +209,16 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -209,6 +209,16 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
}); });
}, },
reset: true}); reset: true});
},
setAndValidate: function(attr, value) {
// If we call model.set() with {validate: true}, model fields
// will not be set if validation fails. This puts the UI and
// the model in an inconsistent state, and causes us to not
// see the right validation errors the next time validate() is
// called on the model. So we set *without* validating, then
// call validate ourselves.
this.model.set(attr, value);
this.model.isValid();
} }
}); });
...@@ -38,6 +38,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -38,6 +38,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
} }
); );
this.listenTo(this.model, 'invalid', this.handleValidationError); this.listenTo(this.model, 'invalid', this.handleValidationError);
this.listenTo(this.model, 'change', this.showNotificationBar);
this.model.get('graders').on('reset', this.render, this); this.model.get('graders').on('reset', this.render, this);
this.model.get('graders').on('add', this.render, this); this.model.get('graders').on('add', this.render, this);
this.selectorToField = _.invert(this.fieldToSelectorMap); this.selectorToField = _.invert(this.fieldToSelectorMap);
...@@ -53,14 +54,18 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -53,14 +54,18 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// Undo the double invocation error. At some point, fix the double invocation // Undo the double invocation error. At some point, fix the double invocation
$(gradelist).empty(); $(gradelist).empty();
var gradeCollection = this.model.get('graders'); var gradeCollection = this.model.get('graders');
// We need to bind the 'remove' event here (rather than in // We need to bind these events here (rather than in
// initialize), or else we can only press the delete button // initialize), or else we can only press the delete button
// once due to the graders collection changing when we cancel // once due to the graders collection changing when we cancel
// our changes. // our changes.
gradeCollection.on('remove', function() { _.each(['change', 'remove', 'add'],
function (event) {
gradeCollection.on(event, function() {
this.showNotificationBar(); this.showNotificationBar();
this.render(); this.render();
}, this); }, this);
},
this);
gradeCollection.each(function(gradeModel) { gradeCollection.each(function(gradeModel) {
$(gradelist).append(self.template({model : gradeModel })); $(gradelist).append(self.template({model : gradeModel }));
var newEle = gradelist.children().last(); var newEle = gradelist.children().last();
...@@ -69,9 +74,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -69,9 +74,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// Listen in order to rerender when the 'cancel' button is // Listen in order to rerender when the 'cancel' button is
// pressed // pressed
self.listenTo(newView, 'revert', _.bind(self.render, self)); self.listenTo(newView, 'revert', _.bind(self.render, self));
self.listenTo(gradeModel, 'change', function() {
self.showNotificationBar();
});
}); });
// render the grade cutoffs // render the grade cutoffs
...@@ -89,7 +91,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -89,7 +91,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
addAssignmentType : function(e) { addAssignmentType : function(e) {
e.preventDefault(); e.preventDefault();
this.model.get('graders').push({}); this.model.get('graders').push({});
this.showNotificationBar();
}, },
fieldToSelectorMap : { fieldToSelectorMap : {
'grace_period' : 'course-grading-graceperiod' 'grace_period' : 'course-grading-graceperiod'
...@@ -99,7 +100,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -99,7 +100,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
self.clearValidationErrors(); self.clearValidationErrors();
var newVal = self.model.dateToGracePeriod($(event.currentTarget).timepicker('getTime')); var newVal = self.model.dateToGracePeriod($(event.currentTarget).timepicker('getTime'));
self.model.set('grace_period', newVal, {validate: true}); self.model.set('grace_period', newVal, {validate: true});
self.showNotificationBar();
}, },
updateModel : function(event) { updateModel : function(event) {
if (!this.selectorToField[event.currentTarget.id]) return; if (!this.selectorToField[event.currentTarget.id]) return;
...@@ -112,7 +112,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -112,7 +112,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
this.setField(event); this.setField(event);
break; break;
} }
this.showNotificationBar();
}, },
// Grade sliders attributes and methods // Grade sliders attributes and methods
...@@ -238,7 +237,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -238,7 +237,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
}, },
{}), {}),
{validate: true}); {validate: true});
this.showNotificationBar();
}, },
addNewGrade: function(e) { addNewGrade: function(e) {
...@@ -333,7 +331,8 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -333,7 +331,8 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
self.render(); self.render();
self.renderCutoffBar(); self.renderCutoffBar();
}, },
reset: true}); reset: true,
silent: true});
}, },
showNotificationBar: function() { showNotificationBar: function() {
// We always call showNotificationBar with the same args, just // We always call showNotificationBar with the same args, just
......
...@@ -45,7 +45,8 @@ CMS.Views.ValidatingView = Backbone.View.extend({ ...@@ -45,7 +45,8 @@ CMS.Views.ValidatingView = Backbone.View.extend({
this.clearValidationErrors(); this.clearValidationErrors();
var field = this.selectorToField[event.currentTarget.id]; var field = this.selectorToField[event.currentTarget.id];
var newVal = $(event.currentTarget).val(); var newVal = $(event.currentTarget).val();
this.model.set(field, newVal, {validate: true}); this.model.set(field, newVal);
this.model.isValid();
return newVal; return newVal;
}, },
// these should perhaps go into a superclass but lack of event hash inheritance demotivates me // these should perhaps go into a superclass but lack of event hash inheritance demotivates me
...@@ -68,9 +69,18 @@ CMS.Views.ValidatingView = Backbone.View.extend({ ...@@ -68,9 +69,18 @@ CMS.Views.ValidatingView = Backbone.View.extend({
}, },
showNotificationBar: function(message, primaryClick, secondaryClick) { showNotificationBar: function(message, primaryClick, secondaryClick) {
// Show a notification with message. primaryClick is called on
// pressing the save button, and secondaryClick (if it's
// passed, which it may not be) will be called on
// cancel. Takes care of hiding the notification bar at the
// appropriate times.
if(this.notificationBarShowing) { if(this.notificationBarShowing) {
return; return;
} }
// If we've already saved something, hide the alert.
if(this.saved) {
this.saved.hide();
}
var self = this; var self = this;
this.confirmation = new CMS.Views.Notification.Warning({ this.confirmation = new CMS.Views.Notification.Warning({
title: gettext("You've made some changes"), title: gettext("You've made some changes"),
...@@ -93,10 +103,6 @@ CMS.Views.ValidatingView = Backbone.View.extend({ ...@@ -93,10 +103,6 @@ CMS.Views.ValidatingView = Backbone.View.extend({
secondaryClick(); secondaryClick();
} }
self.model.clear({silent : true}); self.model.clear({silent : true});
/*self.model.fetch({
success : function() { self.render(); },
reset: true
});*/
self.confirmation.hide(); self.confirmation.hide();
self.notificationBarShowing = false; self.notificationBarShowing = false;
} }
...@@ -114,13 +120,23 @@ CMS.Views.ValidatingView = Backbone.View.extend({ ...@@ -114,13 +120,23 @@ CMS.Views.ValidatingView = Backbone.View.extend({
closeIcon: false closeIcon: false
}); });
this.saved.show(); this.saved.show();
$.smoothScroll({
offset: 0,
easing: 'swing',
speed: 1000
});
}, },
saveView: function() { saveView: function() {
var self = this; var self = this;
this.model.save({}, this.model.save(
{success: function() { {},
{
success: function() {
self.showSavedBar(); self.showSavedBar();
}}); },
silent: true
}
);
} }
}); });
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