Files
edx-platform/cms/static/js/views/course_info_update.js
2023-05-09 13:53:54 +05:00

336 lines
13 KiB
JavaScript

define(['codemirror',
'js/utils/modal',
'js/utils/date_utils',
'edx-ui-toolkit/js/utils/html-utils',
'js/views/course_info_helper',
'js/views/validation',
'js/models/course_update',
'common/js/components/views/feedback_prompt',
'common/js/components/views/feedback_notification'],
function(CodeMirror, ModalUtils, DateUtils, HtmlUtils, CourseInfoHelper, ValidatingView, CourseUpdateModel,
PromptView, NotificationView) {
'use strict';
var CourseInfoUpdateView = ValidatingView.extend({
// collection is CourseUpdateCollection
events: {
'click .new-update-button': 'onNew',
'click .save-button': 'onSave',
'click .cancel-button': 'onCancel',
'click .post-actions > .edit-button': 'onEdit',
'click .post-actions > .delete-button': 'onDelete'
},
initialize: function() {
this.template = this.loadTemplate('course_info_update');
// 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 updateList = this.$el.find('#course-update-list'),
self = this;
$(updateList).empty();
if (this.collection.length > 0) {
this.collection.each(function(update, index) {
try {
CourseInfoHelper.changeContentToPreview(
update, 'content', self.options.base_asset_url);
HtmlUtils.append(
updateList,
HtmlUtils.HTML(self.template({updateModel: update}))
);
DateUtils.setupDatePicker('date', self, index);
update.isValid();
} catch (e) {
// ignore
} finally {
if (index === self.collection.length - 1) {
// Once the collection is loaded enable the button.
self.$el.find('.new-update-button').removeAttr('disabled');
}
}
});
} else {
// If the collection is empty enable the New update button
self.$el.find('.new-update-button').removeAttr('disabled');
}
// Hide Update forms that are not for new updates with the editing class
updateList.children().each(function(index, updateElement) {
var $updateElement = $(updateElement);
var updateForm = $updateElement.find('.new-update-form');
if ($updateElement.length > 0 && !$updateElement.hasClass('editing')) {
$(updateForm).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 self = this,
$validationElement = this.$el.find('#course-update-list li[name="' + model.cid + '"]');
$validationElement.find('.message-error').remove();
Object.keys(error).forEach(function(field) {
if (error.hasOwnProperty(field)) {
HtmlUtils.append(
$validationElement.find('#update-date-' + model.cid).parent(),
self.errorTemplate({message: error[field]})
);
HtmlUtils.append(
$validationElement.find('.date-display').parent(),
self.errorTemplate({message: error[field]})
);
}
});
$validationElement.find('.save-button').addClass('is-disabled');
},
validateModel: function(model) {
var $validationElement = this.$el.find('#course-update-list li[name="' + model.cid + '"]');
if (model.isValid()) {
$validationElement.find('.message-error').remove();
$validationElement.find('.save-button').removeClass('is-disabled');
}
},
onNew: function(event) {
// create new obj, insert into collection, and render this one ele overriding the hidden attr
var newModel = new CourseUpdateModel();
event.preventDefault();
this.collection.add(newModel, {at: 0});
var $newForm = $(
this.template({
updateModel: newModel
})
);
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 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()
});
},
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() {
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 content,
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').text(targetModel.get('date'));
this.$currentPost.find('.date').val(targetModel.get('date'));
content = HtmlUtils.HTML(CourseInfoHelper.changeContentToPreview(
targetModel, 'content', this.options.base_asset_url
));
try {
// just in case the content causes an error (embedded js errors)
HtmlUtils.setHtml(this.$currentPost.find('.update-contents'), 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();
}
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();
}
}
});
return CourseInfoUpdateView;
}); // end define()