Merge branch 'master' into feature/christina/metadata-ui

Conflicts:
	cms/envs/common.py
	cms/templates/base.html
	common/lib/xmodule/xmodule/combined_open_ended_module.py
This commit is contained in:
cahrens
2013-05-23 11:41:12 -04:00
138 changed files with 7554 additions and 1065 deletions

View File

@@ -140,11 +140,6 @@ $(document).ready(function() {
$('.new-course-button').bind('click', addNewCourse);
// section name editing
$('.section-name').bind('click', editSectionName);
$('.edit-section-name-cancel').bind('click', cancelEditSectionName);
// $('.edit-section-name-save').bind('click', saveEditSectionName);
// section date setting
$('.set-publish-date').bind('click', setSectionScheduleDate);
$('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate);
@@ -209,8 +204,8 @@ function toggleSections(e) {
$section = $('.courseware-section');
sectionCount = $section.length;
$button = $(this);
$labelCollapsed = $('<i class="ss-icon ss-symbolicons-block">up</i> <span class="label">Collapse All Sections</span>');
$labelExpanded = $('<i class="ss-icon ss-symbolicons-block">down</i> <span class="label">Expand All Sections</span>');
$labelCollapsed = $('<i class="icon-arrow-up"></i> <span class="label">Collapse All Sections</span>');
$labelExpanded = $('<i class="icon-arrow-down"></i> <span class="label">Expand All Sections</span>');
var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
$button.toggleClass('is-activated').html(buttonLabel);
@@ -763,72 +758,6 @@ function cancelNewSubsection(e) {
$(this).parents('li.branch').remove();
}
function editSectionName(e) {
e.preventDefault();
$(this).unbind('click', editSectionName);
$(this).children('.section-name-edit').show();
$(this).find('.edit-section-name').focus();
$(this).children('.section-name-span').hide();
$(this).find('.section-name-edit').bind('submit', saveEditSectionName);
$(this).find('.edit-section-name-cancel').bind('click', cancelNewSection);
$body.bind('keyup', {
$cancelButton: $(this).find('.edit-section-name-cancel')
}, checkForCancel);
}
function cancelEditSectionName(e) {
e.preventDefault();
$(this).parent().hide();
$(this).parent().siblings('.section-name-span').show();
$(this).closest('.section-name').bind('click', editSectionName);
e.stopPropagation();
}
function saveEditSectionName(e) {
e.preventDefault();
$(this).closest('.section-name').unbind('click', editSectionName);
var id = $(this).closest('.courseware-section').data('id');
var display_name = $.trim($(this).find('.edit-section-name').val());
$(this).closest('.courseware-section .section-name').append($spinner);
$spinner.show();
if (display_name == '') {
alert("You must specify a name before saving.");
return;
}
analytics.track('Edited Section Name', {
'course': course_location_analytics,
'display_name': display_name,
'id': id
});
var $_this = $(this);
// call into server to commit the new order
$.ajax({
url: "/save_item",
type: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
'id': id,
'metadata': {
'display_name': display_name
}
})
}).success(function() {
$spinner.delay(250).fadeOut(250);
$_this.closest('h3').find('.section-name-span').html(display_name).show();
$_this.hide();
$_this.closest('.section-name').bind('click', editSectionName);
e.stopPropagation();
});
}
function setSectionScheduleDate(e) {
e.preventDefault();
$(this).closest("h4").hide();

View File

@@ -0,0 +1,49 @@
CMS.Models.SystemFeedback = Backbone.Model.extend({
defaults: {
"intent": null, // "warning", "confirmation", "error", "announcement", "step-required", etc
"title": "",
"message": ""
/* could also have an "actions" hash: here is an example demonstrating
the expected structure
"actions": {
"primary": {
"text": "Save",
"class": "action-save",
"click": function() {
// do something when Save is clicked
// `this` refers to the model
}
},
"secondary": [
{
"text": "Cancel",
"class": "action-cancel",
"click": function() {}
}, {
"text": "Discard Changes",
"class": "action-discard",
"click": function() {}
}
]
}
*/
}
});
CMS.Models.WarningMessage = CMS.Models.SystemFeedback.extend({
defaults: $.extend({}, CMS.Models.SystemFeedback.prototype.defaults, {
"intent": "warning"
})
});
CMS.Models.ErrorMessage = CMS.Models.SystemFeedback.extend({
defaults: $.extend({}, CMS.Models.SystemFeedback.prototype.defaults, {
"intent": "error"
})
});
CMS.Models.ConfirmationMessage = CMS.Models.SystemFeedback.extend({
defaults: $.extend({}, CMS.Models.SystemFeedback.prototype.defaults, {
"intent": "confirmation"
})
});

View File

@@ -0,0 +1,43 @@
CMS.Models.Section = Backbone.Model.extend({
defaults: {
"name": ""
},
validate: function(attrs, options) {
if (!attrs.name) {
return gettext("You must specify a name");
}
},
url: "/save_item",
toJSON: function() {
return {
id: this.get("id"),
metadata: {
display_name: this.get("name")
}
};
},
initialize: function() {
this.listenTo(this, "request", this.showNotification);
this.listenTo(this, "sync", this.hideNotification);
},
showNotification: function() {
if(!this.msg) {
this.msg = new CMS.Models.SystemFeedback({
intent: "saving",
title: gettext("Saving&hellip;")
});
}
if(!this.msgView) {
this.msgView = new CMS.Views.Notification({
model: this.msg,
closeIcon: false,
minShown: 1250
});
}
this.msgView.show();
},
hideNotification: function() {
if(!this.msgView) { return; }
this.msgView.hide();
}
});

View File

@@ -40,7 +40,6 @@ CMS.Models.Settings.Advanced = Backbone.Model.extend({
// data
data : JSON.stringify({ deleteKeys : self.deleteKeys})
})
.fail(function(hdr, status, error) { CMS.ServerError(self, "Deleting keys:" + status); })
.done(function(data, status, error) {
// clear deleteKeys on success
self.deleteKeys = [];

View File

@@ -22,8 +22,7 @@ CMS.Views.Checklists = Backbone.View.extend({
}
);
},
reset: true,
error: CMS.ServerError
reset: true
}
);
},
@@ -90,8 +89,7 @@ CMS.Views.Checklists = Backbone.View.extend({
'task': model.attributes.items[task_index].short_description,
'state': model.attributes.items[task_index].is_checked
});
},
error : CMS.ServerError
}
});
}
});

