The changeTime event isn't fired when the user types in the field, but only when clicking on the time in the dropdown. I'd consider this a timepicker bug, but for now we can just listen to the user typing in the field and update the value (and thus validate) like we do with other field types.
222 lines
9.3 KiB
JavaScript
222 lines
9.3 KiB
JavaScript
if (!CMS.Views['Settings']) CMS.Views.Settings = {};
|
|
|
|
CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
|
|
// Model class is CMS.Models.Settings.CourseDetails
|
|
events : {
|
|
"input input" : "updateModel",
|
|
"input textarea" : "updateModel",
|
|
// Leaving change in as fallback for older browsers
|
|
"change input" : "updateModel",
|
|
"change textarea" : "updateModel",
|
|
'click .remove-course-introduction-video' : "removeVideo",
|
|
'focus #course-overview' : "codeMirrorize",
|
|
'mouseover #timezone' : "updateTime",
|
|
// would love to move to a general superclass, but event hashes don't inherit in backbone :-(
|
|
'focus :input' : "inputFocus",
|
|
'blur :input' : "inputUnfocus"
|
|
|
|
},
|
|
|
|
initialize : function() {
|
|
this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="icon-file"></i><%= filename %></a>');
|
|
// fill in fields
|
|
this.$el.find("#course-name").val(this.model.get('location').get('name'));
|
|
this.$el.find("#course-organization").val(this.model.get('location').get('org'));
|
|
this.$el.find("#course-number").val(this.model.get('location').get('course'));
|
|
this.$el.find('.set-date').datepicker({ 'dateFormat': 'm/d/yy' });
|
|
|
|
var dateIntrospect = new Date();
|
|
this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
|
|
|
|
this.listenTo(this.model, 'invalid', this.handleValidationError);
|
|
this.listenTo(this.model, 'change', this.showNotificationBar);
|
|
this.selectorToField = _.invert(this.fieldToSelectorMap);
|
|
},
|
|
|
|
render: function() {
|
|
this.setupDatePicker('start_date');
|
|
this.setupDatePicker('end_date');
|
|
this.setupDatePicker('enrollment_start');
|
|
this.setupDatePicker('enrollment_end');
|
|
|
|
this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
|
|
this.codeMirrorize(null, $('#course-overview')[0]);
|
|
|
|
this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
|
|
this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video') || '');
|
|
if (this.model.has('intro_video')) {
|
|
this.$el.find('.remove-course-introduction-video').show();
|
|
}
|
|
else this.$el.find('.remove-course-introduction-video').hide();
|
|
|
|
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',
|
|
'overview' : 'course-overview',
|
|
'intro_video' : 'course-introduction-video',
|
|
'effort' : "course-effort"
|
|
},
|
|
|
|
updateTime : function(e) {
|
|
var now = new Date();
|
|
var hours = now.getHours();
|
|
var minutes = now.getMinutes();
|
|
$(e.currentTarget).attr('title', (hours % 12 === 0 ? 12 : hours % 12) + ":" + (minutes < 10 ? "0" : "") +
|
|
now.getMinutes() + (hours < 12 ? "am" : "pm") + " (current local time)");
|
|
},
|
|
|
|
setupDatePicker: function (fieldName) {
|
|
var cacheModel = this.model;
|
|
var div = this.$el.find('#' + this.fieldToSelectorMap[fieldName]);
|
|
var datefield = $(div).find("input:.date");
|
|
var timefield = $(div).find("input:.time");
|
|
var cachethis = this;
|
|
var setfield = function () {
|
|
var date = datefield.datepicker('getDate');
|
|
if (date) {
|
|
var time = timefield.timepicker("getSecondsFromMidnight");
|
|
if (!time) {
|
|
time = 0;
|
|
}
|
|
var newVal = new Date(date.getTime() + time * 1000);
|
|
if (!cacheModel.has(fieldName) || cacheModel.get(fieldName).getTime() !== newVal.getTime()) {
|
|
cachethis.clearValidationErrors();
|
|
cachethis.setAndValidate(fieldName, newVal);
|
|
}
|
|
}
|
|
else {
|
|
// 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
|
|
// (start date is required by the back end).
|
|
cachethis.clearValidationErrors();
|
|
cachethis.setAndValidate(fieldName, null);
|
|
}
|
|
};
|
|
|
|
// instrument as date and time pickers
|
|
timefield.timepicker({'timeFormat' : 'H:i'});
|
|
datefield.datepicker();
|
|
|
|
// Using the change event causes setfield to be triggered twice, but it is necessary
|
|
// to pick up when the date is typed directly in the field.
|
|
datefield.change(setfield);
|
|
timefield.on('changeTime', setfield);
|
|
timefield.on('input', setfield);
|
|
|
|
datefield.datepicker('setDate', this.model.get(fieldName));
|
|
// timepicker doesn't let us set null, so check that we have a time
|
|
if (this.model.has(fieldName)) {
|
|
timefield.timepicker('setTime', this.model.get(fieldName));
|
|
} // but reset the field either way
|
|
else {
|
|
timefield.val('');
|
|
}
|
|
},
|
|
|
|
updateModel: function(event) {
|
|
switch (event.currentTarget.id) {
|
|
case 'course-effort':
|
|
this.setField(event);
|
|
break;
|
|
// Don't make the user reload the page to check the Youtube ID.
|
|
// Wait for a second to load the video, avoiding egregious AJAX calls.
|
|
case 'course-introduction-video':
|
|
this.clearValidationErrors();
|
|
var previewsource = this.model.set_videosource($(event.currentTarget).val());
|
|
clearTimeout(this.videoTimer);
|
|
this.videoTimer = setTimeout(_.bind(function() {
|
|
this.$el.find(".current-course-introduction-video iframe").attr("src", previewsource);
|
|
if (this.model.has('intro_video')) {
|
|
this.$el.find('.remove-course-introduction-video').show();
|
|
}
|
|
else {
|
|
this.$el.find('.remove-course-introduction-video').hide();
|
|
}
|
|
}, this), 1000);
|
|
break;
|
|
default: // Everything else is handled by datepickers and CodeMirror.
|
|
break;
|
|
}
|
|
},
|
|
|
|
removeVideo: function(event) {
|
|
event.preventDefault();
|
|
if (this.model.has('intro_video')) {
|
|
this.model.set_videosource(null);
|
|
this.$el.find(".current-course-introduction-video iframe").attr("src", "");
|
|
this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val("");
|
|
this.$el.find('.remove-course-introduction-video').hide();
|
|
}
|
|
},
|
|
codeMirrors : {},
|
|
codeMirrorize: function (e, forcedTarget) {
|
|
var thisTarget;
|
|
if (forcedTarget) {
|
|
thisTarget = forcedTarget;
|
|
thisTarget.id = $(thisTarget).attr('id');
|
|
} else {
|
|
thisTarget = e.currentTarget;
|
|
}
|
|
|
|
if (!this.codeMirrors[thisTarget.id]) {
|
|
var cachethis = this;
|
|
var field = this.selectorToField[thisTarget.id];
|
|
this.codeMirrors[thisTarget.id] = CodeMirror.fromTextArea(thisTarget, {
|
|
mode: "text/html", lineNumbers: true, lineWrapping: true,
|
|
onChange: function (mirror) {
|
|
mirror.save();
|
|
cachethis.clearValidationErrors();
|
|
var newVal = mirror.getValue();
|
|
if (cachethis.model.get(field) != newVal) {
|
|
cachethis.setAndValidate(field, newVal);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
revertView: function() {
|
|
// Make sure that the CodeMirror instance has the correct
|
|
// data from its corresponding textarea
|
|
var self = this;
|
|
this.model.fetch({
|
|
success: function() {
|
|
self.render();
|
|
_.each(self.codeMirrors,
|
|
function(mirror) {
|
|
var ele = mirror.getTextArea();
|
|
var field = self.selectorToField[ele.id];
|
|
mirror.setValue(self.model.get(field));
|
|
});
|
|
},
|
|
reset: true,
|
|
silent: 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();
|
|
},
|
|
|
|
showNotificationBar: function() {
|
|
// We always call showNotificationBar with the same args, just
|
|
// delegate to superclass
|
|
CMS.Views.ValidatingView.prototype.showNotificationBar.call(this,
|
|
this.save_message,
|
|
_.bind(this.saveView, this),
|
|
_.bind(this.revertView, this));
|
|
}
|
|
});
|
|
|