Files
edx-platform/cms/static/js/views/course_info_update.js
Usman Khalid 78af4100cd Fixes for course updates date validation.
If the first date the user selects is invalid it is not set on the
model. This is because jQuery datepicker's getDate returns the
current date when an invalid value is put in the field and the
model's initial value is also the current date. So always update
the date.
2016-05-03 15:41:22 +05:00

325 lines
13 KiB
JavaScript

define(["js/views/validation", "codemirror", "js/models/course_update",
"common/js/components/views/feedback_prompt", "common/js/components/views/feedback_notification",
"js/views/course_info_helper", "js/utils/modal", "js/utils/date_utils"],
function(ValidatingView, CodeMirror, CourseUpdateModel, PromptView, NotificationView,
CourseInfoHelper, ModalUtils, DateUtils) {
'use strict';
var CourseInfoUpdateView = ValidatingView.extend({
// collection is CourseUpdateCollection
events: {
"click .new-update-button" : "onNew",
"click #course-update-view .save-button" : "onSave",
"click #course-update-view .cancel-button" : "onCancel",
"click .post-actions > .edit-button" : "onEdit",
"click .post-actions > .delete-button" : "onDelete"
},
initialize: function() {
this.template = this.loadTemplate('course_info_update');
this.render();
// when the client refetches the updates as a whole, re-render them
this.listenTo(this.collection, 'reset', this.render);
this.listenTo(this.collection, 'invalid', this.handleValidationError);
},
render: function () {
// iterate over updates and create views for each using the template
var updateEle = this.$el.find("#course-update-list");
// remove and then add all children
$(updateEle).empty();
var self = this;
this.collection.each(function (update, index) {
try {
CourseInfoHelper.changeContentToPreview(
update, 'content', self.options['base_asset_url']);
// push notification is always disabled for existing updates
var newEle = self.template({ updateModel : update, push_notification_enabled : false });
$(updateEle).append(newEle);
DateUtils.setupDatePicker("date", self, index);
update.isValid();
} catch (e) {
// ignore
}
});
this.$el.find(".new-update-form").hide();
return this;
},
collectionSelector: function(uid) {
return "course-update-list li[name=" + uid + "]";
},
setAndValidate: function(attr, value, event) {
if (attr === 'date') {
// If the value to be set was typed, validate that entry rather than the current datepicker value
if (this.dateEntry(event).length > 0) {
value = DateUtils.parseDateFromString(this.dateEntry(event).val());
if (value && isNaN(value.getTime())) {
value = "";
}
}
value = $.datepicker.formatDate("MM d, yy", value);
}
var targetModel = this.collection.get(this.$currentPost.attr('name'));
var prevValue = targetModel.get(attr);
if (prevValue !== value) {
targetModel.set(attr, value);
this.validateModel(targetModel);
}
},
handleValidationError : function(model, error) {
var ele = this.$el.find('#course-update-list li[name=\"'+model.cid+'\"]');
$(ele).find('.message-error').remove();
for (var field in error) {
if (error.hasOwnProperty(field)) {
$(ele).find('#update-date-'+model.cid).parent().append(
this.errorTemplate({message : error[field]})
);
$(ele).find('.date-display').parent().append(this.errorTemplate({message : error[field]}));
}
}
$(ele).find('.save-button').addClass('is-disabled');
},
validateModel: function(model) {
if (model.isValid()) {
var ele = this.$el.find('#course-update-list li[name=\"' + model.cid + '\"]');
$(ele).find('.message-error').remove();
$(ele).find('.save-button').removeClass('is-disabled');
}
},
onNew: function(event) {
event.preventDefault();
var self = this;
// create new obj, insert into collection, and render this one ele overriding the hidden attr
var newModel = new CourseUpdateModel();
this.collection.add(newModel, {at : 0});
var $newForm = $(
this.template({
updateModel : newModel,
push_notification_enabled : this.options.push_notification_enabled
})
);
var updateEle = this.$el.find("#course-update-list");
$(updateEle).prepend($newForm);
var $textArea = $newForm.find(".new-update-content").first();
this.$codeMirror = CodeMirror.fromTextArea($textArea.get(0), {
mode: "text/html",
lineNumbers: true,
lineWrapping: true
});
$newForm.addClass('editing');
this.$currentPost = $newForm.closest('li');
// Variable stored for unit test.
this.$modalCover = ModalUtils.showModalCover(false, function() {
// Binding empty function to prevent default hideModal.
});
DateUtils.setupDatePicker("date", this, 0);
},
onSave: function(event) {
event.preventDefault();
var targetModel = this.eventModel(event);
targetModel.set({
// translate short-form date (for input) into long form date (for display)
date : $.datepicker.formatDate("MM d, yy", new Date(this.dateEntry(event).val())),
content : this.$codeMirror.getValue(),
push_notification_selected : this.push_notification_selected(event)
});
// push change to display, hide the editor, submit the change
var saving = new NotificationView.Mini({
title: gettext('Saving')
});
saving.show();
var ele = this.modelDom(event);
targetModel.save({}, {
success: function() {
saving.hide();
},
error: function() {
ele.remove();
}
});
this.closeEditor(false);
analytics.track('Saved Course Update', {
'course': course_location_analytics,
'date': this.dateEntry(event).val(),
'push_notification_selected': this.push_notification_selected(event)
});
},
onCancel: function(event) {
event.preventDefault();
// Since we're cancelling, the model should be using it's previous attributes
var targetModel = this.eventModel(event);
targetModel.set(targetModel.previousAttributes());
this.validateModel(targetModel);
// Hide the editor
$(this.editor(event)).hide();
// targetModel will be lacking an id if it was newly created
this.closeEditor(!targetModel.id);
},
onEdit: function(event) {
event.preventDefault();
var self = this;
this.$currentPost = $(event.target).closest('li');
this.$currentPost.addClass('editing');
$(this.editor(event)).show();
var $textArea = this.$currentPost.find(".new-update-content").first();
var targetModel = this.eventModel(event);
// translate long-form date (for viewing) into short-form date (for input)
if (targetModel.get('date') && targetModel.isValid()) {
$(this.dateEntry(event)).val($.datepicker.formatDate("mm/dd/yy", new Date(targetModel.get('date'))));
}
else {
$(this.dateEntry(event)).val("MM/DD/YY");
}
this.$codeMirror = CourseInfoHelper.editWithCodeMirror(
targetModel, 'content', self.options['base_asset_url'], $textArea.get(0));
// Variable stored for unit test.
this.$modalCover = ModalUtils.showModalCover(false,
function() {
self.closeEditor(false);
}
);
// Ensure validity is marked appropriately
targetModel.isValid();
},
onDelete: function(event) {
event.preventDefault();
var self = this;
var targetModel = this.eventModel(event);
var confirm = new PromptView.Warning({
title: gettext('Are you sure you want to delete this update?'),
message: gettext('This action cannot be undone.'),
actions: {
primary: {
text: gettext('OK'),
click: function () {
analytics.track('Deleted Course Update', {
'course': course_location_analytics,
'date': self.dateEntry(event).val()
});
self.modelDom(event).remove();
var deleting = new NotificationView.Mini({
title: gettext('Deleting')
});
deleting.show();
targetModel.destroy({
success: function (model, response) {
self.collection.fetch({
success: function() {
self.render();
deleting.hide();
},
reset: true
});
}
});
confirm.hide();
}
},
secondary: {
text: gettext('Cancel'),
click: function() {
confirm.hide();
}
}
}
});
confirm.show();
},
closeEditor: function(removePost) {
var targetModel = this.collection.get(this.$currentPost.attr('name'));
// If the model was never created (user created a new update, then pressed Cancel),
// we wish to remove it from the DOM.
if(removePost) {
this.$currentPost.remove();
}
else {
// close the modal and insert the appropriate data
this.$currentPost.removeClass('editing');
this.$currentPost.find('.date-display').html(targetModel.get('date'));
this.$currentPost.find('.date').val(targetModel.get('date'));
var content = CourseInfoHelper.changeContentToPreview(
targetModel, 'content', this.options['base_asset_url']);
try {
// just in case the content causes an error (embedded js errors)
this.$currentPost.find('.update-contents').html(content);
this.$currentPost.find('.new-update-content').val(content);
} catch (e) {
// ignore but handle rest of page
}
this.$currentPost.find('form').hide();
this.$currentPost.find('.CodeMirror').remove();
// hide the push notification checkbox for subsequent edits to the Post
var push_notification_ele = this.$currentPost.find(".new-update-push-notification");
if (push_notification_ele) {
push_notification_ele.hide();
}
}
ModalUtils.hideModalCover(this.$modalCover);
this.$codeMirror = null;
},
// Dereferencing from events to screen elements
eventModel: function(event) {
// not sure if it should be currentTarget or delegateTarget
return this.collection.get($(event.currentTarget).attr("name"));
},
modelDom: function(event) {
return $(event.currentTarget).closest("li");
},
editor: function(event) {
var li = $(event.currentTarget).closest("li");
if (li) {
return $(li).find("form").first();
}
},
dateEntry: function(event) {
var li = $(event.currentTarget).closest("li");
if (li) {
return $(li).find(".date").first();
}
},
push_notification_selected: function(event) {
var push_notification_checkbox;
var li = $(event.currentTarget).closest("li");
if (li) {
push_notification_checkbox = li.find(".new-update-push-notification .toggle-checkbox");
if (push_notification_checkbox) {
return push_notification_checkbox.is(":checked");
}
}
}
});
return CourseInfoUpdateView;
}); // end define()