View File

@@ -105,7 +105,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
var targetModel = this.eventModel(event);
targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() });
// push change to display, hide the editor, submit the change
targetModel.save({}, {error : CMS.ServerError});
targetModel.save({});
this.closeEditor(this);
analytics.track('Saved Course Update', {
@@ -166,11 +166,9 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
success: function() {
cacheThis.render();
},
reset: true,
error: CMS.ServerError
reset: true
});
},
error : CMS.ServerError
}
});
},
@@ -254,8 +252,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
}
);
},
reset: true,
error: CMS.ServerError
reset: true
});
},
@@ -296,7 +293,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
onSave: function(event) {
this.model.set('data', this.$codeMirror.getValue());
this.render();
this.model.save({}, {error: CMS.ServerError});
this.model.save({});
this.$form.hide();
this.closeEditor(this);

View File

@@ -0,0 +1,103 @@
CMS.Views.Alert = Backbone.View.extend({
options: {
type: "alert",
shown: true, // is this view currently being shown?
icon: true, // should we render an icon related to the message intent?
closeIcon: true, // should we render a close button in the top right corner?
minShown: 0, // length of time after this view has been shown before it can be hidden (milliseconds)
maxShown: Infinity // length of time after this view has been shown before it will be automatically hidden (milliseconds)
},
initialize: function() {
var tpl = $("#system-feedback-tpl").text();
if(!tpl) {
console.error("Couldn't load system-feedback template");
}
this.template = _.template(tpl);
this.setElement($("#page-"+this.options.type));
this.listenTo(this.model, 'change', this.render);
return this.show();
},
render: function() {
var attrs = $.extend({}, this.options, this.model.attributes);
this.$el.html(this.template(attrs));
return this;
},
events: {
"click .action-close": "hide",
"click .action-primary": "primaryClick",
"click .action-secondary": "secondaryClick"
},
show: function() {
clearTimeout(this.hideTimeout);
this.options.shown = true;
this.shownAt = new Date();
this.render();
if($.isNumeric(this.options.maxShown)) {
this.hideTimeout = setTimeout($.proxy(this.hide, this),
this.options.maxShown);
}
return this;
},
hide: function() {
if(this.shownAt && $.isNumeric(this.options.minShown) &&
this.options.minShown > new Date() - this.shownAt)
{
clearTimeout(this.hideTimeout);
this.hideTimeout = setTimeout($.proxy(this.hide, this),
this.options.minShown - (new Date() - this.shownAt));
} else {
this.options.shown = false;
delete this.shownAt;
this.render();
}
return this;
},
primaryClick: function() {
var actions = this.model.get("actions");
if(!actions) { return; }
var primary = actions.primary;
if(!primary) { return; }
if(primary.click) {
primary.click.call(this.model, this);
}
},
secondaryClick: function(e) {
var actions = this.model.get("actions");
if(!actions) { return; }
var secondaryList = actions.secondary;
if(!secondaryList) { return; }
// which secondary action was clicked?
var i = 0; // default to the first secondary action (easier for testing)
if(e && e.target) {
i = _.indexOf(this.$(".action-secondary"), e.target);
}
var secondary = this.model.get("actions").secondary[i];
if(secondary.click) {
secondary.click.call(this.model, this);
}
}
});
CMS.Views.Notification = CMS.Views.Alert.extend({
options: $.extend({}, CMS.Views.Alert.prototype.options, {
type: "notification",
closeIcon: false
})
});
CMS.Views.Prompt = CMS.Views.Alert.extend({
options: $.extend({}, CMS.Views.Alert.prototype.options, {
type: "prompt",
closeIcon: false,
icon: false
}),
render: function() {
if(!window.$body) { window.$body = $(document.body); }
if(this.options.shown) {
$body.addClass('prompt-is-shown');
} else {
$body.removeClass('prompt-is-shown');
}
// super() in Javascript has awkward syntax :(
return CMS.Views.Alert.prototype.render.apply(this, arguments);
}
});

View File

@@ -35,7 +35,7 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({
// TODO move to a template file
'<h4 class="status-label"><%= assignmentType %></h4>' +
'<a data-tooltip="Mark/unmark this subsection as graded" class="menu-toggle" href="#">' +
'<% if (!hideSymbol) {%><span class="ss-icon ss-standard">&#x2713;</span><%};%>' +
'<% if (!hideSymbol) {%><i class="icon-ok"></i><%};%>' +
'</a>' +
'<ul class="menu">' +
'<% graders.each(function(option) { %>' +

View File

@@ -0,0 +1,85 @@
CMS.Views.SectionShow = Backbone.View.extend({
template: _.template('<span data-tooltip="<%= gettext("Edit this section\'s name") %>" class="section-name-span"><%= name %></span>'),
render: function() {
var attrs = {
name: this.model.escape('name')
};
this.$el.html(this.template(attrs));
this.delegateEvents();
return this;
},
events: {
"click": "switchToEditView"
},
switchToEditView: function() {
if(!this.editView) {
this.editView = new CMS.Views.SectionEdit({
model: this.model, el: this.el, showView: this});
}
this.undelegateEvents();
this.editView.render();
}
});
CMS.Views.SectionEdit = Backbone.View.extend({
render: function() {
var attrs = {
name: this.model.escape('name')
};
this.$el.html(this.template(attrs));
this.delegateEvents();
return this;
},
initialize: function() {
this.template = _.template($("#section-name-edit-tpl").text());
this.listenTo(this.model, "invalid", this.showInvalidMessage);
this.render();
},
events: {
"click .save-button": "saveName",
"submit": "saveName",
"click .cancel-button": "switchToShowView"
},
saveName: function(e) {
if (e) { e.preventDefault(); }
var name = this.$("input[type=text]").val();
var that = this;
this.model.save("name", name, {
success: function() {
analytics.track('Edited Section Name', {
'course': course_location_analytics,
'display_name': that.model.get('name'),
'id': that.model.get('id')
});
that.switchToShowView();
}
});
},
switchToShowView: function() {
if(!this.showView) {
this.showView = new CMS.Views.SectionShow({
model: this.model, el: this.el, editView: this});
}
this.undelegateEvents();
this.stopListening();
this.showView.render();
},
showInvalidMessage: function(model, error, options) {
model.set("name", model.previous("name"));
var that = this;
var msg = new CMS.Models.ErrorMessage({
title: gettext("Your change could not be saved"),
message: error,
actions: {
primary: {
text: gettext("Return and resolve this issue"),
click: function(view) {
view.hide();
that.$("input[type=text]").focus();
}
}
}
});
new CMS.Views.Prompt({model: msg});
}
});

View File

@@ -1,4 +0,0 @@
CMS.ServerError = function(model, error) {
// this handler is for the client:server communication not the validation errors which handleValidationError catches
window.alert("Server Error: " + error.responseText);
};

View File

@@ -23,7 +23,6 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
// because these are outside of this.$el, they can't be in the event hash
$('.save-button').on('click', this, this.saveView);
$('.cancel-button').on('click', this, this.revertView);
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
},
render: function() {
@@ -144,8 +143,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
'course': course_location_analytics
});
},
error : CMS.ServerError
}
});
},
revertView : function(event) {
@@ -155,8 +153,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
self.model.clear({silent : true});
self.model.fetch({
success : function() { self.render(); },
reset: true,
error : CMS.ServerError
reset: true
});
},
renderTemplate: function (key, value) {

View File

@@ -16,7 +16,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
},
initialize : function() {
this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="ss-icon ss-standard">&#x1F4C4;</i><%= filename %></a>');
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'));
@@ -26,7 +26,6 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
var dateIntrospect = new Date();
this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},

