diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 87a2943773..32e7b2d685 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -94,6 +94,8 @@ def login_page(request):
'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE),
})
+def howitworks(request):
+ return render_to_response('howitworks.html', {})
# ==== Views for any logged-in user ==================================
@@ -730,8 +732,6 @@ def clone_item(request):
#@login_required
#@ensure_csrf_cookie
-
-
def upload_asset(request, org, course, coursename):
'''
cdodge: this method allows for POST uploading of files into the course asset library, which will
@@ -796,8 +796,6 @@ def upload_asset(request, org, course, coursename):
'''
This view will return all CMS users who are editors for the specified course
'''
-
-
@login_required
@ensure_csrf_cookie
def manage_users(request, location):
@@ -819,7 +817,7 @@ def manage_users(request, location):
})
-def create_json_response(errmsg=None):
+def create_json_response(errmsg = None):
if errmsg is not None:
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg}))
else:
@@ -831,8 +829,6 @@ def create_json_response(errmsg=None):
This POST-back view will add a user - specified by email - to the list of editors for
the specified course
'''
-
-
@expect_json
@login_required
@ensure_csrf_cookie
@@ -865,8 +861,6 @@ def add_user(request, location):
This POST-back view will remove a user - specified by email - from the list of editors for
the specified course
'''
-
-
@expect_json
@login_required
@ensure_csrf_cookie
@@ -1124,8 +1118,31 @@ def get_course_settings(request, org, course, name):
course_details = CourseDetails.fetch(location)
return render_to_response('settings.html', {
- 'active_tab': 'settings',
'context_course': course_module,
+ 'course_location' : location,
+ 'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
+ })
+
+@login_required
+@ensure_csrf_cookie
+def course_config_graders_page(request, org, course, name):
+ """
+ Send models and views as well as html for editing the course settings to the client.
+
+ org, course, name: Attributes of the Location for the item to edit
+ """
+ location = ['i4x', org, course, 'course', name]
+
+ # check that logged in user has permissions to this item
+ if not has_access(request.user, location):
+ raise PermissionDenied()
+
+ course_module = modulestore().get_item(location)
+ course_details = CourseGradingModel.fetch(location)
+
+ return render_to_response('settings_graders.html', {
+ 'context_course': course_module,
+ 'course_location' : location,
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder)
})
diff --git a/cms/static/client_templates/course_grade_policy.html b/cms/static/client_templates/course_grade_policy.html
index c9a21280dd..639bee3d9b 100644
--- a/cms/static/client_templates/course_grade_policy.html
+++ b/cms/static/client_templates/course_grade_policy.html
@@ -1,69 +1,37 @@
-
-
-
+
+
+
+
+ e.g. Homework, Midterm Exams
+
-
-
-
- e.g. Homework, Labs, Midterm Exams, Final Exam
-
diff --git a/cms/static/img/hiw-feature1.png b/cms/static/img/hiw-feature1.png
new file mode 100644
index 0000000000..3cfd48d066
Binary files /dev/null and b/cms/static/img/hiw-feature1.png differ
diff --git a/cms/static/img/hiw-feature2.png b/cms/static/img/hiw-feature2.png
new file mode 100644
index 0000000000..9442325dd5
Binary files /dev/null and b/cms/static/img/hiw-feature2.png differ
diff --git a/cms/static/img/hiw-feature3.png b/cms/static/img/hiw-feature3.png
new file mode 100644
index 0000000000..fa6b81ae89
Binary files /dev/null and b/cms/static/img/hiw-feature3.png differ
diff --git a/cms/static/img/logo-edx-studio-white.png b/cms/static/img/logo-edx-studio-white.png
new file mode 100644
index 0000000000..3e3ee63622
Binary files /dev/null and b/cms/static/img/logo-edx-studio-white.png differ
diff --git a/cms/static/img/logo-edx-studio.png b/cms/static/img/logo-edx-studio.png
new file mode 100644
index 0000000000..006194a195
Binary files /dev/null and b/cms/static/img/logo-edx-studio.png differ
diff --git a/cms/static/img/pl-1x1-000.png b/cms/static/img/pl-1x1-000.png
new file mode 100644
index 0000000000..b94b7a9746
Binary files /dev/null and b/cms/static/img/pl-1x1-000.png differ
diff --git a/cms/static/img/pl-1x1-fff.png b/cms/static/img/pl-1x1-fff.png
new file mode 100644
index 0000000000..7081c75d36
Binary files /dev/null and b/cms/static/img/pl-1x1-fff.png differ
diff --git a/cms/static/img/thumb-hiw-feature1.png b/cms/static/img/thumb-hiw-feature1.png
new file mode 100644
index 0000000000..b2dc0c00ee
Binary files /dev/null and b/cms/static/img/thumb-hiw-feature1.png differ
diff --git a/cms/static/img/thumb-hiw-feature2.png b/cms/static/img/thumb-hiw-feature2.png
new file mode 100644
index 0000000000..e96bcad1aa
Binary files /dev/null and b/cms/static/img/thumb-hiw-feature2.png differ
diff --git a/cms/static/img/thumb-hiw-feature3.png b/cms/static/img/thumb-hiw-feature3.png
new file mode 100644
index 0000000000..f694fca516
Binary files /dev/null and b/cms/static/img/thumb-hiw-feature3.png differ
diff --git a/cms/static/js/base.js b/cms/static/js/base.js
index 7e55d2b8d8..02b422cecd 100644
--- a/cms/static/js/base.js
+++ b/cms/static/js/base.js
@@ -6,106 +6,141 @@ var $changedInput;
var $spinner;
$(document).ready(function() {
- $body = $('body');
- $modal = $('.history-modal');
- $modalCover = $('
');
- // cdodge: this looks funny, but on AWS instances, this base.js get's wrapped in a separate scope as part of Django static
- // pipelining (note, this doesn't happen on local runtimes). So if we set it on window, when we can access it from other
- // scopes (namely the course-info tab)
- window.$modalCover = $modalCover;
-
- // Control whether template caching in local memory occurs (see template_loader.js). Caching screws up development but may
- // be a good optimization in production (it works fairly well)
- window.cachetemplates = false;
+ $body = $('body');
+ $modal = $('.history-modal');
+ $modalCover = $('
');
+ // cdodge: this looks funny, but on AWS instances, this base.js get's wrapped in a separate scope as part of Django static
+ // pipelining (note, this doesn't happen on local runtimes). So if we set it on window, when we can access it from other
+ // scopes (namely the course-info tab)
+ window.$modalCover = $modalCover;
+
+ // Control whether template caching in local memory occurs (see template_loader.js). Caching screws up development but may
+ // be a good optimization in production (it works fairly well)
+ window.cachetemplates = false;
- $body.append($modalCover);
- $newComponentItem = $('.new-component-item');
- $newComponentTypePicker = $('.new-component');
- $newComponentTemplatePickers = $('.new-component-templates');
- $newComponentButton = $('.new-component-button');
- $spinner = $('');
- $body.bind('keyup', onKeyUp);
+ $body.append($modalCover);
+ $newComponentItem = $('.new-component-item');
+ $newComponentTypePicker = $('.new-component');
+ $newComponentTemplatePickers = $('.new-component-templates');
+ $newComponentButton = $('.new-component-button');
+ $spinner = $('');
+ $body.bind('keyup', onKeyUp);
- $('.expand-collapse-icon').bind('click', toggleSubmodules);
- $('.visibility-options').bind('change', setVisibility);
+ $('.expand-collapse-icon').bind('click', toggleSubmodules);
+ $('.visibility-options').bind('change', setVisibility);
- $modal.bind('click', hideModal);
- $modalCover.bind('click', hideModal);
- $('.assets .upload-button').bind('click', showUploadModal);
- $('.upload-modal .close-button').bind('click', hideModal);
+ $modal.bind('click', hideModal);
+ $modalCover.bind('click', hideModal);
+ $('.uploads .upload-button').bind('click', showUploadModal);
+ $('.upload-modal .close-button').bind('click', hideModal);
- $body.on('click', '.embeddable-xml-input', function(){ $(this).select(); });
+ $body.on('click', '.embeddable-xml-input', function(){ $(this).select(); });
- $('.unit .item-actions .delete-button').bind('click', deleteUnit);
- $('.new-unit-item').bind('click', createNewUnit);
+ $('.unit .item-actions .delete-button').bind('click', deleteUnit);
+ $('.new-unit-item').bind('click', createNewUnit);
- // toggling overview section details
- $(function(){
- if($('.courseware-section').length > 0) {
- $('.toggle-button-sections').addClass('is-shown');
- }
- });
- $('.toggle-button-sections').bind('click', toggleSections);
+ // nav-related
+ $('body').addClass('js');
- // autosave when a field is updated on the subsection page
- $body.on('keyup', '.subsection-display-name-input, .unit-subtitle, .policy-list-value', checkForNewValue);
- $('.subsection-display-name-input, .unit-subtitle, .policy-list-name, .policy-list-value').each(function(i) {
- this.val = $(this).val();
- });
- $("#start_date, #start_time, #due_date, #due_time").bind('change', autosaveInput);
- $('.sync-date, .remove-date').bind('click', autosaveInput);
+ $('.nav-dropdown .nav-item .title').click(function(e){
- // expand/collapse methods for optional date setters
- $('.set-date').bind('click', showDateSetter);
- $('.remove-date').bind('click', removeDateSetter);
- // add new/delete section
- $('.new-courseware-section-button').bind('click', addNewSection);
- $('.delete-section-button').bind('click', deleteSection);
-
- // add new/delete subsection
- $('.new-subsection-item').bind('click', addNewSubsection);
- $('.delete-subsection-button').bind('click', deleteSubsection);
- // add/remove policy metadata button click handlers
- $('.add-policy-data').bind('click', addPolicyMetadata);
- $('.remove-policy-data').bind('click', removePolicyMetadata);
- $body.on('click', '.policy-list-element .save-button', savePolicyMetadata);
- $body.on('click', '.policy-list-element .cancel-button', cancelPolicyMetadata);
+ $subnav = $(this).parent().find('.wrapper-nav-sub');
+ $title = $(this).parent().find('.title');
- $('.sync-date').bind('click', syncReleaseDate);
+ e.preventDefault();
- // import form setup
- $('.import .file-input').bind('change', showImportSubmit);
- $('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function(e) {
- e.preventDefault();
- $('.import .file-input').click();
- });
+ if ($subnav.hasClass('is-shown')) {
+ $subnav.removeClass('is-shown');
+ $title.removeClass('is-selected');
+ }
- $('.new-course-button').bind('click', addNewCourse);
+ else {
+ $('.nav-dropdown .nav-item .title').removeClass('is-selected');
+ $('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown');
+ $title.addClass('is-selected');
+ $subnav.addClass('is-shown');
+ }
+ });
- // section name editing
- $('.section-name').bind('click', editSectionName);
- $('.edit-section-name-cancel').bind('click', cancelEditSectionName);
- // $('.edit-section-name-save').bind('click', saveEditSectionName);
+ // general link management - new window/tab
+ $('a[rel="external"]').attr('title','This link will open in a new browser window/tab').click(function(e) {
+ window.open($(this).attr('href'));
+ e.preventDefault();
+ });
- // section date setting
- $('.set-publish-date').bind('click', setSectionScheduleDate);
- $('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate);
- $('.edit-section-start-save').bind('click', saveSetSectionScheduleDate);
+ // general link management - lean modal window
+ $('a[rel="modal"]').attr('title','This link will open in a modal window').leanModal({overlay : 0.50, closeButton: '.action-modal-close' });
+ $('.action-modal-close').click(function(e){
+ (e).preventDefault();
+ });
- $('.upload-modal .choose-file-button').bind('click', showFileSelectionMenu);
+ // toggling overview section details
+ $(function(){
+ if($('.courseware-section').length > 0) {
+ $('.toggle-button-sections').addClass('is-shown');
+ }
+ });
+ $('.toggle-button-sections').bind('click', toggleSections);
- $body.on('click', '.section-published-date .edit-button', editSectionPublishDate);
- $body.on('click', '.section-published-date .schedule-button', editSectionPublishDate);
- $body.on('click', '.edit-subsection-publish-settings .save-button', saveSetSectionScheduleDate);
- $body.on('click', '.edit-subsection-publish-settings .cancel-button', hideModal);
- $body.on('change', '.edit-subsection-publish-settings .start-date', function() {
- if($('.edit-subsection-publish-settings').find('.start-time').val() == '') {
- $('.edit-subsection-publish-settings').find('.start-time').val('12:00am');
- }
- });
- $('.edit-subsection-publish-settings').on('change', '.start-date, .start-time', function() {
- $('.edit-subsection-publish-settings').find('.save-button').show();
- });
+ // autosave when a field is updated on the subsection page
+ $body.on('keyup', '.subsection-display-name-input, .unit-subtitle, .policy-list-value', checkForNewValue);
+ $('.subsection-display-name-input, .unit-subtitle, .policy-list-name, .policy-list-value').each(function(i) {
+ this.val = $(this).val();
+ });
+ $("#start_date, #start_time, #due_date, #due_time").bind('change', autosaveInput);
+ $('.sync-date, .remove-date').bind('click', autosaveInput);
+
+ // expand/collapse methods for optional date setters
+ $('.set-date').bind('click', showDateSetter);
+ $('.remove-date').bind('click', removeDateSetter);
+ // add new/delete section
+ $('.new-courseware-section-button').bind('click', addNewSection);
+ $('.delete-section-button').bind('click', deleteSection);
+
+ // add new/delete subsection
+ $('.new-subsection-item').bind('click', addNewSubsection);
+ $('.delete-subsection-button').bind('click', deleteSubsection);
+ // add/remove policy metadata button click handlers
+ $('.add-policy-data').bind('click', addPolicyMetadata);
+ $('.remove-policy-data').bind('click', removePolicyMetadata);
+ $body.on('click', '.policy-list-element .save-button', savePolicyMetadata);
+ $body.on('click', '.policy-list-element .cancel-button', cancelPolicyMetadata);
+
+ $('.sync-date').bind('click', syncReleaseDate);
+
+ // import form setup
+ $('.import .file-input').bind('change', showImportSubmit);
+ $('.import .choose-file-button, .import .choose-file-button-inline').bind('click', function(e) {
+ e.preventDefault();
+ $('.import .file-input').click();
+ });
+
+ $('.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);
+ $('.edit-section-start-save').bind('click', saveSetSectionScheduleDate);
+
+ $('.upload-modal .choose-file-button').bind('click', showFileSelectionMenu);
+
+ $body.on('click', '.section-published-date .edit-button', editSectionPublishDate);
+ $body.on('click', '.section-published-date .schedule-button', editSectionPublishDate);
+ $body.on('click', '.edit-subsection-publish-settings .save-button', saveSetSectionScheduleDate);
+ $body.on('click', '.edit-subsection-publish-settings .cancel-button', hideModal);
+ $body.on('change', '.edit-subsection-publish-settings .start-date', function() {
+ if($('.edit-subsection-publish-settings').find('.start-time').val() == '') {
+ $('.edit-subsection-publish-settings').find('.start-time').val('12:00am');
+ }
+ });
+ $('.edit-subsection-publish-settings').on('change', '.start-date, .start-time', function() {
+ $('.edit-subsection-publish-settings').find('.save-button').show();
+ });
});
// function collapseAll(e) {
@@ -126,660 +161,660 @@ function toggleSections(e) {
$button.toggleClass('is-activated').html(buttonLabel);
if($button.hasClass('is-activated')) {
- $section.addClass('collapsed');
- // first child in order to avoid the icons on the subsection lists which are not in the first child
- $section.find('header .expand-collapse-icon').removeClass('collapse').addClass('expand');
+ $section.addClass('collapsed');
+ // first child in order to avoid the icons on the subsection lists which are not in the first child
+ $section.find('header .expand-collapse-icon').removeClass('collapse').addClass('expand');
} else {
- $section.removeClass('collapsed');
- // first child in order to avoid the icons on the subsection lists which are not in the first child
- $section.find('header .expand-collapse-icon').removeClass('expand').addClass('collapse');
+ $section.removeClass('collapsed');
+ // first child in order to avoid the icons on the subsection lists which are not in the first child
+ $section.find('header .expand-collapse-icon').removeClass('expand').addClass('collapse');
}
}
function editSectionPublishDate(e) {
- e.preventDefault();
- $modal = $('.edit-subsection-publish-settings').show();
- $modal = $('.edit-subsection-publish-settings').show();
- $modal.attr('data-id', $(this).attr('data-id'));
- $modal.find('.start-date').val($(this).attr('data-date'));
- $modal.find('.start-time').val($(this).attr('data-time'));
- if($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
- $modal.find('.save-button').hide();
- }
- $modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
- $modalCover.show();
+ e.preventDefault();
+ $modal = $('.edit-subsection-publish-settings').show();
+ $modal = $('.edit-subsection-publish-settings').show();
+ $modal.attr('data-id', $(this).attr('data-id'));
+ $modal.find('.start-date').val($(this).attr('data-date'));
+ $modal.find('.start-time').val($(this).attr('data-time'));
+ if($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
+ $modal.find('.save-button').hide();
+ }
+ $modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
+ $modalCover.show();
}
function showImportSubmit(e) {
- var filepath = $(this).val();
- if(filepath.substr(filepath.length - 6, 6) == 'tar.gz') {
- $('.error-block').hide();
- $('.file-name').html($(this).val().replace('C:\\fakepath\\', ''));
- $('.file-name-block').show();
- $('.import .choose-file-button').hide();
- $('.submit-button').show();
- $('.progress').show();
- } else {
- $('.error-block').html('File format not supported. Please upload a file with a tar.gz extension.').show();
- }
+ var filepath = $(this).val();
+ if(filepath.substr(filepath.length - 6, 6) == 'tar.gz') {
+ $('.error-block').hide();
+ $('.file-name').html($(this).val().replace('C:\\fakepath\\', ''));
+ $('.file-name-block').show();
+ $('.import .choose-file-button').hide();
+ $('.submit-button').show();
+ $('.progress').show();
+ } else {
+ $('.error-block').html('File format not supported. Please upload a file with a tar.gz extension.').show();
+ }
}
function syncReleaseDate(e) {
- e.preventDefault();
- $(this).closest('.notice').hide();
- $("#start_date").val("");
- $("#start_time").val("");
+ e.preventDefault();
+ $(this).closest('.notice').hide();
+ $("#start_date").val("");
+ $("#start_time").val("");
}
function addPolicyMetadata(e) {
- e.preventDefault();
- var template =$('#add-new-policy-element-template > li');
- var newNode = template.clone();
- var _parent_el = $(this).parent('ol:.policy-list');
- newNode.insertBefore('.add-policy-data');
- $('.remove-policy-data').bind('click', removePolicyMetadata);
- newNode.find('.policy-list-name').focus();
+ e.preventDefault();
+ var template =$('#add-new-policy-element-template > li');
+ var newNode = template.clone();
+ var _parent_el = $(this).parent('ol:.policy-list');
+ newNode.insertBefore('.add-policy-data');
+ $('.remove-policy-data').bind('click', removePolicyMetadata);
+ newNode.find('.policy-list-name').focus();
}
function savePolicyMetadata(e) {
- e.preventDefault();
+ e.preventDefault();
- var $policyElement = $(this).parents('.policy-list-element');
- saveSubsection()
- $policyElement.removeClass('new-policy-list-element');
- $policyElement.find('.policy-list-name').attr('disabled', 'disabled');
- $policyElement.removeClass('editing');
+ var $policyElement = $(this).parents('.policy-list-element');
+ saveSubsection()
+ $policyElement.removeClass('new-policy-list-element');
+ $policyElement.find('.policy-list-name').attr('disabled', 'disabled');
+ $policyElement.removeClass('editing');
}
function cancelPolicyMetadata(e) {
- e.preventDefault();
+ e.preventDefault();
- var $policyElement = $(this).parents('.policy-list-element');
- if(!$policyElement.hasClass('editing')) {
- $policyElement.remove();
- } else {
- $policyElement.removeClass('new-policy-list-element');
- $policyElement.find('.policy-list-name').val($policyElement.data('currentValues')[0]);
- $policyElement.find('.policy-list-value').val($policyElement.data('currentValues')[1]);
- }
- $policyElement.removeClass('editing');
+ var $policyElement = $(this).parents('.policy-list-element');
+ if(!$policyElement.hasClass('editing')) {
+ $policyElement.remove();
+ } else {
+ $policyElement.removeClass('new-policy-list-element');
+ $policyElement.find('.policy-list-name').val($policyElement.data('currentValues')[0]);
+ $policyElement.find('.policy-list-value').val($policyElement.data('currentValues')[1]);
+ }
+ $policyElement.removeClass('editing');
}
function removePolicyMetadata(e) {
- e.preventDefault();
+ e.preventDefault();
- if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
- return;
+ if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
+ return;
- policy_name = $(this).data('policy-name');
- var _parent_el = $(this).parent('li:.policy-list-element');
- if ($(_parent_el).hasClass("new-policy-list-element")) {
- _parent_el.remove();
- } else {
- _parent_el.appendTo("#policy-to-delete");
- }
- saveSubsection()
+ policy_name = $(this).data('policy-name');
+ var _parent_el = $(this).parent('li:.policy-list-element');
+ if ($(_parent_el).hasClass("new-policy-list-element")) {
+ _parent_el.remove();
+ } else {
+ _parent_el.appendTo("#policy-to-delete");
+ }
+ saveSubsection()
}
function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
- var edxTimeStr = null;
+ var edxTimeStr = null;
- if (date_val != '') {
- if (time_val == '')
- time_val = '00:00';
+ if (date_val != '') {
+ if (time_val == '')
+ time_val = '00:00';
- // Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
- date = Date.parse(date_val + " " + time_val);
- if (format == null)
- format = 'yyyy-MM-ddTHH:mm';
+ // Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
+ date = Date.parse(date_val + " " + time_val);
+ if (format == null)
+ format = 'yyyy-MM-ddTHH:mm';
- edxTimeStr = date.toString(format);
- }
+ edxTimeStr = date.toString(format);
+ }
- return edxTimeStr;
+ return edxTimeStr;
}
function getEdxTimeFromDateTimeInputs(date_id, time_id, format) {
- var input_date = $('#'+date_id).val();
- var input_time = $('#'+time_id).val();
+ var input_date = $('#'+date_id).val();
+ var input_time = $('#'+time_id).val();
- return getEdxTimeFromDateTimeVals(input_date, input_time, format);
+ return getEdxTimeFromDateTimeVals(input_date, input_time, format);
}
function checkForNewValue(e) {
- if($(this).parents('.new-policy-list-element')[0]) {
- return;
+ if($(this).parents('.new-policy-list-element')[0]) {
+ return;
+ }
+
+ if(this.val) {
+ this.hasChanged = this.val != $(this).val();
+ } else {
+ this.hasChanged = false;
+ }
+
+ this.val = $(this).val();
+ if(this.hasChanged) {
+ if(this.saveTimer) {
+ clearTimeout(this.saveTimer);
}
- if(this.val) {
- this.hasChanged = this.val != $(this).val();
- } else {
- this.hasChanged = false;
- }
-
- this.val = $(this).val();
- if(this.hasChanged) {
- if(this.saveTimer) {
- clearTimeout(this.saveTimer);
- }
-
- this.saveTimer = setTimeout(function() {
- $changedInput = $(e.target);
- saveSubsection();
- this.saveTimer = null;
- }, 500);
- }
+ this.saveTimer = setTimeout(function() {
+ $changedInput = $(e.target);
+ saveSubsection();
+ this.saveTimer = null;
+ }, 500);
+ }
}
function autosaveInput(e) {
- if(this.saveTimer) {
- clearTimeout(this.saveTimer);
- }
+ if(this.saveTimer) {
+ clearTimeout(this.saveTimer);
+ }
- this.saveTimer = setTimeout(function() {
- $changedInput = $(e.target);
- saveSubsection();
- this.saveTimer = null;
- }, 500);
+ this.saveTimer = setTimeout(function() {
+ $changedInput = $(e.target);
+ saveSubsection();
+ this.saveTimer = null;
+ }, 500);
}
function saveSubsection() {
- if($changedInput && !$changedInput.hasClass('no-spinner')) {
- $spinner.css({
- 'position': 'absolute',
- 'top': Math.floor($changedInput.position().top + ($changedInput.outerHeight() / 2) + 3),
- 'left': $changedInput.position().left + $changedInput.outerWidth() - 24,
- 'margin-top': '-10px'
- });
- $changedInput.after($spinner);
- $spinner.show();
+ if($changedInput && !$changedInput.hasClass('no-spinner')) {
+ $spinner.css({
+ 'position': 'absolute',
+ 'top': Math.floor($changedInput.position().top + ($changedInput.outerHeight() / 2) + 3),
+ 'left': $changedInput.position().left + $changedInput.outerWidth() - 24,
+ 'margin-top': '-10px'
+ });
+ $changedInput.after($spinner);
+ $spinner.show();
+ }
+
+ var id = $('.subsection-body').data('id');
+
+ // pull all 'normalized' metadata editable fields on page
+ var metadata_fields = $('input[data-metadata-name]');
+
+ var metadata = {};
+ for(var i=0; i< metadata_fields.length;i++) {
+ var el = metadata_fields[i];
+ metadata[$(el).data("metadata-name")] = el.value;
+ }
+
+ // now add 'free-formed' metadata which are presented to the user as dual input fields (name/value)
+ $('ol.policy-list > li.policy-list-element').each( function(i, element) {
+ var name = $(element).children('.policy-list-name').val();
+ metadata[name] = $(element).children('.policy-list-value').val();
+ });
+
+ // now add any 'removed' policy metadata which is stored in a separate hidden div
+ // 'null' presented to the server means 'remove'
+ $("#policy-to-delete > li.policy-list-element").each(function(i, element) {
+ var name = $(element).children('.policy-list-name').val();
+ if (name != "")
+ metadata[name] = null;
+ });
+
+ // Piece back together the date/time UI elements into one date/time string
+ // NOTE: our various "date/time" metadata elements don't always utilize the same formatting string
+ // so make sure we're passing back the correct format
+ metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time');
+ metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time', 'MMMM dd HH:mm');
+
+ $.ajax({
+ url: "/save_item",
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ data:JSON.stringify({ 'id' : id, 'metadata' : metadata}),
+ success: function() {
+ $spinner.delay(500).fadeOut(150);
+ },
+ error: function() {
+ showToastMessage('There has been an error while saving your changes.');
}
-
- var id = $('.subsection-body').data('id');
-
- // pull all 'normalized' metadata editable fields on page
- var metadata_fields = $('input[data-metadata-name]');
-
- var metadata = {};
- for(var i=0; i< metadata_fields.length;i++) {
- var el = metadata_fields[i];
- metadata[$(el).data("metadata-name")] = el.value;
- }
-
- // now add 'free-formed' metadata which are presented to the user as dual input fields (name/value)
- $('ol.policy-list > li.policy-list-element').each( function(i, element) {
- var name = $(element).children('.policy-list-name').val();
- metadata[name] = $(element).children('.policy-list-value').val();
- });
-
- // now add any 'removed' policy metadata which is stored in a separate hidden div
- // 'null' presented to the server means 'remove'
- $("#policy-to-delete > li.policy-list-element").each(function(i, element) {
- var name = $(element).children('.policy-list-name').val();
- if (name != "")
- metadata[name] = null;
- });
-
- // Piece back together the date/time UI elements into one date/time string
- // NOTE: our various "date/time" metadata elements don't always utilize the same formatting string
- // so make sure we're passing back the correct format
- metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time');
- metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time', 'MMMM dd HH:mm');
-
- $.ajax({
- url: "/save_item",
- type: "POST",
- dataType: "json",
- contentType: "application/json",
- data:JSON.stringify({ 'id' : id, 'metadata' : metadata}),
- success: function() {
- $spinner.delay(500).fadeOut(150);
- },
- error: function() {
- showToastMessage('There has been an error while saving your changes.');
- }
- });
+ });
}
function createNewUnit(e) {
- e.preventDefault();
+ e.preventDefault();
- parent = $(this).data('parent');
- template = $(this).data('template');
+ parent = $(this).data('parent');
+ template = $(this).data('template');
- $.post('/clone_item',
- {'parent_location' : parent,
- 'template' : template,
- 'display_name': 'New Unit'
- },
- function(data) {
- // redirect to the edit page
- window.location = "/edit/" + data['id'];
- });
+ $.post('/clone_item',
+ {'parent_location' : parent,
+ 'template' : template,
+ 'display_name': 'New Unit'
+ },
+ function(data) {
+ // redirect to the edit page
+ window.location = "/edit/" + data['id'];
+ });
}
function deleteUnit(e) {
- e.preventDefault();
- _deleteItem($(this).parents('li.leaf'));
+ e.preventDefault();
+ _deleteItem($(this).parents('li.leaf'));
}
function deleteSubsection(e) {
- e.preventDefault();
- _deleteItem($(this).parents('li.branch'));
+ e.preventDefault();
+ _deleteItem($(this).parents('li.branch'));
}
function deleteSection(e) {
- e.preventDefault();
- _deleteItem($(this).parents('section.branch'));
+ e.preventDefault();
+ _deleteItem($(this).parents('section.branch'));
}
function _deleteItem($el) {
- if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
- return;
-
- var id = $el.data('id');
-
- $.post('/delete_item',
- {'id': id, 'delete_children' : true, 'delete_all_versions' : true},
- function(data) {
- $el.remove();
- });
+ if(!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
+ return;
+
+ var id = $el.data('id');
+
+ $.post('/delete_item',
+ {'id': id, 'delete_children' : true, 'delete_all_versions' : true},
+ function(data) {
+ $el.remove();
+ });
}
function showUploadModal(e) {
- e.preventDefault();
- $modal = $('.upload-modal').show();
- $('.file-input').bind('change', startUpload);
- $modalCover.show();
+ e.preventDefault();
+ $modal = $('.upload-modal').show();
+ $('.file-input').bind('change', startUpload);
+ $modalCover.show();
}
function showFileSelectionMenu(e) {
- e.preventDefault();
- $('.file-input').click();
+ e.preventDefault();
+ $('.file-input').click();
}
function startUpload(e) {
- $('.upload-modal h1').html('Uploading…');
- $('.upload-modal .file-name').html($('.file-input').val().replace('C:\\fakepath\\', ''));
- $('.upload-modal .file-chooser').ajaxSubmit({
- beforeSend: resetUploadBar,
- uploadProgress: showUploadFeedback,
- complete: displayFinishedUpload
- });
- $('.upload-modal .choose-file-button').hide();
- $('.upload-modal .progress-bar').removeClass('loaded').show();
+ $('.upload-modal h1').html('Uploading…');
+ $('.upload-modal .file-name').html($('.file-input').val().replace('C:\\fakepath\\', ''));
+ $('.upload-modal .file-chooser').ajaxSubmit({
+ beforeSend: resetUploadBar,
+ uploadProgress: showUploadFeedback,
+ complete: displayFinishedUpload
+ });
+ $('.upload-modal .choose-file-button').hide();
+ $('.upload-modal .progress-bar').removeClass('loaded').show();
}
function resetUploadBar(){
- var percentVal = '0%';
- $('.upload-modal .progress-fill').width(percentVal);
- $('.upload-modal .progress-fill').html(percentVal);
+ var percentVal = '0%';
+ $('.upload-modal .progress-fill').width(percentVal);
+ $('.upload-modal .progress-fill').html(percentVal);
}
function showUploadFeedback(event, position, total, percentComplete) {
- var percentVal = percentComplete + '%';
- $('.upload-modal .progress-fill').width(percentVal);
- $('.upload-modal .progress-fill').html(percentVal);
+ var percentVal = percentComplete + '%';
+ $('.upload-modal .progress-fill').width(percentVal);
+ $('.upload-modal .progress-fill').html(percentVal);
}
function displayFinishedUpload(xhr) {
- if(xhr.status = 200){
- markAsLoaded();
- }
+ if(xhr.status = 200){
+ markAsLoaded();
+ }
- var resp = JSON.parse(xhr.responseText);
- $('.upload-modal .embeddable-xml-input').val(xhr.getResponseHeader('asset_url'));
- $('.upload-modal .embeddable').show();
- $('.upload-modal .file-name').hide();
- $('.upload-modal .progress-fill').html(resp.msg);
- $('.upload-modal .choose-file-button').html('Load Another File').show();
- $('.upload-modal .progress-fill').width('100%');
+ var resp = JSON.parse(xhr.responseText);
+ $('.upload-modal .embeddable-xml-input').val(xhr.getResponseHeader('asset_url'));
+ $('.upload-modal .embeddable').show();
+ $('.upload-modal .file-name').hide();
+ $('.upload-modal .progress-fill').html(resp.msg);
+ $('.upload-modal .choose-file-button').html('Load Another File').show();
+ $('.upload-modal .progress-fill').width('100%');
- // see if this id already exists, if so, then user must have updated an existing piece of content
- $("tr[data-id='" + resp.url + "']").remove();
+ // see if this id already exists, if so, then user must have updated an existing piece of content
+ $("tr[data-id='" + resp.url + "']").remove();
- var template = $('#new-asset-element').html();
- var html = Mustache.to_html(template, resp);
- $('table > tbody').prepend(html);
+ var template = $('#new-asset-element').html();
+ var html = Mustache.to_html(template, resp);
+ $('table > tbody').prepend(html);
}
function markAsLoaded() {
- $('.upload-modal .copy-button').css('display', 'inline-block');
- $('.upload-modal .progress-bar').addClass('loaded');
+ $('.upload-modal .copy-button').css('display', 'inline-block');
+ $('.upload-modal .progress-bar').addClass('loaded');
}
function hideModal(e) {
- if(e) {
- e.preventDefault();
- }
- // Unit editors do not want the modal cover to hide when users click outside
- // of the editor. Users must press Cancel or Save to exit the editor.
- // module_edit adds and removes the "is-fixed" class.
- if (!$modalCover.hasClass("is-fixed")) {
- $('.file-input').unbind('change', startUpload);
- $modal.hide();
- $modalCover.hide();
- }
+ if(e) {
+ e.preventDefault();
+ }
+ // Unit editors do not want the modal cover to hide when users click outside
+ // of the editor. Users must press Cancel or Save to exit the editor.
+ // module_edit adds and removes the "is-fixed" class.
+ if (!$modalCover.hasClass("is-fixed")) {
+ $('.file-input').unbind('change', startUpload);
+ $modal.hide();
+ $modalCover.hide();
+ }
}
function onKeyUp(e) {
- if(e.which == 87) {
- $body.toggleClass('show-wip hide-wip');
- }
+ if(e.which == 87) {
+ $body.toggleClass('show-wip hide-wip');
+ }
}
function toggleSubmodules(e) {
- e.preventDefault();
- $(this).toggleClass('expand').toggleClass('collapse');
- $(this).closest('.branch, .window').toggleClass('collapsed');
+ e.preventDefault();
+ $(this).toggleClass('expand').toggleClass('collapse');
+ $(this).closest('.branch, .window').toggleClass('collapsed');
}
function setVisibility(e) {
- $(this).find('.checked').removeClass('checked');
- $(e.target).closest('.option').addClass('checked');
+ $(this).find('.checked').removeClass('checked');
+ $(e.target).closest('.option').addClass('checked');
}
function editComponent(e) {
- e.preventDefault();
- $(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
+ e.preventDefault();
+ $(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
}
function closeComponentEditor(e) {
- e.preventDefault();
- $(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
+ e.preventDefault();
+ $(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
}
function showDateSetter(e) {
- e.preventDefault();
- var $block = $(this).closest('.due-date-input');
- $(this).hide();
- $block.find('.date-setter').show();
+ e.preventDefault();
+ var $block = $(this).closest('.due-date-input');
+ $(this).hide();
+ $block.find('.date-setter').show();
}
function removeDateSetter(e) {
- e.preventDefault();
- var $block = $(this).closest('.due-date-input');
- $block.find('.date-setter').hide();
- $block.find('.set-date').show();
- // clear out the values
- $block.find('.date').val('');
- $block.find('.time').val('');
+ e.preventDefault();
+ var $block = $(this).closest('.due-date-input');
+ $block.find('.date-setter').hide();
+ $block.find('.set-date').show();
+ // clear out the values
+ $block.find('.date').val('');
+ $block.find('.time').val('');
}
function showToastMessage(message, $button, lifespan) {
- var $toast = $('');
- var $closeBtn = $('×');
- $toast.append($closeBtn);
- var $content = $('');
- $content.html(message);
- $toast.append($content);
- if($button) {
- $button.addClass('action-button');
- $button.bind('click', hideToastMessage);
- $content.append($button);
- }
- $closeBtn.bind('click', hideToastMessage);
+ var $toast = $('');
+ var $closeBtn = $('×');
+ $toast.append($closeBtn);
+ var $content = $('');
+ $content.html(message);
+ $toast.append($content);
+ if($button) {
+ $button.addClass('action-button');
+ $button.bind('click', hideToastMessage);
+ $content.append($button);
+ }
+ $closeBtn.bind('click', hideToastMessage);
- if($('.toast-notification')[0]) {
- var targetY = $('.toast-notification').offset().top + $('.toast-notification').outerHeight();
- $toast.css('top', (targetY + 10) + 'px');
- }
+ if($('.toast-notification')[0]) {
+ var targetY = $('.toast-notification').offset().top + $('.toast-notification').outerHeight();
+ $toast.css('top', (targetY + 10) + 'px');
+ }
- $body.prepend($toast);
- $toast.fadeIn(200);
+ $body.prepend($toast);
+ $toast.fadeIn(200);
- if(lifespan) {
- $toast.timer = setTimeout(function() {
- $toast.fadeOut(300);
- }, lifespan * 1000);
- }
+ if(lifespan) {
+ $toast.timer = setTimeout(function() {
+ $toast.fadeOut(300);
+ }, lifespan * 1000);
+ }
}
function hideToastMessage(e) {
- e.preventDefault();
- $(this).closest('.toast-notification').remove();
+ e.preventDefault();
+ $(this).closest('.toast-notification').remove();
}
function addNewSection(e, isTemplate) {
- e.preventDefault();
+ e.preventDefault();
- $(e.target).addClass('disabled');
+ $(e.target).addClass('disabled');
- var $newSection = $($('#new-section-template').html());
- var $cancelButton = $newSection.find('.new-section-name-cancel');
- $('.courseware-overview').prepend($newSection);
- $newSection.find('.new-section-name').focus().select();
- $newSection.find('.section-name-form').bind('submit', saveNewSection);
- $cancelButton.bind('click', cancelNewSection);
- $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
+ var $newSection = $($('#new-section-template').html());
+ var $cancelButton = $newSection.find('.new-section-name-cancel');
+ $('.courseware-overview').prepend($newSection);
+ $newSection.find('.new-section-name').focus().select();
+ $newSection.find('.section-name-form').bind('submit', saveNewSection);
+ $cancelButton.bind('click', cancelNewSection);
+ $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
}
function checkForCancel(e) {
- if(e.which == 27) {
- $body.unbind('keyup', checkForCancel);
- e.data.$cancelButton.click();
- }
+ if(e.which == 27) {
+ $body.unbind('keyup', checkForCancel);
+ e.data.$cancelButton.click();
+ }
}
function saveNewSection(e) {
- e.preventDefault();
+ e.preventDefault();
- var $saveButton = $(this).find('.new-section-name-save');
- var parent = $saveButton.data('parent');
- var template = $saveButton.data('template');
- var display_name = $(this).find('.new-section-name').val();
+ var $saveButton = $(this).find('.new-section-name-save');
+ var parent = $saveButton.data('parent');
+ var template = $saveButton.data('template');
+ var display_name = $(this).find('.new-section-name').val();
- $.post('/clone_item', {
- 'parent_location' : parent,
- 'template' : template,
- 'display_name': display_name,
- },
- function(data) {
- if (data.id != undefined)
- location.reload();
- }
- );
+ $.post('/clone_item', {
+ 'parent_location' : parent,
+ 'template' : template,
+ 'display_name': display_name,
+ },
+ function(data) {
+ if (data.id != undefined)
+ location.reload();
+ }
+ );
}
function cancelNewSection(e) {
- e.preventDefault();
- $('.new-courseware-section-button').removeClass('disabled');
- $(this).parents('section.new-section').remove();
+ e.preventDefault();
+ $('.new-courseware-section-button').removeClass('disabled');
+ $(this).parents('section.new-section').remove();
}
function addNewCourse(e) {
- e.preventDefault();
+ e.preventDefault();
- $(e.target).hide();
- var $newCourse = $($('#new-course-template').html());
- var $cancelButton = $newCourse.find('.new-course-cancel');
- $('.new-course-button').after($newCourse);
- $newCourse.find('.new-course-name').focus().select();
- $newCourse.find('form').bind('submit', saveNewCourse);
- $cancelButton.bind('click', cancelNewCourse);
- $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
+ $(e.target).hide();
+ var $newCourse = $($('#new-course-template').html());
+ var $cancelButton = $newCourse.find('.new-course-cancel');
+ $('.new-course-button').after($newCourse);
+ $newCourse.find('.new-course-name').focus().select();
+ $newCourse.find('form').bind('submit', saveNewCourse);
+ $cancelButton.bind('click', cancelNewCourse);
+ $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
}
function saveNewCourse(e) {
- e.preventDefault();
+ e.preventDefault();
- var $newCourse = $(this).closest('.new-course');
- var template = $(this).find('.new-course-save').data('template');
- var org = $newCourse.find('.new-course-org').val();
- var number = $newCourse.find('.new-course-number').val();
- var display_name = $newCourse.find('.new-course-name').val();
+ var $newCourse = $(this).closest('.new-course');
+ var template = $(this).find('.new-course-save').data('template');
+ var org = $newCourse.find('.new-course-org').val();
+ var number = $newCourse.find('.new-course-number').val();
+ var display_name = $newCourse.find('.new-course-name').val();
- if (org == '' || number == '' || display_name == ''){
- alert('You must specify all fields in order to create a new course.');
- return;
- }
+ if (org == '' || number == '' || display_name == ''){
+ alert('You must specify all fields in order to create a new course.');
+ return;
+ }
- $.post('/create_new_course', {
- 'template' : template,
- 'org' : org,
- 'number' : number,
- 'display_name': display_name
- },
- function(data) {
- if (data.id != undefined) {
- window.location = '/' + data.id.replace(/.*:\/\//, '');
- } else if (data.ErrMsg != undefined) {
- alert(data.ErrMsg);
- }
- });
+ $.post('/create_new_course', {
+ 'template' : template,
+ 'org' : org,
+ 'number' : number,
+ 'display_name': display_name
+ },
+ function(data) {
+ if (data.id != undefined) {
+ window.location = '/' + data.id.replace(/.*:\/\//, '');
+ } else if (data.ErrMsg != undefined) {
+ alert(data.ErrMsg);
+ }
+ });
}
function cancelNewCourse(e) {
- e.preventDefault();
- $('.new-course-button').show();
- $(this).parents('section.new-course').remove();
+ e.preventDefault();
+ $('.new-course-button').show();
+ $(this).parents('section.new-course').remove();
}
function addNewSubsection(e) {
- e.preventDefault();
- var $section = $(this).closest('.courseware-section');
- var $newSubsection = $($('#new-subsection-template').html());
- $section.find('.subsection-list > ol').append($newSubsection);
- $section.find('.new-subsection-name-input').focus().select();
+ e.preventDefault();
+ var $section = $(this).closest('.courseware-section');
+ var $newSubsection = $($('#new-subsection-template').html());
+ $section.find('.subsection-list > ol').append($newSubsection);
+ $section.find('.new-subsection-name-input').focus().select();
- var $saveButton = $newSubsection.find('.new-subsection-name-save');
- var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
+ var $saveButton = $newSubsection.find('.new-subsection-name-save');
+ var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
- var parent = $(this).parents("section.branch").data("id");
+ var parent = $(this).parents("section.branch").data("id");
- $saveButton.data('parent', parent);
- $saveButton.data('template', $(this).data('template'));
+ $saveButton.data('parent', parent);
+ $saveButton.data('template', $(this).data('template'));
- $newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
- $cancelButton.bind('click', cancelNewSubsection);
- $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
+ $newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
+ $cancelButton.bind('click', cancelNewSubsection);
+ $body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
}
function saveNewSubsection(e) {
- e.preventDefault();
+ e.preventDefault();
- var parent = $(this).find('.new-subsection-name-save').data('parent');
- var template = $(this).find('.new-subsection-name-save').data('template');
+ var parent = $(this).find('.new-subsection-name-save').data('parent');
+ var template = $(this).find('.new-subsection-name-save').data('template');
- var display_name = $(this).find('.new-subsection-name-input').val();
+ var display_name = $(this).find('.new-subsection-name-input').val();
- $.post('/clone_item', {
- 'parent_location' : parent,
- 'template' : template,
- 'display_name': display_name
- },
- function(data) {
- if (data.id != undefined) {
- location.reload();
- }
- }
- );
+ $.post('/clone_item', {
+ 'parent_location' : parent,
+ 'template' : template,
+ 'display_name': display_name
+ },
+ function(data) {
+ if (data.id != undefined) {
+ location.reload();
+ }
+ }
+ );
}
function cancelNewSubsection(e) {
- e.preventDefault();
- $(this).parents('li.branch').remove();
+ e.preventDefault();
+ $(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);
+ 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();
+ 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();
+ e.preventDefault();
- $(this).closest('.section-name').unbind('click', editSectionName);
+ $(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());
+ 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();
+ $(this).closest('.courseware-section .section-name').append($spinner);
+ $spinner.show();
- if (display_name == '') {
- alert("You must specify a name before saving.");
- return;
- }
+ if (display_name == '') {
+ alert("You must specify a name before saving.");
+ return;
+ }
- 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();
- });
+ 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();
- $(this).parent().siblings(".datepair").show();
+ e.preventDefault();
+ $(this).closest("h4").hide();
+ $(this).parent().siblings(".datepair").show();
}
function cancelSetSectionScheduleDate(e) {
- e.preventDefault();
- $(this).closest(".datepair").hide();
- $(this).parent().siblings("h4").show();
+ e.preventDefault();
+ $(this).closest(".datepair").hide();
+ $(this).parent().siblings("h4").show();
}
function saveSetSectionScheduleDate(e) {
- e.preventDefault();
+ e.preventDefault();
- var input_date = $('.edit-subsection-publish-settings .start-date').val();
- var input_time = $('.edit-subsection-publish-settings .start-time').val();
+ var input_date = $('.edit-subsection-publish-settings .start-date').val();
+ var input_time = $('.edit-subsection-publish-settings .start-time').val();
- var start = getEdxTimeFromDateTimeVals(input_date, input_time);
+ var start = getEdxTimeFromDateTimeVals(input_date, input_time);
- var id = $modal.attr('data-id');
+ var id = $modal.attr('data-id');
- // 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' : {'start' : start}})
- }).success(function()
- {
- var $thisSection = $('.courseware-section[data-id="' + id + '"]');
- $thisSection.find('.section-published-date').html('Will Release: ' + input_date + ' at ' + input_time + 'Edit');
- $thisSection.find('.section-published-date').animate({
- 'background-color': 'rgb(182,37,104)'
- }, 300).animate({
- 'background-color': '#edf1f5'
- }, 300).animate({
- 'background-color': 'rgb(182,37,104)'
- }, 300).animate({
- 'background-color': '#edf1f5'
- }, 300);
-
- hideModal();
- });
+ // 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' : {'start' : start}})
+ }).success(function()
+ {
+ var $thisSection = $('.courseware-section[data-id="' + id + '"]');
+ $thisSection.find('.section-published-date').html('Will Release: ' + input_date + ' at ' + input_time + 'Edit');
+ $thisSection.find('.section-published-date').animate({
+ 'background-color': 'rgb(182,37,104)'
+ }, 300).animate({
+ 'background-color': '#edf1f5'
+ }, 300).animate({
+ 'background-color': 'rgb(182,37,104)'
+ }, 300).animate({
+ 'background-color': '#edf1f5'
+ }, 300);
+
+ hideModal();
+ });
}
diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js
index bdbb46b3b1..168cb960be 100644
--- a/cms/static/js/models/settings/course_details.js
+++ b/cms/static/js/models/settings/course_details.js
@@ -1,85 +1,83 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
- defaults: {
- location : null, // the course's Location model, required
- start_date: null, // maps to 'start'
- end_date: null, // maps to 'end'
- enrollment_start: null,
- enrollment_end: null,
- syllabus: null,
- overview: "",
- intro_video: null,
- effort: null // an int or null
- },
-
- // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
- parse: function(attributes) {
- if (attributes['course_location']) {
- attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
- }
- if (attributes['start_date']) {
- attributes.start_date = new Date(attributes.start_date);
- }
- if (attributes['end_date']) {
- attributes.end_date = new Date(attributes.end_date);
- }
- if (attributes['enrollment_start']) {
- attributes.enrollment_start = new Date(attributes.enrollment_start);
- }
- if (attributes['enrollment_end']) {
- attributes.enrollment_end = new Date(attributes.enrollment_end);
- }
- return attributes;
- },
-
- validate: function(newattrs) {
- // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
- // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
- var errors = {};
- if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
- errors.end_date = "The course end date cannot be before the course start date.";
- }
- if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
- errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
- }
- if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
- errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
- }
- if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
- errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
- }
- if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
- if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
- errors.intro_video = "Key should only contain letters, numbers, _, or -";
- }
- // TODO check if key points to a real video using google's youtube api
- }
- if (!_.isEmpty(errors)) return errors;
- // NOTE don't return empty errors as that will be interpreted as an error state
- },
-
- url: function() {
- var location = this.get('location');
- return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details';
- },
-
- _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
- save_videosource: function(newsource) {
- // newsource either is or just the "speed:key, *" string
- // returns the videosource for the preview which iss the key whose speed is closest to 1
- if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null},
- { error : CMS.ServerError});
- // TODO remove all whitespace w/in string
- else {
- if (this.get('intro_video') !== newsource) this.save('intro_video', newsource,
- { error : CMS.ServerError});
- }
-
- return this.videosourceSample();
- },
- videosourceSample : function() {
- if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
- else return "";
- }
+ defaults: {
+ location : null, // the course's Location model, required
+ start_date: null, // maps to 'start'
+ end_date: null, // maps to 'end'
+ enrollment_start: null,
+ enrollment_end: null,
+ syllabus: null,
+ overview: "",
+ intro_video: null,
+ effort: null // an int or null
+ },
+
+ // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
+ parse: function(attributes) {
+ if (attributes['course_location']) {
+ attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
+ }
+ if (attributes['start_date']) {
+ attributes.start_date = new Date(attributes.start_date);
+ }
+ if (attributes['end_date']) {
+ attributes.end_date = new Date(attributes.end_date);
+ }
+ if (attributes['enrollment_start']) {
+ attributes.enrollment_start = new Date(attributes.enrollment_start);
+ }
+ if (attributes['enrollment_end']) {
+ attributes.enrollment_end = new Date(attributes.enrollment_end);
+ }
+ return attributes;
+ },
+
+ validate: function(newattrs) {
+ // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
+ // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
+ var errors = {};
+ if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
+ errors.end_date = "The course end date cannot be before the course start date.";
+ }
+ if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
+ errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
+ }
+ if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
+ errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
+ }
+ if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
+ errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
+ }
+ if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
+ if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
+ errors.intro_video = "Key should only contain letters, numbers, _, or -";
+ }
+ // TODO check if key points to a real video using google's youtube api
+ }
+ if (!_.isEmpty(errors)) return errors;
+ // NOTE don't return empty errors as that will be interpreted as an error state
+ },
+
+ url: function() {
+ var location = this.get('location');
+ return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/details';
+ },
+
+ _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
+ save_videosource: function(newsource) {
+ // newsource either is or just the "speed:key, *" string
+ // returns the videosource for the preview which iss the key whose speed is closest to 1
+ if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.set({'intro_video': null});
+ // TODO remove all whitespace w/in string
+ else {
+ if (this.get('intro_video') !== newsource) this.set('intro_video', newsource);
+ }
+
+ return this.videosourceSample();
+ },
+ videosourceSample : function() {
+ if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
+ else return "";
+ }
});
diff --git a/cms/static/js/models/settings/course_grading_policy.js b/cms/static/js/models/settings/course_grading_policy.js
index cce4e0207d..3f8b1bf29a 100644
--- a/cms/static/js/models/settings/course_grading_policy.js
+++ b/cms/static/js/models/settings/course_grading_policy.js
@@ -1,55 +1,56 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
- defaults : {
- course_location : null,
- graders : null, // CourseGraderCollection
- grade_cutoffs : null, // CourseGradeCutoff model
+ defaults : {
+ course_location : null,
+ graders : null, // CourseGraderCollection
+ grade_cutoffs : null, // CourseGradeCutoff model
grace_period : null // either null or { hours: n, minutes: m, ...}
- },
- parse: function(attributes) {
- if (attributes['course_location']) {
- attributes.course_location = new CMS.Models.Location(attributes.course_location, {parse:true});
- }
- if (attributes['graders']) {
- var graderCollection;
- if (this.has('graders')) {
- graderCollection = this.get('graders');
- graderCollection.reset(attributes.graders);
- }
- else {
- graderCollection = new CMS.Models.Settings.CourseGraderCollection(attributes.graders);
- graderCollection.course_location = attributes['course_location'] || this.get('course_location');
- }
- attributes.graders = graderCollection;
- }
- return attributes;
- },
- url : function() {
- var location = this.get('course_location');
- return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/grading';
- },
- gracePeriodToDate : function() {
- var newDate = new Date();
- if (this.has('grace_period') && this.get('grace_period')['hours'])
- newDate.setHours(this.get('grace_period')['hours']);
- else newDate.setHours(0);
- if (this.has('grace_period') && this.get('grace_period')['minutes'])
- newDate.setMinutes(this.get('grace_period')['minutes']);
- else newDate.setMinutes(0);
- if (this.has('grace_period') && this.get('grace_period')['seconds'])
- newDate.setSeconds(this.get('grace_period')['seconds']);
- else newDate.setSeconds(0);
-
- return newDate;
- },
- dateToGracePeriod : function(date) {
- return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() };
- }
+ },
+ parse: function(attributes) {
+ if (attributes['course_location']) {
+ attributes.course_location = new CMS.Models.Location(attributes.course_location, {parse:true});
+ }
+ if (attributes['graders']) {
+ var graderCollection;
+ // interesting race condition: if {parse:true} when newing, then parse called before .attributes created
+ if (this.attributes && this.has('graders')) {
+ graderCollection = this.get('graders');
+ graderCollection.reset(attributes.graders);
+ }
+ else {
+ graderCollection = new CMS.Models.Settings.CourseGraderCollection(attributes.graders);
+ graderCollection.course_location = attributes['course_location'] || this.get('course_location');
+ }
+ attributes.graders = graderCollection;
+ }
+ return attributes;
+ },
+ url : function() {
+ var location = this.get('course_location');
+ return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/grading';
+ },
+ gracePeriodToDate : function() {
+ var newDate = new Date();
+ if (this.has('grace_period') && this.get('grace_period')['hours'])
+ newDate.setHours(this.get('grace_period')['hours']);
+ else newDate.setHours(0);
+ if (this.has('grace_period') && this.get('grace_period')['minutes'])
+ newDate.setMinutes(this.get('grace_period')['minutes']);
+ else newDate.setMinutes(0);
+ if (this.has('grace_period') && this.get('grace_period')['seconds'])
+ newDate.setSeconds(this.get('grace_period')['seconds']);
+ else newDate.setSeconds(0);
+
+ return newDate;
+ },
+ dateToGracePeriod : function(date) {
+ return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() };
+ }
});
CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
- defaults: {
+ defaults: {
"type" : "", // must be unique w/in collection (ie. w/in course)
"min_count" : 1,
"drop_count" : 0,
@@ -57,71 +58,71 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
"weight" : 0 // int 0..100
},
parse : function(attrs) {
- if (attrs['weight']) {
- if (!_.isNumber(attrs.weight)) attrs.weight = parseInt(attrs.weight);
- }
- if (attrs['min_count']) {
- if (!_.isNumber(attrs.min_count)) attrs.min_count = parseInt(attrs.min_count);
- }
- if (attrs['drop_count']) {
- if (!_.isNumber(attrs.drop_count)) attrs.drop_count = parseInt(attrs.drop_count);
- }
- return attrs;
+ if (attrs['weight']) {
+ if (!_.isNumber(attrs.weight)) attrs.weight = parseInt(attrs.weight);
+ }
+ if (attrs['min_count']) {
+ if (!_.isNumber(attrs.min_count)) attrs.min_count = parseInt(attrs.min_count);
+ }
+ if (attrs['drop_count']) {
+ if (!_.isNumber(attrs.drop_count)) attrs.drop_count = parseInt(attrs.drop_count);
+ }
+ return attrs;
},
validate : function(attrs) {
- var errors = {};
- if (attrs['type']) {
- if (_.isEmpty(attrs['type'])) {
- errors.type = "The assignment type must have a name.";
- }
- else {
- // FIXME somehow this.collection is unbound sometimes. I can't track down when
- var existing = this.collection && this.collection.some(function(other) { return (other != this) && (other.get('type') == attrs['type']);}, this);
- if (existing) {
- errors.type = "There's already another assignment type with this name.";
- }
- }
- }
- if (attrs['weight']) {
- if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) {
- errors.weight = "Please enter an integer between 0 and 100.";
- }
- else {
- attrs.weight = parseInt(attrs.weight); // see if this ensures value saved is int
- if (this.collection && attrs.weight > 0) {
- // FIXME b/c saves don't update the models if validation fails, we should
- // either revert the field value to the one in the model and make them make room
- // or figure out a wholistic way to balance the vals across the whole
-// if ((this.collection.sumWeights() + attrs.weight - this.get('weight')) > 100)
-// errors.weight = "The weights cannot add to more than 100.";
- }
- }}
- if (attrs['min_count']) {
- if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
- errors.min_count = "Please enter an integer.";
- }
- else attrs.min_count = parseInt(attrs.min_count);
- }
- if (attrs['drop_count']) {
- if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
- errors.drop_count = "Please enter an integer.";
- }
- else attrs.drop_count = parseInt(attrs.drop_count);
- }
- if (attrs['min_count'] && attrs['drop_count'] && attrs.drop_count > attrs.min_count) {
- errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned.";
- }
- if (!_.isEmpty(errors)) return errors;
+ var errors = {};
+ if (attrs['type']) {
+ if (_.isEmpty(attrs['type'])) {
+ errors.type = "The assignment type must have a name.";
+ }
+ else {
+ // FIXME somehow this.collection is unbound sometimes. I can't track down when
+ var existing = this.collection && this.collection.some(function(other) { return (other != this) && (other.get('type') == attrs['type']);}, this);
+ if (existing) {
+ errors.type = "There's already another assignment type with this name.";
+ }
+ }
+ }
+ if (attrs['weight']) {
+ if (!isFinite(attrs.weight) || /\D+/.test(attrs.weight)) {
+ errors.weight = "Please enter an integer between 0 and 100.";
+ }
+ else {
+ attrs.weight = parseInt(attrs.weight); // see if this ensures value saved is int
+ if (this.collection && attrs.weight > 0) {
+ // FIXME b/c saves don't update the models if validation fails, we should
+ // either revert the field value to the one in the model and make them make room
+ // or figure out a wholistic way to balance the vals across the whole
+// if ((this.collection.sumWeights() + attrs.weight - this.get('weight')) > 100)
+// errors.weight = "The weights cannot add to more than 100.";
+ }
+ }}
+ if (attrs['min_count']) {
+ if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
+ errors.min_count = "Please enter an integer.";
+ }
+ else attrs.min_count = parseInt(attrs.min_count);
+ }
+ if (attrs['drop_count']) {
+ if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
+ errors.drop_count = "Please enter an integer.";
+ }
+ else attrs.drop_count = parseInt(attrs.drop_count);
+ }
+ if (attrs['min_count'] && attrs['drop_count'] && attrs.drop_count > attrs.min_count) {
+ errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned.";
+ }
+ if (!_.isEmpty(errors)) return errors;
}
});
CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({
- model : CMS.Models.Settings.CourseGrader,
- course_location : null, // must be set to a Location object
- url : function() {
- return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/grades/' + this.course_location.get('name') + '/';
- },
- sumWeights : function() {
- return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
- }
+ model : CMS.Models.Settings.CourseGrader,
+ course_location : null, // must be set to a Location object
+ url : function() {
+ return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/settings-grading/' + this.course_location.get('name') + '/';
+ },
+ sumWeights : function() {
+ return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
+ }
});
\ No newline at end of file
diff --git a/cms/static/js/models/settings/course_settings.js b/cms/static/js/models/settings/course_settings.js
index 9d09e4bdc5..62b214e853 100644
--- a/cms/static/js/models/settings/course_settings.js
+++ b/cms/static/js/models/settings/course_settings.js
@@ -1,43 +1,42 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseSettings = Backbone.Model.extend({
- // a container for the models representing the n possible tabbed states
- defaults: {
- courseLocation: null,
- // NOTE: keep these sync'd w/ the data-section names in settings-page-menu
- details: null,
- faculty: null,
- grading: null,
- problems: null,
- discussions: null
- },
+ // a container for the models representing the n possible tabbed states
+ defaults: {
+ courseLocation: null,
+ details: null,
+ faculty: null,
+ grading: null,
+ problems: null,
+ discussions: null
+ },
- retrieve: function(submodel, callback) {
- if (this.get(submodel)) callback();
- else {
- var cachethis = this;
- switch (submodel) {
- case 'details':
- var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
- details.fetch( {
- success : function(model) {
- cachethis.set('details', model);
- callback(model);
- }
- });
- break;
- case 'grading':
- var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
- grading.fetch( {
- success : function(model) {
- cachethis.set('grading', model);
- callback(model);
- }
- });
- break;
+ retrieve: function(submodel, callback) {
+ if (this.get(submodel)) callback();
+ else {
+ var cachethis = this;
+ switch (submodel) {
+ case 'details':
+ var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
+ details.fetch( {
+ success : function(model) {
+ cachethis.set('details', model);
+ callback(model);
+ }
+ });
+ break;
+ case 'grading':
+ var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
+ grading.fetch( {
+ success : function(model) {
+ cachethis.set('grading', model);
+ callback(model);
+ }
+ });
+ break;
- default:
- break;
- }
- }
- }
+ default:
+ break;
+ }
+ }
+ }
})
\ No newline at end of file
diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js
index f4c7df41a6..6f96ed574e 100644
--- a/cms/static/js/views/settings/main_settings_view.js
+++ b/cms/static/js/views/settings/main_settings_view.js
@@ -1,222 +1,91 @@
-if (!CMS.Views['Settings']) CMS.Views.Settings = {};
-
-// TODO move to common place
-CMS.Views.ValidatingView = Backbone.View.extend({
- // Intended as an abstract class which catches validation errors on the model and
- // 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.model.on('error', this.handleValidationError, this);
- this.selectorToField = _.invert(this.fieldToSelectorMap);
- },
-
- errorTemplate : _.template('<%= message %>'),
-
- events : {
- "blur input" : "clearValidationErrors",
- "blur textarea" : "clearValidationErrors"
- },
- fieldToSelectorMap : {
- // Your subclass must populate this w/ all of the model keys and dom selectors
- // which may be the subjects of validation errors
- },
- _cacheValidationErrors : [],
- handleValidationError : function(model, error) {
- // error is object w/ fields and error strings
- for (var field in error) {
- var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
- this._cacheValidationErrors.push(ele);
- if ($(ele).is('div')) {
- // put error on the contained inputs
- $(ele).find('input, textarea').addClass('error');
- }
- else $(ele).addClass('error');
- $(ele).parent().append(this.errorTemplate({message : error[field]}));
- }
- },
-
- clearValidationErrors : function() {
- // error is object w/ fields and error strings
- while (this._cacheValidationErrors.length > 0) {
- var ele = this._cacheValidationErrors.pop();
- if ($(ele).is('div')) {
- // put error on the contained inputs
- $(ele).find('input, textarea').removeClass('error');
- }
- else $(ele).removeClass('error');
- $(ele).nextAll('.message-error').remove();
- }
- },
-
- saveIfChanged : function(event) {
- // returns true if the value changed and was thus sent to server
- var field = this.selectorToField[event.currentTarget.id];
- var currentVal = this.model.get(field);
- var newVal = $(event.currentTarget).val();
- if (currentVal != newVal) {
- this.clearValidationErrors();
- this.model.save(field, newVal, { error : CMS.ServerError});
- return true;
- }
- else return false;
- }
-});
-
-CMS.Views.Settings.Main = Backbone.View.extend({
- // Model class is CMS.Models.Settings.CourseSettings
- // allow navigation between the tabs
- events: {
- 'click .settings-page-menu a': "showSettingsTab",
- 'mouseover #timezone' : "updateTime"
- },
-
- currentTab: null,
- subviews: {}, // indexed by tab name
-
- initialize: function() {
- // load templates
- this.currentTab = this.$el.find('.settings-page-menu .is-shown').attr('data-section');
- // create the initial subview
- this.subviews[this.currentTab] = this.createSubview();
-
- // fill in fields
- this.$el.find("#course-name").val(this.model.get('courseLocation').get('name'));
- this.$el.find("#course-organization").val(this.model.get('courseLocation').get('org'));
- this.$el.find("#course-number").val(this.model.get('courseLocation').get('course'));
- this.$el.find('.set-date').datepicker({ 'dateFormat': 'm/d/yy' });
- this.$el.find(":input, textarea").focus(function() {
- $("label[for='" + this.id + "']").addClass("is-focused");
- }).blur(function() {
- $("label").removeClass("is-focused");
- });
- this.render();
- },
-
- render: function() {
-
- // create any necessary subviews and put them onto the page
- if (!this.model.has(this.currentTab)) {
- // TODO disable screen until fetch completes?
- var cachethis = this;
- this.model.retrieve(this.currentTab, function() {
- cachethis.subviews[cachethis.currentTab] = cachethis.createSubview();
- cachethis.subviews[cachethis.currentTab].render();
- });
- }
- else this.subviews[this.currentTab].render();
-
- var dateIntrospect = new Date();
- this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
-
- return this;
- },
-
- createSubview: function() {
- switch (this.currentTab) {
- case 'details':
- return new CMS.Views.Settings.Details({
- el: this.$el.find('.settings-' + this.currentTab),
- model: this.model.get(this.currentTab)
- });
- case 'faculty':
- break;
- case 'grading':
- return new CMS.Views.Settings.Grading({
- el: this.$el.find('.settings-' + this.currentTab),
- model: this.model.get(this.currentTab)
- });
- case 'problems':
- break;
- case 'discussions':
- break;
- }
- },
-
- updateTime : function(e) {
- var now = new Date();
- var hours = now.getHours();
- var minutes = now.getMinutes();
- $(e.currentTarget).attr('title', (hours % 12 === 0 ? 12 : hours % 12) + ":" + (minutes < 10 ? "0" : "") +
- now.getMinutes() + (hours < 12 ? "am" : "pm") + " (current local time)");
- },
-
- showSettingsTab: function(e) {
- this.currentTab = $(e.target).attr('data-section');
- $('.settings-page-section > section').hide();
- $('.settings-' + this.currentTab).show();
- $('.settings-page-menu .is-shown').removeClass('is-shown');
- $(e.target).addClass('is-shown');
- // fetch model for the tab if not loaded already
- this.render();
- }
-
-});
+if (!CMS.Views['Settings']) CMS.Views.Settings = {}; // ensure the pseudo pkg exists
CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
- // Model class is CMS.Models.Settings.CourseDetails
- events : {
- "blur input" : "updateModel",
- "blur textarea" : "updateModel",
- 'click .remove-course-syllabus' : "removeSyllabus",
- 'click .new-course-syllabus' : 'assetSyllabus',
- 'click .remove-course-introduction-video' : "removeVideo",
- 'focus #course-overview' : "codeMirrorize"
- },
- initialize : function() {
- // TODO move the html frag to a loaded asset
- this.fileAnchorTemplate = _.template('📄<%= filename %>');
- this.model.on('error', this.handleValidationError, this);
- this.selectorToField = _.invert(this.fieldToSelectorMap);
- },
-
- render: function() {
- this.setupDatePicker('start_date');
- this.setupDatePicker('end_date');
- this.setupDatePicker('enrollment_start');
- this.setupDatePicker('enrollment_end');
-
- if (this.model.has('syllabus')) {
- this.$el.find(this.fieldToSelectorMap['syllabus']).html(
- this.fileAnchorTemplate({
- fullpath : this.model.get('syllabus'),
- filename: 'syllabus'}));
- this.$el.find('.remove-course-syllabus').show();
- }
- else {
- this.$el.find('#' + this.fieldToSelectorMap['syllabus']).html("");
- this.$el.find('.remove-course-syllabus').hide();
- }
-
- this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
- this.codeMirrorize(null, $('#course-overview')[0]);
-
- this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
- if (this.model.has('intro_video')) {
- this.$el.find('.remove-course-introduction-video').show();
- this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video'));
- }
- else this.$el.find('.remove-course-introduction-video').hide();
-
- this.$el.find('#' + this.fieldToSelectorMap['effort']).val(this.model.get('effort'));
-
- return this;
- },
- fieldToSelectorMap : {
- 'start_date' : "course-start",
- 'end_date' : 'course-end',
- 'enrollment_start' : 'enrollment-start',
- 'enrollment_end' : 'enrollment-end',
- 'syllabus' : '.current-course-syllabus .doc-filename',
- 'overview' : 'course-overview',
- 'intro_video' : 'course-introduction-video',
- 'effort' : "course-effort"
- },
+ // Model class is CMS.Models.Settings.CourseDetails
+ events : {
+ "blur input" : "updateModel",
+ "blur textarea" : "updateModel",
+ 'click .remove-course-syllabus' : "removeSyllabus",
+ 'click .new-course-syllabus' : 'assetSyllabus',
+ 'click .remove-course-introduction-video' : "removeVideo",
+ 'focus #course-overview' : "codeMirrorize",
+ 'mouseover #timezone' : "updateTime",
+ // would love to move to a general superclass, but event hashes don't inherit in backbone :-(
+ 'focus :input' : "inputFocus",
+ 'blur :input' : "inputUnfocus"
+
+ },
+ initialize : function() {
+ this.fileAnchorTemplate = _.template('📄<%= filename %>');
+ // 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'));
+ this.$el.find("#course-number").val(this.model.get('location').get('course'));
+ this.$el.find('.set-date').datepicker({ 'dateFormat': 'm/d/yy' });
+
+ var dateIntrospect = new Date();
+ this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
+
+ this.model.on('error', this.handleValidationError, this);
+ this.selectorToField = _.invert(this.fieldToSelectorMap);
+ },
+
+ render: function() {
+ this.setupDatePicker('start_date');
+ this.setupDatePicker('end_date');
+ this.setupDatePicker('enrollment_start');
+ this.setupDatePicker('enrollment_end');
+
+ if (this.model.has('syllabus')) {
+ this.$el.find(this.fieldToSelectorMap['syllabus']).html(
+ this.fileAnchorTemplate({
+ fullpath : this.model.get('syllabus'),
+ filename: 'syllabus'}));
+ this.$el.find('.remove-course-syllabus').show();
+ }
+ else {
+ this.$el.find('#' + this.fieldToSelectorMap['syllabus']).html("");
+ this.$el.find('.remove-course-syllabus').hide();
+ }
+
+ this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
+ this.codeMirrorize(null, $('#course-overview')[0]);
+
+ this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
+ if (this.model.has('intro_video')) {
+ this.$el.find('.remove-course-introduction-video').show();
+ this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video'));
+ }
+ else this.$el.find('.remove-course-introduction-video').hide();
+
+ this.$el.find('#' + this.fieldToSelectorMap['effort']).val(this.model.get('effort'));
+
+ return this;
+ },
+ fieldToSelectorMap : {
+ 'start_date' : "course-start",
+ 'end_date' : 'course-end',
+ 'enrollment_start' : 'enrollment-start',
+ 'enrollment_end' : 'enrollment-end',
+ 'syllabus' : '.current-course-syllabus .doc-filename',
+ 'overview' : 'course-overview',
+ 'intro_video' : 'course-introduction-video',
+ 'effort' : "course-effort"
+ },
+
+ updateTime : function(e) {
+ var now = new Date();
+ var hours = now.getHours();
+ var minutes = now.getMinutes();
+ $(e.currentTarget).attr('title', (hours % 12 === 0 ? 12 : hours % 12) + ":" + (minutes < 10 ? "0" : "") +
+ now.getMinutes() + (hours < 12 ? "am" : "pm") + " (current local time)");
+ },
setupDatePicker: function (fieldName) {
var cacheModel = this.model;
var div = this.$el.find('#' + this.fieldToSelectorMap[fieldName]);
- var datefield = $(div).find(".date");
- var timefield = $(div).find(".time");
+ var datefield = $(div).find("input:.date");
+ var timefield = $(div).find("input:.time");
var cachethis = this;
var savefield = function () {
cachethis.clearValidationErrors();
@@ -228,7 +97,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
}
var newVal = new Date(date.getTime() + time * 1000);
if (!cacheModel.has(fieldName) || cacheModel.get(fieldName).getTime() !== newVal.getTime()) {
- cacheModel.save(fieldName, newVal, { error: CMS.ServerError});
+ cacheModel.save(fieldName, newVal);
}
}
};
@@ -245,58 +114,57 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
datefield.datepicker('setDate', this.model.get(fieldName));
if (this.model.has(fieldName)) timefield.timepicker('setTime', this.model.get(fieldName));
},
-
- updateModel: function(event) {
- switch (event.currentTarget.id) {
- case 'course-start-date': // handled via onSelect method
- case 'course-end-date':
- case 'course-enrollment-start-date':
- case 'course-enrollment-end-date':
- break;
- case 'course-overview':
- // handled via code mirror
- break;
+ updateModel: function(event) {
+ switch (event.currentTarget.id) {
+ case 'course-start-date': // handled via onSelect method
+ case 'course-end-date':
+ case 'course-enrollment-start-date':
+ case 'course-enrollment-end-date':
+ break;
- case 'course-effort':
- this.saveIfChanged(event);
- break;
- case 'course-introduction-video':
- this.clearValidationErrors();
- var previewsource = this.model.save_videosource($(event.currentTarget).val());
- this.$el.find(".current-course-introduction-video iframe").attr("src", previewsource);
- if (this.model.has('intro_video')) {
- this.$el.find('.remove-course-introduction-video').show();
- }
- else {
- this.$el.find('.remove-course-introduction-video').hide();
- }
- break;
-
- default:
- break;
- }
-
- },
-
- removeSyllabus: function() {
- if (this.model.has('syllabus')) this.model.save({'syllabus': null},
- { error : CMS.ServerError});
- },
-
- assetSyllabus : function() {
- // TODO implement
- },
-
- removeVideo: function() {
- if (this.model.has('intro_video')) {
- this.model.save_videosource(null);
- this.$el.find(".current-course-introduction-video iframe").attr("src", "");
- this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val("");
- this.$el.find('.remove-course-introduction-video').hide();
- }
- },
- codeMirrors : {},
+ case 'course-overview':
+ // handled via code mirror
+ break;
+
+ case 'course-effort':
+ this.saveIfChanged(event);
+ break;
+ case 'course-introduction-video':
+ this.clearValidationErrors();
+ var previewsource = this.model.save_videosource($(event.currentTarget).val());
+ this.$el.find(".current-course-introduction-video iframe").attr("src", previewsource);
+ if (this.model.has('intro_video')) {
+ this.$el.find('.remove-course-introduction-video').show();
+ }
+ else {
+ this.$el.find('.remove-course-introduction-video').hide();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ },
+
+ removeSyllabus: function() {
+ if (this.model.has('syllabus')) this.model.save({'syllabus': null});
+ },
+
+ assetSyllabus : function() {
+ // TODO implement
+ },
+
+ removeVideo: function() {
+ if (this.model.has('intro_video')) {
+ this.model.save_videosource(null);
+ this.$el.find(".current-course-introduction-video iframe").attr("src", "");
+ this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val("");
+ this.$el.find('.remove-course-introduction-video').hide();
+ }
+ },
+ codeMirrors : {},
codeMirrorize: function (e, forcedTarget) {
var thisTarget;
if (forcedTarget) {
@@ -315,374 +183,11 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
mirror.save();
cachethis.clearValidationErrors();
var newVal = mirror.getValue();
- if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal,
- { error: CMS.ServerError});
+ if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal);
}
});
}
}
-
-});
-
-CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
- // Model class is CMS.Models.Settings.CourseGradingPolicy
- events : {
- "blur input" : "updateModel",
- "blur textarea" : "updateModel",
- "blur span[contenteditable=true]" : "updateDesignation",
- "click .settings-extra header" : "showSettingsExtras",
- "click .new-grade-button" : "addNewGrade",
- "click .remove-button" : "removeGrade",
- "click .add-grading-data" : "addAssignmentType"
- },
- initialize : function() {
- // load template for grading view
- var self = this;
- this.gradeCutoffTemplate = _.template('