Commit 7ed0002b by Don Mitchell

Details now has validation except for youtube ids which cause

crossdomain policy violations when i try to validate.
parent c0da6e42
......@@ -33,20 +33,57 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
return attributes;
},
validate: function(newattrs) {
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {};
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = "The course end date cannot be before the course start date.";
}
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
}
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
}
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
}
if (newattrs.intro_video && newattrs.intro_video != this.get('intro_video')) {
var videos = this.parse_videosource(newattrs.intro_video);
var vid_errors = new Array();
var cachethis = this;
for (var i=0; i<videos.length; i++) {
// doesn't call parseFloat or Number b/c they stop on first non parsable and return what they have
if (!isFinite(videos[i].speed)) vid_errors.push(videos[i].speed + " is not a valid speed.");
// can't use get from client to test if video exists b/c of CORS (crossbrowser get not allowed)
// GET "http://gdata.youtube.com/feeds/api/videos/" + videokey
}
if (!_.isEmpty(vid_errors)) {
errors.intro_video = vid_errors.join('/n');
}
}
if (!_.isEmpty(errors)) return errors;
// NOTE don't return empty errors as that will be interpreted as an error state
},
urlRoot: function() {
var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details';
},
_videoprefix : /\s*<video\s*youtube="/g,
_videospeedparse : /\d+\.?\d*(?=:)/g,
// the below is lax to enable validation
_videospeedparse : /[^:]*/g, // /\d+\.?\d*(?=:)/g,
_videokeyparse : /([^,\/]+)/g,
_videonosuffix : /[^\"]+/g,
_getNextMatch : function (regex, string, cursor) {
regex.lastIndex = cursor;
return regex.exec(string);
var result = regex.exec(string);
if (_.isArray(result)) return result[0];
else return result;
},
// the whole string for editing
// the whole string for editing (put in edit box)
getVideoSource: function() {
if (this.get('intro_video')) {
var cursor = 0;
......@@ -95,10 +132,32 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
else return "";
}
},
parse_videosource: function(videostring) {
// used to validate before set so cannot get from model attr. Returns [{ speed: fff, key: sss }]
var cursor = 0;
this._getNextMatch(this._videoprefix, videostring, cursor);
cursor = this._videoprefix.lastIndex;
videostring = this._getNextMatch(this._videonosuffix, videostring, cursor);
cursor = 0;
// parsed to "fff:kkk,fff:kkk"
var result = new Array();
while (cursor < videostring.length) {
var speed = this._getNextMatch(this._videospeedparse, videostring, cursor);
if (speed) cursor = this._videospeedparse.lastIndex + 1;
else return result;
var key = this._getNextMatch(this._videokeyparse, videostring, cursor);
cursor = this._videokeyparse.lastIndex + 1;
// See the WTF above
if (_.isArray(key)) key = key[0];
result.push({speed: speed, key: key});
}
return result;
},
save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1
if (newsource == null) this.save({'intro_video': null});
// TODO remove all whitespace w/in string
else if (this._getNextMatch(this._videoprefix, newsource, 0)) this.save('intro_video', newsource);
else this.save('intro_video', '<video youtube="' + newsource + '"/>');
......
......@@ -87,43 +87,85 @@ CMS.Views.Settings.Details = Backbone.View.extend({
initialize : function() {
// TODO move the html frag to a loaded asset
this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="ss-icon ss-standard">&#x1F4C4;</i><%= filename %></a>');
this.errorTemplate = _.template('<span class="message-error"><%= message %></span>');
this.model.on('error', this.handleValidationError, this);
},
render: function() {
this.setupDatePicker('#course-start', 'start_date');
this.setupDatePicker('#course-end', 'end_date');
this.setupDatePicker('#enrollment-start', 'enrollment_start');
this.setupDatePicker('#enrollment-end', 'enrollment_end');
this.setupDatePicker('start_date')
this.setupDatePicker('end_date')
this.setupDatePicker('enrollment_start')
this.setupDatePicker('enrollment_end')
if (this.model.has('syllabus')) {
this.$el.find('.current-course-syllabus .doc-filename').html(
this.$el.find(this.fieldToSelectorMap['syllabus']).html(
this.fileAnchorTemplate({
fullpath : this.model.get('syllabus'),
filename: 'syllabus'}));
this.$el.find('.remove-course-syllabus').show();
}
else {
this.$el.find('.current-course-syllabus .doc-filename').html("");
this.$el.find(this.fieldToSelectorMap['syllabus']).html("");
this.$el.find('.remove-course-syllabus').hide();
}
this.$el.find('#course-overview').val(this.model.get('overview'));
this.$el.find(this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
if (this.model.has('intro_video')) {
this.$el.find('.remove-course-introduction-video').show();
this.$el.find('#course-introduction-video').val(this.model.getVideoSource());
this.$el.find(this.fieldToSelectorMap['intro_video']).val(this.model.getVideoSource());
}
else this.$el.find('.remove-course-introduction-video').hide();
this.$el.find("#course-effort").val(this.model.get('effort'));
this.$el.find(this.fieldToSelectorMap['effort']).val(this.model.get('effort'));
return this;
},
fieldToSelectorMap : {
'start_date' : "#course-start",
'end_date' : '#course-end',
'enrollment_start' : '#enrollment-start',
'enrollment_end' : '#enrollment-end',
'syllabus' : '.current-course-syllabus .doc-filename',
'overview' : '#course-overview',
'intro_video' : '#course-introduction-video',
'effort' : "#course-effort"
},
_cacheValidationErrors : null,
handleValidationError : function(model, error) {
this._cacheValidationErrors = error;
// error is object w/ fields and error strings
for (var field in error) {
var ele = this.$el.find(this.fieldToSelectorMap[field]);
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').addClass('error');
}
else $(ele).addClass('error');
$(ele).parent().append(this.errorTemplate({message : error[field]}));
}
},
setupDatePicker : function(elementName, fieldName) {
clearValidationErrors : function() {
if (this._cacheValidationErrors == null) return;
// error is object w/ fields and error strings
for (var field in this._cacheValidationErrors) {
var ele = this.$el.find(this.fieldToSelectorMap[field]);
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').removeClass('error');
}
else $(ele).removeClass('error');
$(ele).nextAll('.message-error').remove();
}
this._cacheValidationErrors = null;
},
setupDatePicker : function(fieldName) {
var cacheModel = this.model;
var div = this.$el.find(elementName);
var div = this.$el.find(this.fieldToSelectorMap[fieldName]);
var datefield = $(div).find(".date");
var timefield = $(div).find(".time");
var savefield = function() {
......@@ -143,7 +185,8 @@ CMS.Views.Settings.Details = Backbone.View.extend({
},
updateModel: function(event) {
// figure out which field
this.clearValidationErrors();
switch (event.currentTarget.id) {
case 'course-start-date': // handled via onSelect method
case 'course-end-date':
......@@ -181,7 +224,7 @@ CMS.Views.Settings.Details = Backbone.View.extend({
if (this.model.has('intro_video')) {
this.model.save_videosource(null);
this.$el.find(".current-course-introduction-video iframe").attr("src", "");
this.$el.find('#course-introduction-video').val("");
this.$el.find(this.fieldToSelectorMap['intro_video']).val("");
}
}
......
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