View File

@@ -44,7 +44,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
self.render();
}
);
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.model.get('graders').on('remove', this.render, this);
this.model.get('graders').on('reset', this.render, this);
@@ -65,7 +64,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
gradeCollection.each(function(gradeModel) {
$(gradelist).append(self.template({model : gradeModel }));
var newEle = gradelist.children().last();
var newView = new CMS.Views.Settings.GraderView({el: newEle,
var newView = new CMS.Views.Settings.GraderView({el: newEle,
model : gradeModel, collection : gradeCollection });
});
@@ -119,7 +118,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
gradeBarWidth : null, // cache of value since it won't change (more certain)
renderCutoffBar: function() {
var gradeBar =this.$el.find('.grade-bar');
var gradeBar =this.$el.find('.grade-bar');
this.gradeBarWidth = gradeBar.width();
var gradelist = gradeBar.children('.grades');
// HACK fixing a duplicate call issue by undoing previous call effect. Need to figure out why called 2x
@@ -128,11 +127,11 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// Can probably be simplified to one variable now.
var removable = false;
var draggable = false; // first and last are not removable, first is not draggable
_.each(this.descendingCutoffs,
_.each(this.descendingCutoffs,
function(cutoff, index) {
var newBar = this.gradeCutoffTemplate({
descriptor : cutoff['designation'] ,
width : nextWidth,
var newBar = this.gradeCutoffTemplate({
descriptor : cutoff['designation'] ,
width : nextWidth,
removable : removable });
gradelist.append(newBar);
if (draggable) {
@@ -152,7 +151,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
},
this);
// add fail which is not in data
var failBar = this.gradeCutoffTemplate({ descriptor : this.failLabel(),
var failBar = this.gradeCutoffTemplate({ descriptor : this.failLabel(),
width : nextWidth, removable : false});
$(failBar).find("span[contenteditable=true]").attr("contenteditable", false);
gradelist.append(failBar);
@@ -221,12 +220,12 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
},
saveCutoffs: function() {
this.model.save('grade_cutoffs',
_.reduce(this.descendingCutoffs,
function(object, cutoff) {
this.model.save('grade_cutoffs',
_.reduce(this.descendingCutoffs,
function(object, cutoff) {
object[cutoff['designation']] = cutoff['cutoff'] / 100.0;
return object;
},
},
{}));
},
@@ -244,7 +243,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
this.descendingCutoffs.push({designation: this.GRADES[gradeLength], cutoff: failBarWidth});
this.descendingCutoffs[gradeLength - 1]['cutoff'] = Math.round(targetWidth);
var $newGradeBar = this.gradeCutoffTemplate({ descriptor : this.GRADES[gradeLength],
var $newGradeBar = this.gradeCutoffTemplate({ descriptor : this.GRADES[gradeLength],
width : targetWidth, removable : true });
var gradeDom = this.$el.find('.grades');
gradeDom.children().last().before($newGradeBar);
@@ -317,7 +316,6 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
'blur :input' : "inputUnfocus"
},
initialize : function() {
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap);
this.render();
@@ -362,9 +360,8 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
}
},
deleteModel : function(e) {
this.model.destroy(
{ error : CMS.ServerError});
this.model.destroy();
e.preventDefault();
}
});
});

View File

@@ -3,7 +3,6 @@ CMS.Views.ValidatingView = Backbone.View.extend({
// decorates the fields. Needs wiring per class, but this initialization shows how
// either have your init call this one or copy the contents
initialize : function() {
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},