336 lines
13 KiB
JavaScript
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()
|