STUD-1911, STUD-1932: Publish sections, subsections and units from course outline.
This commit is contained in:
@@ -59,7 +59,7 @@ function(Backbone, _, str, ModuleUtils) {
|
||||
*/
|
||||
"visibility_state": null,
|
||||
/**
|
||||
* True iff the release date of the xblock is in the past.
|
||||
* True if the release date of the xblock is in the past.
|
||||
*/
|
||||
'released_to_students': null,
|
||||
/**
|
||||
@@ -153,6 +153,10 @@ function(Backbone, _, str, ModuleUtils) {
|
||||
return childInfo && childInfo.children.length > 0;
|
||||
},
|
||||
|
||||
isPublishable: function(){
|
||||
return !this.get('published') || this.get('has_changes');
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a list of convenience methods to check affiliation to the category.
|
||||
* @return {Array}
|
||||
|
||||
@@ -5,14 +5,14 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
describe("CourseOutlinePage", function() {
|
||||
var createCourseOutlinePage, displayNameInput, model, outlinePage, requests,
|
||||
getItemsOfType, getItemHeaders, verifyItemsExpanded, expandItemsAndVerifyState, collapseItemsAndVerifyState,
|
||||
createMockCourseJSON, createMockSectionJSON, createMockSubsectionJSON,
|
||||
mockCourseJSON, mockEmptyCourseJSON, mockSingleSectionCourseJSON,
|
||||
createMockCourseJSON, createMockSectionJSON, createMockSubsectionJSON, verifyTypePublishable,
|
||||
mockCourseJSON, mockEmptyCourseJSON, mockSingleSectionCourseJSON, createMockVerticalJSON,
|
||||
mockOutlinePage = readFixtures('mock/mock-course-outline-page.underscore');
|
||||
|
||||
createMockCourseJSON = function(id, displayName, children) {
|
||||
return {
|
||||
id: id,
|
||||
display_name: displayName,
|
||||
createMockCourseJSON = function(options, children) {
|
||||
return $.extend(true, {}, {
|
||||
id: 'mock-course',
|
||||
display_name: 'Mock Course',
|
||||
category: 'course',
|
||||
studio_url: '/course/slashes:MockCourse',
|
||||
is_container: true,
|
||||
@@ -22,17 +22,18 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
edited_by: 'MockUser',
|
||||
has_explicit_staff_lock: false,
|
||||
child_info: {
|
||||
display_name: 'Section',
|
||||
category: 'chapter',
|
||||
children: children
|
||||
display_name: 'Section',
|
||||
children: []
|
||||
}
|
||||
};
|
||||
}, options, {child_info: {children: children}});
|
||||
};
|
||||
createMockSectionJSON = function(id, displayName, children) {
|
||||
return {
|
||||
id: id,
|
||||
|
||||
createMockSectionJSON = function(options, children) {
|
||||
return $.extend(true, {}, {
|
||||
id: 'mock-section',
|
||||
display_name: 'Mock Section',
|
||||
category: 'chapter',
|
||||
display_name: displayName,
|
||||
studio_url: '/course/slashes:MockCourse',
|
||||
is_container: true,
|
||||
has_changes: false,
|
||||
@@ -43,14 +44,15 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
child_info: {
|
||||
category: 'sequential',
|
||||
display_name: 'Subsection',
|
||||
children: children
|
||||
children: []
|
||||
}
|
||||
};
|
||||
}, options, {child_info: {children: children}});
|
||||
};
|
||||
createMockSubsectionJSON = function(id, displayName, children) {
|
||||
return {
|
||||
id: id,
|
||||
display_name: displayName,
|
||||
|
||||
createMockSubsectionJSON = function(options, children) {
|
||||
return $.extend(true, {}, {
|
||||
id: 'mock-subsection',
|
||||
display_name: 'Mock Subsection',
|
||||
category: 'sequential',
|
||||
studio_url: '/course/slashes:MockCourse',
|
||||
is_container: true,
|
||||
@@ -63,9 +65,24 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
child_info: {
|
||||
category: 'vertical',
|
||||
display_name: 'Unit',
|
||||
children: children
|
||||
children: []
|
||||
}
|
||||
};
|
||||
}, options, {child_info: {children: children}});
|
||||
};
|
||||
|
||||
createMockVerticalJSON = function(options) {
|
||||
return $.extend(true, {}, {
|
||||
id: 'mock-unit',
|
||||
display_name: 'Mock Unit',
|
||||
category: 'vertical',
|
||||
studio_url: '/container/mock-unit',
|
||||
is_container: true,
|
||||
has_changes: false,
|
||||
published: true,
|
||||
visibility_state: 'unscheduled',
|
||||
edited_on: 'Jul 02, 2014 at 20:56 UTC',
|
||||
edited_by: 'MockUser'
|
||||
}, options);
|
||||
};
|
||||
|
||||
getItemsOfType = function(type) {
|
||||
@@ -108,40 +125,100 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
return outlinePage;
|
||||
};
|
||||
|
||||
verifyTypePublishable = function (type, getMockCourseJSON) {
|
||||
var createCourseOutlinePageAndShowUnit, verifyPublishButton;
|
||||
|
||||
createCourseOutlinePageAndShowUnit = function (test, courseJSON, createOnly) {
|
||||
outlinePage = createCourseOutlinePage.apply(this, arguments);
|
||||
if (type === 'unit') {
|
||||
expandItemsAndVerifyState('subsection');
|
||||
}
|
||||
};
|
||||
|
||||
verifyPublishButton = function (test, courseJSON, createOnly) {
|
||||
createCourseOutlinePageAndShowUnit.apply(this, arguments);
|
||||
expect(getItemHeaders(type).find('.publish-button')).toExist();
|
||||
};
|
||||
|
||||
it('can be published', function() {
|
||||
var mockCourseJSON = getMockCourseJSON({
|
||||
has_changes: true
|
||||
});
|
||||
createCourseOutlinePageAndShowUnit(this, mockCourseJSON);
|
||||
getItemHeaders(type).find('.publish-button').click();
|
||||
$(".wrapper-modal-window .action-publish").click();
|
||||
create_sinon.expectJsonRequest(requests, 'POST', '/xblock/mock-' + type, {
|
||||
publish : 'make_public'
|
||||
});
|
||||
expect(requests[0].requestHeaders['X-HTTP-Method-Override']).toBe('PATCH');
|
||||
create_sinon.respondWithJson(requests, {});
|
||||
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-section');
|
||||
});
|
||||
|
||||
it('should show publish button if it is not published and not changed', function() {
|
||||
var mockCourseJSON = getMockCourseJSON({
|
||||
has_changes: false,
|
||||
published: false
|
||||
});
|
||||
verifyPublishButton(this, mockCourseJSON);
|
||||
});
|
||||
|
||||
it('should show publish button if it is published and changed', function() {
|
||||
var mockCourseJSON = getMockCourseJSON({
|
||||
has_changes: true,
|
||||
published: true
|
||||
});
|
||||
verifyPublishButton(this, mockCourseJSON);
|
||||
});
|
||||
|
||||
it('should show publish button if it is not published, but changed', function() {
|
||||
var mockCourseJSON = getMockCourseJSON({
|
||||
has_changes: true,
|
||||
published: false
|
||||
});
|
||||
verifyPublishButton(this, mockCourseJSON);
|
||||
});
|
||||
|
||||
it('should hide publish button if it is not changed, but published', function() {
|
||||
var mockCourseJSON = getMockCourseJSON({
|
||||
has_changes: false,
|
||||
published: true
|
||||
});
|
||||
createCourseOutlinePageAndShowUnit(this, mockCourseJSON);
|
||||
expect(getItemHeaders(type).find('.publish-button')).not.toExist();
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
view_helpers.installMockAnalytics();
|
||||
view_helpers.installViewTemplates();
|
||||
view_helpers.installTemplate('course-outline');
|
||||
view_helpers.installTemplate('xblock-string-field-editor');
|
||||
view_helpers.installTemplate('modal-button');
|
||||
view_helpers.installTemplate('basic-modal');
|
||||
view_helpers.installTemplate('edit-outline-item-modal');
|
||||
view_helpers.installTemplates([
|
||||
'course-outline', 'xblock-string-field-editor', 'modal-button',
|
||||
'basic-modal', 'course-outline-modal', 'release-date-editor',
|
||||
'due-date-editor', 'grading-editor', 'publish-editor',
|
||||
'staff-lock-editor'
|
||||
]);
|
||||
appendSetFixtures(mockOutlinePage);
|
||||
mockCourseJSON = createMockCourseJSON('mock-course', 'Mock Course', [
|
||||
createMockSectionJSON('mock-section', 'Mock Section', [
|
||||
createMockSubsectionJSON('mock-subsection', 'Mock Subsection', [{
|
||||
id: 'mock-unit',
|
||||
display_name: 'Mock Unit',
|
||||
category: 'vertical',
|
||||
studio_url: '/container/mock-unit',
|
||||
is_container: true,
|
||||
has_changes: false,
|
||||
published: true,
|
||||
visibility_state: 'unscheduled',
|
||||
edited_on: 'Jul 02, 2014 at 20:56 UTC',
|
||||
edited_by: 'MockUser'
|
||||
}])
|
||||
mockCourseJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({}, [
|
||||
createMockSubsectionJSON({}, [
|
||||
createMockVerticalJSON()
|
||||
])
|
||||
])
|
||||
]);
|
||||
mockEmptyCourseJSON = createMockCourseJSON('mock-course', 'Mock Course', []);
|
||||
mockSingleSectionCourseJSON = createMockCourseJSON('mock-course', 'Mock Course', [
|
||||
createMockSectionJSON('mock-section', 'Mock Section', [])
|
||||
mockEmptyCourseJSON = createMockCourseJSON();
|
||||
mockSingleSectionCourseJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON()
|
||||
]);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
view_helpers.removeMockAnalytics();
|
||||
edit_helpers.cancelModalIfShowing();
|
||||
// Clean up after the $.datepicker
|
||||
$("#start_date").datepicker( "destroy" );
|
||||
$("#due_date").datepicker( "destroy" );
|
||||
$('.ui-datepicker').remove();
|
||||
});
|
||||
|
||||
describe('Initial display', function() {
|
||||
@@ -201,7 +278,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
// Expect the UI to just fetch the new section and repaint it
|
||||
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-section-2');
|
||||
create_sinon.respondWithJson(requests,
|
||||
createMockSectionJSON('mock-section-2', 'Mock Section 2', []));
|
||||
createMockSectionJSON({id: 'mock-section-2', display_name: 'Mock Section 2'}));
|
||||
sectionElements = getItemsOfType('section');
|
||||
expect(sectionElements.length).toBe(2);
|
||||
expect($(sectionElements[0]).data('locator')).toEqual('mock-section');
|
||||
@@ -269,9 +346,9 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
|
||||
it('can be deleted', function() {
|
||||
var promptSpy = view_helpers.createPromptSpy(), requestCount;
|
||||
createCourseOutlinePage(this, createMockCourseJSON('mock-course', 'Mock Course', [
|
||||
createMockSectionJSON('mock-section', 'Mock Section', []),
|
||||
createMockSectionJSON('mock-section-2', 'Mock Section 2', [])
|
||||
createCourseOutlinePage(this, createMockCourseJSON({}, [
|
||||
createMockSectionJSON(),
|
||||
createMockSectionJSON({id: 'mock-section-2', display_name: 'Mock Section 2'})
|
||||
]));
|
||||
getItemHeaders('section').find('.delete-button').first().click();
|
||||
view_helpers.confirmPrompt(promptSpy);
|
||||
@@ -356,7 +433,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
outlinePage.$('.section-header-actions .configure-button').click();
|
||||
$("#start_date").val("1/2/2015");
|
||||
// Section release date can't be cleared.
|
||||
expect($(".edit-outline-item-modal .action-clear")).not.toExist();
|
||||
expect($(".wrapper-modal-window .action-clear")).not.toExist();
|
||||
|
||||
// Section does not contain due_date or grading type selector
|
||||
expect($("due_date")).not.toExist();
|
||||
@@ -364,9 +441,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
|
||||
// Staff lock controls are always visible
|
||||
expect($("#staff_lock")).toExist();
|
||||
|
||||
$(".edit-outline-item-modal .action-save").click();
|
||||
|
||||
$(".wrapper-modal-window .action-save").click();
|
||||
create_sinon.expectJsonRequest(requests, 'POST', '/xblock/mock-section', {
|
||||
"metadata":{
|
||||
"start":"2015-01-02T00:00:00.000Z"
|
||||
@@ -376,25 +451,16 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
|
||||
// This is the response for the change operation.
|
||||
create_sinon.respondWithJson(requests, {});
|
||||
var mockResponseSectionJSON = $.extend(true, {},
|
||||
createMockSectionJSON('mock-section', 'Mock Section', [
|
||||
createMockSubsectionJSON('mock-subsection', 'Mock Subsection', [{
|
||||
id: 'mock-unit',
|
||||
display_name: 'Mock Unit',
|
||||
category: 'vertical',
|
||||
studio_url: '/container/mock-unit',
|
||||
is_container: true,
|
||||
has_changes: true,
|
||||
published: false,
|
||||
edited_on: 'Jul 02, 2014 at 20:56 UTC',
|
||||
edited_by: 'MockUser'
|
||||
}
|
||||
var mockResponseSectionJSON = createMockSectionJSON({
|
||||
release_date: 'Jan 02, 2015 at 00:00 UTC'
|
||||
}, [
|
||||
createMockSubsectionJSON({}, [
|
||||
createMockVerticalJSON({
|
||||
has_changes: true,
|
||||
published: false
|
||||
})
|
||||
])
|
||||
]),
|
||||
{
|
||||
release_date: 'Jan 02, 2015 at 00:00 UTC',
|
||||
}
|
||||
);
|
||||
]);
|
||||
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-section')
|
||||
expect(requests.length).toBe(2);
|
||||
// This is the response for the subsequent fetch operation for the section.
|
||||
@@ -402,6 +468,46 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
|
||||
expect($(".outline-section .status-release-value")).toContainText("Jan 02, 2015 at 00:00 UTC");
|
||||
});
|
||||
|
||||
verifyTypePublishable('section', function (options) {
|
||||
return createMockCourseJSON({}, [
|
||||
createMockSectionJSON(options, [
|
||||
createMockSubsectionJSON({}, [
|
||||
createMockVerticalJSON()
|
||||
])
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
it('can display a publish modal with a list of unpublished subsections and units', function () {
|
||||
var mockCourseJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({has_changes: true}, [
|
||||
createMockSubsectionJSON({has_changes: true}, [
|
||||
createMockVerticalJSON(),
|
||||
createMockVerticalJSON({has_changes: true, display_name: 'Unit 100'}),
|
||||
createMockVerticalJSON({published: false, display_name: 'Unit 50'})
|
||||
]),
|
||||
createMockSubsectionJSON({has_changes: true}, [
|
||||
createMockVerticalJSON({has_changes: true, display_name: 'Unit 1'})
|
||||
]),
|
||||
createMockSubsectionJSON({}, [createMockVerticalJSON])
|
||||
]),
|
||||
createMockSectionJSON({has_changes: true}, [
|
||||
createMockSubsectionJSON({has_changes: true}, [
|
||||
createMockVerticalJSON({has_changes: true}),
|
||||
])
|
||||
])
|
||||
]), modalWindow;
|
||||
|
||||
createCourseOutlinePage(this, mockCourseJSON, false);
|
||||
getItemHeaders('section').first().find('.publish-button').click();
|
||||
modalWindow = $('.wrapper-modal-window');
|
||||
expect(modalWindow.find('.outline-unit').length).toBe(3);
|
||||
expect(_.compact(_.map(modalWindow.find('.outline-unit').text().split("\n"), $.trim))).toEqual(
|
||||
['Unit 100', 'Unit 50', 'Unit 1']
|
||||
)
|
||||
expect(modalWindow.find('.outline-subsection').length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Subsection", function() {
|
||||
@@ -419,37 +525,25 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
};
|
||||
|
||||
// Contains hard-coded dates because dates are presented in different formats.
|
||||
mockServerValuesJson = $.extend(true, {},
|
||||
createMockSectionJSON('mock-section', 'Mock Section', [
|
||||
createMockSubsectionJSON('mock-subsection', 'Mock Subsection', [{
|
||||
id: 'mock-unit',
|
||||
display_name: 'Mock Unit',
|
||||
category: 'vertical',
|
||||
studio_url: '/container/mock-unit',
|
||||
is_container: true,
|
||||
has_changes: true,
|
||||
published: false,
|
||||
edited_on: 'Jul 02, 2014 at 20:56 UTC',
|
||||
edited_by: 'MockUser'
|
||||
}
|
||||
var mockServerValuesJson = createMockSectionJSON({
|
||||
release_date: 'Jan 01, 2970 at 05:00 UTC'
|
||||
}, [
|
||||
createMockSubsectionJSON({
|
||||
graded: true,
|
||||
due_date: 'Jul 10, 2014 at 00:00 UTC',
|
||||
release_date: 'Jul 09, 2014 at 00:00 UTC',
|
||||
start: "2014-07-09T00:00:00Z",
|
||||
format: "Lab",
|
||||
due: "2014-07-10T00:00:00Z",
|
||||
has_explicit_staff_lock: true,
|
||||
staff_only_message: true
|
||||
}, [
|
||||
createMockVerticalJSON({
|
||||
has_changes: true,
|
||||
published: false
|
||||
})
|
||||
])
|
||||
]),
|
||||
{
|
||||
release_date: 'Jan 01, 2970 at 05:00 UTC',
|
||||
child_info: { //Section child_info
|
||||
children: [{ // Section children
|
||||
graded: true,
|
||||
due_date: 'Jul 10, 2014 at 00:00 UTC',
|
||||
release_date: 'Jul 09, 2014 at 00:00 UTC',
|
||||
start: "2014-07-09T00:00:00Z",
|
||||
format: "Lab",
|
||||
due: "2014-07-10T00:00:00Z",
|
||||
has_explicit_staff_lock: true,
|
||||
staff_only_message: true
|
||||
}]
|
||||
}
|
||||
}
|
||||
);
|
||||
]);
|
||||
|
||||
it('can be deleted', function() {
|
||||
var promptSpy = view_helpers.createPromptSpy();
|
||||
@@ -492,9 +586,12 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
create_sinon.respondWithJson(requests, { });
|
||||
// This is the response for the subsequent fetch operation for the section.
|
||||
create_sinon.respondWithJson(requests,
|
||||
createMockSectionJSON('mock-section', 'Mock Section', [
|
||||
createMockSubsectionJSON('mock-subsection', updatedDisplayName, [])
|
||||
]));
|
||||
createMockSectionJSON({}, [
|
||||
createMockSubsectionJSON({
|
||||
display_name: updatedDisplayName
|
||||
})
|
||||
])
|
||||
);
|
||||
// Find the display name again in the refreshed DOM and verify it
|
||||
displayNameWrapper = getItemHeaders('subsection').find('.wrapper-xblock-field');
|
||||
view_helpers.verifyInlineEditChange(displayNameWrapper, updatedDisplayName);
|
||||
@@ -514,7 +611,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
createCourseOutlinePage(this, mockCourseJSON, false);
|
||||
outlinePage.$('.outline-subsection .configure-button').click();
|
||||
setEditModalValues("7/9/2014", "7/10/2014", "Lab", true);
|
||||
$(".edit-outline-item-modal .action-save").click();
|
||||
$(".wrapper-modal-window .action-save").click();
|
||||
create_sinon.expectJsonRequest(requests, 'POST', '/xblock/mock-subsection', {
|
||||
"graderType":"Lab",
|
||||
"publish": "republish",
|
||||
@@ -550,7 +647,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
createCourseOutlinePage(this, mockCourseJSON, false);
|
||||
outlinePage.$('.outline-item .outline-subsection .configure-button').click();
|
||||
setEditModalValues("7/9/2014", "7/10/2014", "Lab", true);
|
||||
$(".edit-outline-item-modal .action-save").click();
|
||||
$(".wrapper-modal-window .action-save").click();
|
||||
|
||||
// This is the response for the change operation.
|
||||
create_sinon.respondWithJson(requests, {});
|
||||
@@ -568,29 +665,62 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
expect($("#grading_type").val()).toBe('Lab');
|
||||
expect($("#staff_lock").is(":checked")).toBe(true);
|
||||
|
||||
$(".edit-outline-item-modal .scheduled-date-input .action-clear").click();
|
||||
$(".edit-outline-item-modal .due-date-input .action-clear").click();
|
||||
$(".wrapper-modal-window .scheduled-date-input .action-clear").click();
|
||||
$(".wrapper-modal-window .due-date-input .action-clear").click();
|
||||
expect($("#start_date").val()).toBe('');
|
||||
expect($("#due_date").val()).toBe('');
|
||||
|
||||
$("#grading_type").val('notgraded');
|
||||
$("#staff_lock").prop('checked', false);
|
||||
|
||||
$(".edit-outline-item-modal .action-save").click();
|
||||
$(".wrapper-modal-window .action-save").click();
|
||||
|
||||
// This is the response for the change operation.
|
||||
create_sinon.respondWithJson(requests, {});
|
||||
// This is the response for the subsequent fetch operation.
|
||||
create_sinon.respondWithJson(requests,
|
||||
createMockSectionJSON('mock-section', 'Mock Section', [
|
||||
createMockSubsectionJSON('mock-subsection', 'Mock Subsection', [])
|
||||
])
|
||||
createMockSectionJSON({}, [createMockSubsectionJSON()])
|
||||
);
|
||||
expect($(".outline-subsection .status-release-value")).not.toContainText("Jul 09, 2014 at 00:00 UTC");
|
||||
expect($(".outline-subsection .status-grading-date")).not.toExist();
|
||||
expect($(".outline-subsection .status-grading-value")).not.toExist();
|
||||
expect($(".outline-subsection .status-message-copy")).not.toContainText("Contains staff only content");
|
||||
});
|
||||
|
||||
verifyTypePublishable('subsection', function (options) {
|
||||
return createMockCourseJSON({}, [
|
||||
createMockSectionJSON({}, [
|
||||
createMockSubsectionJSON(options, [
|
||||
createMockVerticalJSON()
|
||||
])
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
it('can display a publish modal with a list of unpublished units', function () {
|
||||
var mockCourseJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({has_changes: true}, [
|
||||
createMockSubsectionJSON({has_changes: true}, [
|
||||
createMockVerticalJSON(),
|
||||
createMockVerticalJSON({has_changes: true, display_name: "Unit 100"}),
|
||||
createMockVerticalJSON({published: false, display_name: "Unit 50"})
|
||||
]),
|
||||
createMockSubsectionJSON({has_changes: true}, [
|
||||
createMockVerticalJSON({has_changes: true})
|
||||
]),
|
||||
createMockSubsectionJSON({}, [createMockVerticalJSON])
|
||||
])
|
||||
]), modalWindow;
|
||||
|
||||
createCourseOutlinePage(this, mockCourseJSON, false);
|
||||
getItemHeaders('subsection').first().find('.publish-button').click();
|
||||
modalWindow = $('.wrapper-modal-window');
|
||||
expect(modalWindow.find('.outline-unit').length).toBe(2);
|
||||
expect(_.compact(_.map(modalWindow.find('.outline-unit').text().split("\n"), $.trim))).toEqual(
|
||||
['Unit 100', 'Unit 50']
|
||||
)
|
||||
expect(modalWindow.find('.outline-subsection')).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
// Note: most tests for units can be found in Bok Choy
|
||||
@@ -615,6 +745,16 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
unitAnchor = getItemsOfType('unit').find('.unit-title a');
|
||||
expect(unitAnchor.attr('href')).toBe('/container/mock-unit');
|
||||
});
|
||||
|
||||
verifyTypePublishable('unit', function (options) {
|
||||
return createMockCourseJSON({}, [
|
||||
createMockSectionJSON({}, [
|
||||
createMockSubsectionJSON({}, [
|
||||
createMockVerticalJSON(options)
|
||||
])
|
||||
])
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Date and Time picker", function() {
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
* - changes cause a refresh of the entire section rather than just the view for the changed xblock
|
||||
* - adding units will automatically redirect to the unit page rather than showing them inline
|
||||
*/
|
||||
define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_utils",
|
||||
"js/models/xblock_outline_info", "js/views/modals/edit_outline_item", "js/utils/drag_and_drop"],
|
||||
function($, _, XBlockOutlineView, ViewUtils, XBlockOutlineInfo, EditSectionXBlockModal, ContentDragger) {
|
||||
define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_utils", "js/views/utils/xblock_utils",
|
||||
"js/models/xblock_outline_info", "js/views/modals/course_outline_modals", "js/utils/drag_and_drop"],
|
||||
function(
|
||||
$, _, XBlockOutlineView, ViewUtils, XBlockViewUtils,
|
||||
XBlockOutlineInfo, CourseOutlineModalsFactory, ContentDragger
|
||||
) {
|
||||
|
||||
var CourseOutlineView = XBlockOutlineView.extend({
|
||||
// takes XBlockOutlineInfo as a model
|
||||
@@ -144,13 +147,30 @@ define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_
|
||||
},
|
||||
|
||||
editXBlock: function() {
|
||||
var modal = new EditSectionXBlockModal({
|
||||
model: this.model,
|
||||
var modal = CourseOutlineModalsFactory.getModal('edit', this.model, {
|
||||
onSave: this.refresh.bind(this),
|
||||
parentInfo: this.parentInfo
|
||||
parentInfo: this.parentInfo,
|
||||
xblockType: XBlockViewUtils.getXBlockType(
|
||||
this.model.get('category'), this.parentView.model, true
|
||||
)
|
||||
});
|
||||
|
||||
modal.show();
|
||||
if (modal) {
|
||||
modal.show();
|
||||
}
|
||||
},
|
||||
|
||||
publishXBlock: function() {
|
||||
var modal = CourseOutlineModalsFactory.getModal('publish', this.model, {
|
||||
onSave: this.refresh.bind(this),
|
||||
xblockType: XBlockViewUtils.getXBlockType(
|
||||
this.model.get('category'), this.parentView.model, true
|
||||
)
|
||||
});
|
||||
|
||||
if (modal) {
|
||||
modal.show();
|
||||
}
|
||||
},
|
||||
|
||||
addButtonActions: function(element) {
|
||||
@@ -159,6 +179,10 @@ define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_
|
||||
event.preventDefault();
|
||||
this.editXBlock();
|
||||
}.bind(this));
|
||||
element.find('.publish-button').click(function(event) {
|
||||
event.preventDefault();
|
||||
this.publishXBlock();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
makeContentDraggable: function(element) {
|
||||
|
||||
379
cms/static/js/views/modals/course_outline_modals.js
Normal file
379
cms/static/js/views/modals/course_outline_modals.js
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* The CourseOutlineXBlockModal is a Backbone view that shows an editor in a modal window.
|
||||
* It has nested views: for release date, due date and grading format.
|
||||
* It is invoked using the editXBlock method and uses xblock_info as a model,
|
||||
* and upon save parent invokes refresh function that fetches updated model and
|
||||
* re-renders edited course outline.
|
||||
*/
|
||||
define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
'js/views/modals/base_modal', 'date', 'js/views/utils/xblock_utils',
|
||||
'js/utils/date_utils'
|
||||
], function(
|
||||
$, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils
|
||||
) {
|
||||
'use strict';
|
||||
var CourseOutlineXBlockModal, SettingsXBlockModal, PublishXBlockModal, AbstractEditor, BaseDateEditor,
|
||||
ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, StaffLockEditor;
|
||||
|
||||
CourseOutlineXBlockModal = BaseModal.extend({
|
||||
events : {
|
||||
'click .action-save': 'save'
|
||||
},
|
||||
|
||||
options: $.extend({}, BaseModal.prototype.options, {
|
||||
modalName: 'course-outline',
|
||||
modalType: 'edit-settings',
|
||||
addSaveButton: true,
|
||||
modalSize: 'med',
|
||||
viewSpecificClasses: 'confirm',
|
||||
editors: []
|
||||
}),
|
||||
|
||||
initialize: function() {
|
||||
BaseModal.prototype.initialize.call(this);
|
||||
this.events = $.extend({}, BaseModal.prototype.events, this.events);
|
||||
this.template = this.loadTemplate('course-outline-modal');
|
||||
this.options.title = this.getTitle();
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
BaseModal.prototype.afterRender.call(this);
|
||||
this.initializeEditors();
|
||||
},
|
||||
|
||||
initializeEditors: function () {
|
||||
this.options.editors = _.map(this.options.editors, function (Editor) {
|
||||
return new Editor({
|
||||
parentElement: this.$('.modal-section'),
|
||||
model: this.model,
|
||||
xblockType: this.options.xblockType
|
||||
});
|
||||
}, this);
|
||||
},
|
||||
|
||||
getTitle: function () {
|
||||
return '';
|
||||
},
|
||||
|
||||
getIntroductionMessage: function () {
|
||||
return '';
|
||||
},
|
||||
|
||||
getContentHtml: function() {
|
||||
return this.template(this.getContext());
|
||||
},
|
||||
|
||||
save: function(event) {
|
||||
event.preventDefault();
|
||||
var requestData = this.getRequestData();
|
||||
if (!_.isEqual(requestData, { metadata: {} })) {
|
||||
XBlockViewUtils.updateXBlockFields(this.model, requestData, {
|
||||
success: this.options.onSave
|
||||
});
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return context for the modal.
|
||||
* @return {Object}
|
||||
*/
|
||||
getContext: function () {
|
||||
return $.extend({
|
||||
xblockInfo: this.model,
|
||||
introductionMessage: this.getIntroductionMessage()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return request data.
|
||||
* @return {Object}
|
||||
*/
|
||||
getRequestData: function () {
|
||||
var requestData = _.map(this.options.editors, function (editor) {
|
||||
return editor.getRequestData();
|
||||
});
|
||||
|
||||
return $.extend.apply(this, [true, {}].concat(requestData));
|
||||
}
|
||||
});
|
||||
|
||||
SettingsXBlockModal = CourseOutlineXBlockModal.extend({
|
||||
getTitle: function () {
|
||||
return interpolate(
|
||||
gettext('%(display_name)s Settings'),
|
||||
{ display_name: this.model.get('display_name') }, true
|
||||
);
|
||||
},
|
||||
|
||||
getIntroductionMessage: function () {
|
||||
return interpolate(
|
||||
gettext('Change the settings for %(display_name)s'),
|
||||
{ display_name: this.model.get('display_name') }, true
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
PublishXBlockModal = CourseOutlineXBlockModal.extend({
|
||||
events : {
|
||||
'click .action-publish': 'save'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
CourseOutlineXBlockModal.prototype.initialize.call(this);
|
||||
if (this.options.xblockType) {
|
||||
this.options.modalName = 'bulkpublish-' + this.options.xblockType;
|
||||
}
|
||||
},
|
||||
|
||||
getTitle: function () {
|
||||
return interpolate(
|
||||
gettext('Publish %(display_name)s'),
|
||||
{ display_name: this.model.get('display_name') }, true
|
||||
);
|
||||
},
|
||||
|
||||
getIntroductionMessage: function () {
|
||||
return interpolate(
|
||||
gettext('Publish all unpublished changes for this %(item)s?'),
|
||||
{ item: this.options.xblockType }, true
|
||||
);
|
||||
},
|
||||
|
||||
addActionButtons: function() {
|
||||
this.addActionButton('publish', gettext('Publish'), true);
|
||||
this.addActionButton('cancel', gettext('Cancel'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
AbstractEditor = BaseView.extend({
|
||||
tagName: 'section',
|
||||
templateName: null,
|
||||
initialize: function() {
|
||||
this.template = this.loadTemplate(this.templateName);
|
||||
this.parentElement = this.options.parentElement;
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var html = this.template($.extend({}, {
|
||||
xblockInfo: this.model,
|
||||
xblockType: this.options.xblockType
|
||||
}, this.getContext()));
|
||||
|
||||
this.$el.html(html);
|
||||
this.parentElement.append(this.$el);
|
||||
},
|
||||
|
||||
getContext: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
getRequestData: function () {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
BaseDateEditor = AbstractEditor.extend({
|
||||
// Attribute name in the model, should be defined in children classes.
|
||||
fieldName: null,
|
||||
|
||||
events : {
|
||||
'click .clear-date': 'clearValue'
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
AbstractEditor.prototype.afterRender.call(this);
|
||||
this.$('input.date').datepicker({'dateFormat': 'm/d/yy'});
|
||||
this.$('input.time').timepicker({
|
||||
'timeFormat' : 'H:i',
|
||||
'forceRoundTime': true
|
||||
});
|
||||
if (this.model.get(this.fieldName)) {
|
||||
DateUtils.setDate(
|
||||
this.$('input.date'), this.$('input.time'),
|
||||
this.model.get(this.fieldName)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
DueDateEditor = BaseDateEditor.extend({
|
||||
fieldName: 'due',
|
||||
templateName: 'due-date-editor',
|
||||
className: 'modal-section-content has-actions due-date-input grading-due-date',
|
||||
|
||||
getValue: function () {
|
||||
return DateUtils.getDate(this.$('#due_date'), this.$('#due_time'));
|
||||
},
|
||||
|
||||
clearValue: function (event) {
|
||||
event.preventDefault();
|
||||
this.$('#due_time, #due_date').val('');
|
||||
},
|
||||
|
||||
getRequestData: function () {
|
||||
return {
|
||||
metadata: {
|
||||
'due': this.getValue()
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ReleaseDateEditor = BaseDateEditor.extend({
|
||||
fieldName: 'start',
|
||||
templateName: 'release-date-editor',
|
||||
className: 'edit-settings-release scheduled-date-input',
|
||||
startingReleaseDate: null,
|
||||
|
||||
afterRender: function () {
|
||||
BaseDateEditor.prototype.afterRender.call(this);
|
||||
// Store the starting date and time so that we can determine if the user
|
||||
// actually changed it when "Save" is pressed.
|
||||
this.startingReleaseDate = this.getValue();
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
return DateUtils.getDate(this.$('#start_date'), this.$('#start_time'));
|
||||
},
|
||||
|
||||
clearValue: function (event) {
|
||||
event.preventDefault();
|
||||
this.$('#start_time, #start_date').val('');
|
||||
},
|
||||
|
||||
getRequestData: function () {
|
||||
var newReleaseDate = this.getValue();
|
||||
if (JSON.stringify(newReleaseDate) === JSON.stringify(this.startingReleaseDate)) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
metadata: {
|
||||
'start': newReleaseDate
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
GradingEditor = AbstractEditor.extend({
|
||||
templateName: 'grading-editor',
|
||||
className: 'edit-settings-grading',
|
||||
|
||||
afterRender: function () {
|
||||
AbstractEditor.prototype.afterRender.call(this);
|
||||
this.setValue(this.model.get('format'));
|
||||
},
|
||||
|
||||
setValue: function (value) {
|
||||
this.$('#grading_type').val(value);
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
return this.$('#grading_type').val();
|
||||
},
|
||||
|
||||
getRequestData: function () {
|
||||
return {
|
||||
'graderType': this.getValue()
|
||||
};
|
||||
},
|
||||
|
||||
getContext: function () {
|
||||
return {
|
||||
graderTypes: JSON.parse(this.model.get('course_graders'))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
PublishEditor = AbstractEditor.extend({
|
||||
templateName: 'publish-editor',
|
||||
className: 'edit-settings-publish',
|
||||
getRequestData: function () {
|
||||
return {
|
||||
publish: 'make_public'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
StaffLockEditor = AbstractEditor.extend({
|
||||
templateName: 'staff-lock-editor',
|
||||
className: 'edit-staff-lock',
|
||||
isModelLocked: function() {
|
||||
return this.model.get('has_explicit_staff_lock');
|
||||
},
|
||||
|
||||
isAncestorLocked: function() {
|
||||
return this.model.get('ancestor_has_staff_lock');
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
AbstractEditor.prototype.afterRender.call(this);
|
||||
this.setLock(this.isModelLocked());
|
||||
},
|
||||
|
||||
setLock: function(value) {
|
||||
this.$('#staff_lock').prop('checked', value);
|
||||
},
|
||||
|
||||
isLocked: function() {
|
||||
return this.$('#staff_lock').is(':checked');
|
||||
},
|
||||
|
||||
hasChanges: function() {
|
||||
return this.isModelLocked() != this.isLocked();
|
||||
},
|
||||
|
||||
getRequestData: function() {
|
||||
return this.hasChanges() ? {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
visible_to_staff_only: this.isLocked() ? true : null
|
||||
}
|
||||
} : {};
|
||||
},
|
||||
|
||||
getContext: function () {
|
||||
return {
|
||||
hasExplicitStaffLock: this.isModelLocked(),
|
||||
ancestorLocked: this.isAncestorLocked()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
getModal: function (type, xblockInfo, options) {
|
||||
if (type === 'edit') {
|
||||
return this.getEditModal(xblockInfo, options);
|
||||
} else if (type === 'publish') {
|
||||
return this.getPublishModal(xblockInfo, options);
|
||||
}
|
||||
},
|
||||
|
||||
getEditModal: function (xblockInfo, options) {
|
||||
var editors = [];
|
||||
|
||||
if (xblockInfo.isChapter()) {
|
||||
editors = [ReleaseDateEditor, StaffLockEditor];
|
||||
} else if (xblockInfo.isSequential()) {
|
||||
editors = [ReleaseDateEditor, GradingEditor, DueDateEditor, StaffLockEditor];
|
||||
} else if (xblockInfo.isVertical()) {
|
||||
editors = [StaffLockEditor];
|
||||
}
|
||||
|
||||
return new SettingsXBlockModal($.extend({
|
||||
editors: editors,
|
||||
model: xblockInfo
|
||||
}, options));
|
||||
},
|
||||
|
||||
getPublishModal: function (xblockInfo, options) {
|
||||
return new PublishXBlockModal($.extend({
|
||||
editors: [PublishEditor],
|
||||
model: xblockInfo
|
||||
}, options));
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,299 +0,0 @@
|
||||
/**
|
||||
* The EditSectionXBlockModal is a Backbone view that shows an editor in a modal window.
|
||||
* It has nested views: for release date, due date, grading format, and staff lock.
|
||||
* It is invoked using the editXBlock method and uses xblock_info as a model,
|
||||
* and upon save parent invokes refresh function that fetches updated model and
|
||||
* re-renders edited course outline.
|
||||
*/
|
||||
define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/modals/base_modal',
|
||||
'date', 'js/views/utils/xblock_utils', 'js/utils/date_utils', 'js/views/utils/view_utils'
|
||||
],
|
||||
function(
|
||||
$, Backbone, _, gettext, BaseModal, date, XBlockViewUtils, DateUtils, ViewUtils
|
||||
) {
|
||||
'use strict';
|
||||
var EditSectionXBlockModal, BaseDateView, ReleaseDateView, DueDateView,
|
||||
GradingView, StaffLockView;
|
||||
|
||||
EditSectionXBlockModal = BaseModal.extend({
|
||||
events : {
|
||||
'click .action-save': 'save',
|
||||
'click .action-modes a': 'changeMode'
|
||||
},
|
||||
|
||||
options: $.extend({}, BaseModal.prototype.options, {
|
||||
modalName: 'edit-outline-item',
|
||||
modalType: 'edit-settings',
|
||||
addSaveButton: true,
|
||||
modalSize: 'med',
|
||||
viewSpecificClasses: 'confirm'
|
||||
}),
|
||||
|
||||
initialize: function() {
|
||||
BaseModal.prototype.initialize.call(this);
|
||||
this.events = _.extend({}, BaseModal.prototype.events, this.events);
|
||||
this.template = this.loadTemplate('edit-outline-item-modal');
|
||||
this.options.title = this.getTitle();
|
||||
this.initializeComponents();
|
||||
},
|
||||
|
||||
getTitle: function () {
|
||||
return _.template(
|
||||
gettext('<%= sectionName %> Settings'),
|
||||
{ sectionName: this.model.get('display_name') }
|
||||
);
|
||||
},
|
||||
|
||||
getContentHtml: function() {
|
||||
return this.template(this.getContext());
|
||||
},
|
||||
|
||||
afterRender: function() {
|
||||
BaseModal.prototype.render.apply(this, arguments);
|
||||
this.invokeComponentMethod('afterRender');
|
||||
},
|
||||
|
||||
save: function(event) {
|
||||
event.preventDefault();
|
||||
var requestData = _.extend({}, this.getRequestData(), {
|
||||
metadata: this.getMetadata()
|
||||
});
|
||||
// Only update if something changed to prevent items from erroneously entering draft state
|
||||
if (!_.isEqual(requestData, { metadata: {} })) {
|
||||
XBlockViewUtils.updateXBlockFields(this.model, requestData, {
|
||||
success: this.options.onSave
|
||||
});
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Call the method on each value in the list. If the element of the
|
||||
* list doesn't have such a method it will be skipped.
|
||||
* @param {String} methodName The method name needs to be called.
|
||||
* @return {Object}
|
||||
*/
|
||||
invokeComponentMethod: function (methodName) {
|
||||
var values = _.map(this.components, function (component) {
|
||||
if (_.isFunction(component[methodName])) {
|
||||
return component[methodName].call(component);
|
||||
}
|
||||
});
|
||||
|
||||
return _.extend.apply(this, [{}].concat(values));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return context for the modal.
|
||||
* @return {Object}
|
||||
*/
|
||||
getContext: function () {
|
||||
return _.extend({
|
||||
xblockInfo: this.model,
|
||||
xblockType: XBlockViewUtils.getXBlockType(this.model.get('category'), this.parentInfo, true)
|
||||
}, this.invokeComponentMethod('getContext'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return request data.
|
||||
* @return {Object}
|
||||
*/
|
||||
getRequestData: function () {
|
||||
return this.invokeComponentMethod('getRequestData');
|
||||
},
|
||||
|
||||
/**
|
||||
* Return metadata for the XBlock.
|
||||
* @return {Object}
|
||||
*/
|
||||
getMetadata: function () {
|
||||
return this.invokeComponentMethod('getMetadata');
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize internal components.
|
||||
*/
|
||||
initializeComponents: function () {
|
||||
this.components = [];
|
||||
this.components.push(
|
||||
new StaffLockView({
|
||||
selector: '.edit-staff-lock',
|
||||
parentView: this,
|
||||
model: this.model
|
||||
})
|
||||
);
|
||||
|
||||
if (this.model.isChapter() || this.model.isSequential()) {
|
||||
this.components.push(
|
||||
new ReleaseDateView({
|
||||
selector: '.scheduled-date-input',
|
||||
parentView: this,
|
||||
model: this.model
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this.model.isSequential()) {
|
||||
this.components.push(
|
||||
new DueDateView({
|
||||
selector: '.due-date-input',
|
||||
parentView: this,
|
||||
model: this.model
|
||||
}),
|
||||
new GradingView({
|
||||
selector: '.edit-settings-grading',
|
||||
parentView: this,
|
||||
model: this.model
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BaseDateView = Backbone.View.extend({
|
||||
// Attribute name in the model, should be defined in children classes.
|
||||
fieldName: null,
|
||||
|
||||
events : {
|
||||
'click .clear-date': 'clearValue'
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
this.setElement(this.options.parentView.$(this.options.selector).get(0));
|
||||
this.$('input.date').datepicker({'dateFormat': 'm/d/yy'});
|
||||
this.$('input.time').timepicker({
|
||||
'timeFormat' : 'H:i',
|
||||
'forceRoundTime': true
|
||||
});
|
||||
if (this.model.get(this.fieldName)) {
|
||||
DateUtils.setDate(
|
||||
this.$('input.date'), this.$('input.time'),
|
||||
this.model.get(this.fieldName)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
DueDateView = BaseDateView.extend({
|
||||
fieldName: 'due',
|
||||
|
||||
getValue: function () {
|
||||
return DateUtils.getDate(this.$('#due_date'), this.$('#due_time'));
|
||||
},
|
||||
|
||||
clearValue: function (event) {
|
||||
event.preventDefault();
|
||||
this.$('#due_time, #due_date').val('');
|
||||
},
|
||||
|
||||
getMetadata: function () {
|
||||
return {
|
||||
'due': this.getValue()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ReleaseDateView = BaseDateView.extend({
|
||||
fieldName: 'start',
|
||||
startingReleaseDate: null,
|
||||
|
||||
afterRender: function () {
|
||||
BaseDateView.prototype.afterRender.call(this);
|
||||
// Store the starting date and time so that we can determine if the user
|
||||
// actually changed it when "Save" is pressed.
|
||||
this.startingReleaseDate = this.getValue();
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
return DateUtils.getDate(this.$('#start_date'), this.$('#start_time'));
|
||||
},
|
||||
|
||||
clearValue: function (event) {
|
||||
event.preventDefault();
|
||||
this.$('#start_time, #start_date').val('');
|
||||
},
|
||||
|
||||
getMetadata: function () {
|
||||
var newReleaseDate = this.getValue();
|
||||
if (JSON.stringify(newReleaseDate) === JSON.stringify(this.startingReleaseDate)) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
'start': newReleaseDate
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
GradingView = Backbone.View.extend({
|
||||
afterRender: function () {
|
||||
this.setElement(this.options.parentView.$(this.options.selector).get(0));
|
||||
this.setValue(this.model.get('format'));
|
||||
},
|
||||
|
||||
setValue: function (value) {
|
||||
this.$('#grading_type').val(value);
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
return this.$('#grading_type').val();
|
||||
},
|
||||
|
||||
getRequestData: function () {
|
||||
return {
|
||||
'graderType': this.getValue()
|
||||
};
|
||||
},
|
||||
|
||||
getContext: function () {
|
||||
return {
|
||||
graderTypes: JSON.parse(this.model.get('course_graders'))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
StaffLockView = Backbone.View.extend({
|
||||
isModelLocked: function() {
|
||||
return this.model.get('has_explicit_staff_lock');
|
||||
},
|
||||
|
||||
isAncestorLocked: function() {
|
||||
return this.model.get('ancestor_has_staff_lock');
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
this.setElement(this.options.parentView.$(this.options.selector).get(0));
|
||||
this.setLock(this.isModelLocked());
|
||||
},
|
||||
|
||||
setLock: function(value) {
|
||||
this.$('#staff_lock').prop('checked', value);
|
||||
},
|
||||
|
||||
isLocked: function() {
|
||||
return this.$('#staff_lock').is(':checked');
|
||||
},
|
||||
|
||||
hasChanges: function() {
|
||||
return this.isModelLocked() != this.isLocked();
|
||||
},
|
||||
|
||||
getRequestData: function() {
|
||||
return this.hasChanges() ? { publish: 'republish' } : {};
|
||||
},
|
||||
|
||||
getMetadata: function() {
|
||||
// Setting visible_to_staff_only to null when disabled will delete the field from this
|
||||
// xblock, allowing it to inherit the value of its ancestors.
|
||||
return this.hasChanges() ? { visible_to_staff_only: this.isLocked() ? true : null } : {};
|
||||
},
|
||||
|
||||
getContext: function () {
|
||||
return {
|
||||
hasExplicitStaffLock: this.isModelLocked(),
|
||||
ancestorLocked: this.isAncestorLocked()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return EditSectionXBlockModal;
|
||||
});
|
||||
Reference in New Issue
Block a user