From 7b9c566f3d4b489634a65e84c9cade12594b1219 Mon Sep 17 00:00:00 2001 From: alisan617 Date: Thu, 6 Oct 2016 10:52:50 -0400 Subject: [PATCH 1/2] convert coffeeScript files in instructor dashboard to JS --- .../course_info.coffee => js/instructor_dashboard/course_info.js} | 0 .../instructor_dashboard/data_download.js} | 0 .../e-commerce.coffee => js/instructor_dashboard/e-commerce.js} | 0 .../extensions.coffee => js/instructor_dashboard/extensions.js} | 0 .../instructor_dashboard/instructor_dashboard.js} | 0 .../membership.coffee => js/instructor_dashboard/membership.js} | 0 .../metrics.coffee => js/instructor_dashboard/metrics.js} | 0 .../send_email.coffee => js/instructor_dashboard/send_email.js} | 0 .../instructor_dashboard/student_admin.js} | 0 .../util.coffee => js/instructor_dashboard/util.js} | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename lms/static/{coffee/src/instructor_dashboard/course_info.coffee => js/instructor_dashboard/course_info.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/data_download.coffee => js/instructor_dashboard/data_download.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/e-commerce.coffee => js/instructor_dashboard/e-commerce.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/extensions.coffee => js/instructor_dashboard/extensions.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/instructor_dashboard.coffee => js/instructor_dashboard/instructor_dashboard.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/membership.coffee => js/instructor_dashboard/membership.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/metrics.coffee => js/instructor_dashboard/metrics.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/send_email.coffee => js/instructor_dashboard/send_email.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/student_admin.coffee => js/instructor_dashboard/student_admin.js} (100%) rename lms/static/{coffee/src/instructor_dashboard/util.coffee => js/instructor_dashboard/util.js} (100%) diff --git a/lms/static/coffee/src/instructor_dashboard/course_info.coffee b/lms/static/js/instructor_dashboard/course_info.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/course_info.coffee rename to lms/static/js/instructor_dashboard/course_info.js diff --git a/lms/static/coffee/src/instructor_dashboard/data_download.coffee b/lms/static/js/instructor_dashboard/data_download.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/data_download.coffee rename to lms/static/js/instructor_dashboard/data_download.js diff --git a/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee b/lms/static/js/instructor_dashboard/e-commerce.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/e-commerce.coffee rename to lms/static/js/instructor_dashboard/e-commerce.js diff --git a/lms/static/coffee/src/instructor_dashboard/extensions.coffee b/lms/static/js/instructor_dashboard/extensions.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/extensions.coffee rename to lms/static/js/instructor_dashboard/extensions.js diff --git a/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee b/lms/static/js/instructor_dashboard/instructor_dashboard.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee rename to lms/static/js/instructor_dashboard/instructor_dashboard.js diff --git a/lms/static/coffee/src/instructor_dashboard/membership.coffee b/lms/static/js/instructor_dashboard/membership.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/membership.coffee rename to lms/static/js/instructor_dashboard/membership.js diff --git a/lms/static/coffee/src/instructor_dashboard/metrics.coffee b/lms/static/js/instructor_dashboard/metrics.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/metrics.coffee rename to lms/static/js/instructor_dashboard/metrics.js diff --git a/lms/static/coffee/src/instructor_dashboard/send_email.coffee b/lms/static/js/instructor_dashboard/send_email.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/send_email.coffee rename to lms/static/js/instructor_dashboard/send_email.js diff --git a/lms/static/coffee/src/instructor_dashboard/student_admin.coffee b/lms/static/js/instructor_dashboard/student_admin.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/student_admin.coffee rename to lms/static/js/instructor_dashboard/student_admin.js diff --git a/lms/static/coffee/src/instructor_dashboard/util.coffee b/lms/static/js/instructor_dashboard/util.js similarity index 100% rename from lms/static/coffee/src/instructor_dashboard/util.coffee rename to lms/static/js/instructor_dashboard/util.js From 5fe397f8c9824823b2299121397677ee1c2b5b8e Mon Sep 17 00:00:00 2001 From: alisan617 Date: Thu, 6 Oct 2016 11:09:10 -0400 Subject: [PATCH 2/2] change Instructor Dashboard coffeeScript bundle path and move fixtures to JS folder, plus eslint --- lms/envs/common.py | 5 +- .../membership_spec.coffee | 67 - .../send_email_spec.coffee | 92 - .../instructor_dashboard}/autoenrollment.html | 0 .../instructor_dashboard}/send_email.html | 0 .../js/instructor_dashboard/course_info.js | 89 +- .../js/instructor_dashboard/data_download.js | 649 ++++--- .../js/instructor_dashboard/e-commerce.js | 166 +- .../js/instructor_dashboard/extensions.js | 325 ++-- .../instructor_dashboard.js | 333 ++-- .../js/instructor_dashboard/membership.js | 1624 ++++++++++------- lms/static/js/instructor_dashboard/metrics.js | 36 +- .../js/instructor_dashboard/send_email.js | 415 +++-- .../js/instructor_dashboard/student_admin.js | 940 ++++++---- lms/static/js/instructor_dashboard/util.js | 918 +++++----- .../data_download_spec.js | 2 +- .../instructor_dashboard/membership_spec.js | 93 + .../instructor_dashboard/send_email_spec.js | 126 ++ .../student_admin_spec.js | 281 +-- lms/static/lms/js/spec/main.js | 12 +- 20 files changed, 3454 insertions(+), 2719 deletions(-) delete mode 100644 lms/static/coffee/spec/instructor_dashboard/membership_spec.coffee delete mode 100644 lms/static/coffee/spec/instructor_dashboard/send_email_spec.coffee rename lms/static/{coffee/fixtures => js/fixtures/instructor_dashboard}/autoenrollment.html (100%) rename lms/static/{coffee/fixtures => js/fixtures/instructor_dashboard}/send_email.html (100%) create mode 100644 lms/static/js/spec/instructor_dashboard/membership_spec.js create mode 100644 lms/static/js/spec/instructor_dashboard/send_email_spec.js diff --git a/lms/envs/common.py b/lms/envs/common.py index f11f2a04fb..83911c4bc8 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1319,10 +1319,7 @@ discussion_vendor_js = [ ] notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.js')) -instructor_dash_js = ( - sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/instructor_dashboard/**/*.js')) + - sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.js')) -) +instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.js')) verify_student_js = [ 'js/sticky_filter.js', diff --git a/lms/static/coffee/spec/instructor_dashboard/membership_spec.coffee b/lms/static/coffee/spec/instructor_dashboard/membership_spec.coffee deleted file mode 100644 index bb2ba81a11..0000000000 --- a/lms/static/coffee/spec/instructor_dashboard/membership_spec.coffee +++ /dev/null @@ -1,67 +0,0 @@ -describe 'AutoEnrollment', -> - beforeEach -> - loadFixtures 'coffee/fixtures/autoenrollment.html' - @autoenrollment = new AutoEnrollmentViaCsv $('.auto_enroll_csv') - - it 'binds the ajax call and the result will be success', -> - spyOn($, "ajax").and.callFake((params) => - params.success({row_errors: [], general_errors: [], warnings: []}) - {always: ->} - ) - # mock the render_notification_view which returns the html (since we are only using the existing notification model) - @autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").and.callFake => - return '

Success

All accounts were created successfully.

' - - submitCallback = jasmine.createSpy().and.returnValue() - @autoenrollment.$student_enrollment_form.submit(submitCallback) - @autoenrollment.$enrollment_signup_button.click() - expect($('.results .message-copy').text()).toEqual('All accounts were created successfully.') - expect(submitCallback).toHaveBeenCalled() - - it 'binds the ajax call and the result will be error', -> - spyOn($, "ajax").and.callFake((params) => - params.success({ - row_errors: [{ - 'username': 'testuser1', - 'email': 'testemail1@email.com', - 'response': 'Username already exists' - }], - general_errors: [{ - 'response': 'cannot read the line 2' - }], - warnings: [] - }) - {always: ->} - ) - # mock the render_notification_view which returns the html (since we are only using the existing notification model) - @autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").and.callFake => - return '

Errors

The following errors were generated:

  • cannot read the line 2
  • testuser1 (testemail1@email.com): (Username already exists)
' - - submitCallback = jasmine.createSpy().and.returnValue() - @autoenrollment.$student_enrollment_form.submit(submitCallback) - @autoenrollment.$enrollment_signup_button.click() - expect($('.results .list-summary').text()).toEqual('cannot read the line 2testuser1 (testemail1@email.com): (Username already exists)'); - expect(submitCallback).toHaveBeenCalled() - - it 'binds the ajax call and the result will be warnings', -> - spyOn($, "ajax").and.callFake((params) => - params.success({ - row_errors: [], - general_errors: [], - warnings: [{ - 'username': 'user1', - 'email': 'user1email', - 'response': 'email is in valid' - }] - }) - {always: ->} - ) - # mock the render_notification_view which returns the html (since we are only using the existing notification model) - @autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").and.callFake => - return '

Warnings

The following warnings were generated:

  • user1 (user1email): (email is in valid)
' - - submitCallback = jasmine.createSpy().and.returnValue() - @autoenrollment.$student_enrollment_form.submit(submitCallback) - @autoenrollment.$enrollment_signup_button.click() - expect($('.results .list-summary').text()).toEqual('user1 (user1email): (email is in valid)') - expect(submitCallback).toHaveBeenCalled() \ No newline at end of file diff --git a/lms/static/coffee/spec/instructor_dashboard/send_email_spec.coffee b/lms/static/coffee/spec/instructor_dashboard/send_email_spec.coffee deleted file mode 100644 index 63250d12ae..0000000000 --- a/lms/static/coffee/spec/instructor_dashboard/send_email_spec.coffee +++ /dev/null @@ -1,92 +0,0 @@ -describe "Bulk Email Queueing", -> - beforeEach -> - testSubject = "Test Subject" - testBody = "Hello, World! This is a test email message!" - loadFixtures 'coffee/fixtures/send_email.html' - @send_email = new SendEmail $('.send-email') - @send_email.$subject.val(testSubject) - @send_email.$send_to.first().prop("checked", true) - @send_email.$emailEditor = - save: -> - {"data": testBody} - @ajax_params = { - type: "POST", - dataType: "json", - url: undefined, - data: { - action: "send", - send_to: JSON.stringify([@send_email.$send_to.first().val()]), - subject: testSubject, - message: testBody, - }, - success: jasmine.any(Function), - error: jasmine.any(Function), - } - - it 'cannot send an email with no target', -> - spyOn(window, "alert") - spyOn($, "ajax") - for target in @send_email.$send_to - target.checked = false - @send_email.$btn_send.click() - expect(window.alert).toHaveBeenCalledWith("Your message must have at least one target.") - expect($.ajax).not.toHaveBeenCalled() - - it 'cannot send an email with no subject', -> - spyOn(window, "alert") - spyOn($, "ajax") - @send_email.$subject.val("") - @send_email.$btn_send.click() - expect(window.alert).toHaveBeenCalledWith("Your message must have a subject.") - expect($.ajax).not.toHaveBeenCalled() - - it 'cannot send an email with no message', -> - spyOn(window, "alert") - spyOn($, "ajax") - @send_email.$emailEditor = - save: -> - {"data": ""} - @send_email.$btn_send.click() - expect(window.alert).toHaveBeenCalledWith("Your message cannot be blank.") - expect($.ajax).not.toHaveBeenCalled() - - it 'can send a simple message to a single target', -> - spyOn($, "ajax").and.callFake((params) => - params.success() - ) - @send_email.$btn_send.click() - expect($('.msg-confirm').text()).toEqual('Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.') - expect($.ajax).toHaveBeenCalledWith(@ajax_params) - - it 'can send a simple message to a multiple targets', -> - spyOn($, "ajax").and.callFake((params) => - params.success() - ) - @ajax_params.data.send_to = JSON.stringify(target.value for target in @send_email.$send_to) - for target in @send_email.$send_to - target.checked = true - @send_email.$btn_send.click() - expect($('.msg-confirm').text()).toEqual('Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.') - expect($.ajax).toHaveBeenCalledWith(@ajax_params) - - it 'can handle an error result from the bulk email api', -> - spyOn($, "ajax").and.callFake((params) => - params.error() - ) - spyOn(console, "warn") - @send_email.$btn_send.click() - expect($('.request-response-error').text()).toEqual('Error sending email.') - expect(console.warn).toHaveBeenCalled() - - it 'selecting all learners disables cohort selections', -> - @send_email.$send_to.filter("[value='learners']").click - @send_email.$cohort_targets.each -> - expect(this.disabled).toBe(true) - @send_email.$send_to.filter("[value='learners']").click - @send_email.$cohort_targets.each -> - expect(this.disabled).toBe(false) - - it 'selected targets are listed after "send to:"', -> - @send_email.$send_to.click - $('input[name="send_to"]:checked+label').each -> - expect($('.send_to_list'.text())).toContain(this.innerText.replace(/\s*\n.*/g,'')) diff --git a/lms/static/coffee/fixtures/autoenrollment.html b/lms/static/js/fixtures/instructor_dashboard/autoenrollment.html similarity index 100% rename from lms/static/coffee/fixtures/autoenrollment.html rename to lms/static/js/fixtures/instructor_dashboard/autoenrollment.html diff --git a/lms/static/coffee/fixtures/send_email.html b/lms/static/js/fixtures/instructor_dashboard/send_email.html similarity index 100% rename from lms/static/coffee/fixtures/send_email.html rename to lms/static/js/fixtures/instructor_dashboard/send_email.html diff --git a/lms/static/js/instructor_dashboard/course_info.js b/lms/static/js/instructor_dashboard/course_info.js index c481a33bb5..81dcc2f062 100644 --- a/lms/static/js/instructor_dashboard/course_info.js +++ b/lms/static/js/instructor_dashboard/course_info.js @@ -1,55 +1,46 @@ -### -Course Info Section +(function() { + 'use strict'; + var InstructorDashboardCourseInfo, PendingInstructorTasks; -imports from other modules. -wrap in (-> ... apply) to defer evaluation -such that the value can be defined later than this assignment (file load order). -### + PendingInstructorTasks = function() { + return window.InstructorDashboard.util.PendingInstructorTasks; + }; -# Load utilities -PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks + InstructorDashboardCourseInfo = (function() { + function CourseInfo($section) { + var courseInfo = this; + this.$section = $section; + this.$section.data('wrapper', this); + this.instructor_tasks = new (PendingInstructorTasks())(this.$section); + this.$course_errors_wrapper = this.$section.find('.course-errors-wrapper'); + if (this.$course_errors_wrapper.length) { + this.$course_error_toggle = this.$course_errors_wrapper.find('.toggle-wrapper'); + this.$course_error_toggle_text = this.$course_error_toggle.find('h2'); + this.$course_errors = this.$course_errors_wrapper.find('.course-error'); + this.$course_error_toggle_text.text( + this.$course_error_toggle_text.text() + (" (' + this.$course_errors.length + ')") + ); + this.$course_error_toggle.click(function(e) { + e.preventDefault(); + if (courseInfo.$course_errors_wrapper.hasClass('open')) { + return courseInfo.$course_errors_wrapper.removeClass('open'); + } else { + return courseInfo.$course_errors_wrapper.addClass('open'); + } + }); + } + } -# A typical section object. -# constructed with $section, a jquery object -# which holds the section body container. -class CourseInfo - constructor: (@$section) -> - # attach self to html so that instructor_dashboard.coffee can find - # this object to call event handlers like 'onClickTitle' - @$section.data 'wrapper', @ + CourseInfo.prototype.onClickTitle = function() { + return this.instructor_tasks.task_poller.start(); + }; - # gather elements - @instructor_tasks = new (PendingInstructorTasks()) @$section - @$course_errors_wrapper = @$section.find '.course-errors-wrapper' + CourseInfo.prototype.onExit = function() { + return this.instructor_tasks.task_poller.stop(); + }; - # if there are errors - if @$course_errors_wrapper.length - @$course_error_toggle = @$course_errors_wrapper.find '.toggle-wrapper' - @$course_error_toggle_text = @$course_error_toggle.find 'h2' - @$course_error_visibility_wrapper = @$course_errors_wrapper.find '.course-errors-visibility-wrapper' - @$course_errors = @$course_errors_wrapper.find '.course-error' + return CourseInfo; + }()); - # append "(34)" to the course errors label - @$course_error_toggle_text.text @$course_error_toggle_text.text() + " (#{@$course_errors.length})" - - # toggle .open class on errors - # to show and hide them. - @$course_error_toggle.click (e) => - e.preventDefault() - if @$course_errors_wrapper.hasClass 'open' - @$course_errors_wrapper.removeClass 'open' - else - @$course_errors_wrapper.addClass 'open' - - # handler for when the section title is clicked. - onClickTitle: -> @instructor_tasks.task_poller.start() - - # handler for when the section is closed - onExit: -> @instructor_tasks.task_poller.stop() - -# export for use -# create parent namespaces if they do not already exist. -_.defaults window, InstructorDashboard: {} -_.defaults window.InstructorDashboard, sections: {} -_.defaults window.InstructorDashboard.sections, - CourseInfo: CourseInfo + window.InstructorDashboard.sections.CourseInfo = InstructorDashboardCourseInfo; +}).call(this); diff --git a/lms/static/js/instructor_dashboard/data_download.js b/lms/static/js/instructor_dashboard/data_download.js index 4fe762965a..bd4e62b4d6 100644 --- a/lms/static/js/instructor_dashboard/data_download.js +++ b/lms/static/js/instructor_dashboard/data_download.js @@ -1,297 +1,382 @@ -### -Data Download Section +/* globals _ */ -imports from other modules. -wrap in (-> ... apply) to defer evaluation -such that the value can be defined later than this assignment (file load order). -### +(function() { + 'use strict'; + var DataDownload, DataDownloadCertificate, PendingInstructorTasks, ReportDownloads, statusAjaxError; -# Load utilities -std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments -PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks -ReportDownloads = -> window.InstructorDashboard.util.ReportDownloads + statusAjaxError = function() { + return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments); + }; -# Data Download Certificate issued -class @DataDownload_Certificate - constructor: (@$container) -> - # gather elements - @$list_issued_certificate_table_btn = @$container.find("input[name='issued-certificates-list']") - @$list_issued_certificate_csv_btn = @$container.find("input[name='issued-certificates-csv']") - @$certificate_display_table = @$container.find '.certificate-data-display-table' - @$certificates_request_response_error = @$container.find '.issued-certificates-error.request-response-error' + PendingInstructorTasks = function() { + return window.InstructorDashboard.util.PendingInstructorTasks; + }; + ReportDownloads = function() { + return window.InstructorDashboard.util.ReportDownloads; + }; - @$list_issued_certificate_table_btn.click (e) => - url = @$list_issued_certificate_table_btn.data 'endpoint' - # Dynamically generate slickgrid table for displaying issued certificate information. - @clear_ui() - @$certificate_display_table.text gettext('Loading data...') - # fetch user list - $.ajax - type: 'POST' - url: url - error: (std_ajax_err) => - @clear_ui() - @$certificates_request_response_error.text gettext("Error getting issued certificates list.") - $(".issued_certificates .issued-certificates-error.msg-error").css({"display":"block"}) - success: (data) => - @clear_ui() - # display on a SlickGrid - options = - enableCellNavigation: true - enableColumnReorder: false - forceFitColumns: true - rowHeight: 35 + DataDownloadCertificate = (function() { + function InstructorDashboardDataDownloadCertificate($container) { + var dataDownloadCert = this; + this.$container = $container; + this.$list_issued_certificate_table_btn = this.$container.find("input[name='issued-certificates-list']"); + this.$list_issued_certificate_csv_btn = this.$container.find("input[name='issued-certificates-csv']"); + this.$certificate_display_table = this.$container.find('.certificate-data-display-table'); + this.$certificates_request_err = this.$container.find('.issued-certificates-error.request-response-error'); + this.$list_issued_certificate_table_btn.click(function() { + var url = dataDownloadCert.$list_issued_certificate_table_btn.data('endpoint'); + dataDownloadCert.clear_ui(); + dataDownloadCert.$certificate_display_table.text(gettext('Loading data...')); + return $.ajax({ + type: 'POST', + url: url, + error: function() { + dataDownloadCert.clear_ui(); + dataDownloadCert.$certificates_request_err.text( + gettext('Error getting issued certificates list.') + ); + return $('.issued_certificates .issued-certificates-error.msg-error').css({ + display: 'block' + }); + }, + success: function(data) { + var $tablePlaceholder, columns, feature, gridData, options; + dataDownloadCert.clear_ui(); + options = { + enableCellNavigation: true, + enableColumnReorder: false, + forceFitColumns: true, + rowHeight: 35 + }; + columns = (function() { + var i, len, ref, results; + ref = data.queried_features; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + feature = ref[i]; + results.push({ + id: feature, + field: feature, + name: data.feature_names[feature] + }); + } + return results; + }()); + gridData = data.certificates; + $tablePlaceholder = $('
', { + class: 'slickgrid' + }); + dataDownloadCert.$certificate_display_table.append($tablePlaceholder); + return new window.Slick.Grid($tablePlaceholder, gridData, columns, options); + } + }); + }); + this.$list_issued_certificate_csv_btn.click(function() { + dataDownloadCert.clear_ui(); + location.href = dataDownloadCert.$list_issued_certificate_csv_btn.data('endpoint') + '?csv=true'; + }); + } - columns = ({id: feature, field: feature, name: data.feature_names[feature]} for feature in data.queried_features) - grid_data = data.certificates + InstructorDashboardDataDownloadCertificate.prototype.clear_ui = function() { + this.$certificate_display_table.empty(); + this.$certificates_request_err.empty(); + return $('.issued-certificates-error.msg-error').css({ + display: 'none' + }); + }; - $table_placeholder = $ '
', class: 'slickgrid' - @$certificate_display_table.append $table_placeholder - new Slick.Grid($table_placeholder, grid_data, columns, options) + return InstructorDashboardDataDownloadCertificate; + }()); - @$list_issued_certificate_csv_btn.click (e) => - @clear_ui() - url = @$list_issued_certificate_csv_btn.data 'endpoint' - location.href = url + '?csv=true' + DataDownload = (function() { + function InstructorDashboardDataDownload($section) { + var dataDownloadObj = this; + this.$section = $section; + this.$section.data('wrapper', this); + this.ddc = new DataDownloadCertificate(this.$section.find('.issued_certificates')); + this.$list_studs_btn = this.$section.find("input[name='list-profiles']"); + this.$list_studs_csv_btn = this.$section.find("input[name='list-profiles-csv']"); + this.$proctored_exam_csv_btn = this.$section.find("input[name='proctored-exam-results-report']"); + this.$survey_results_csv_btn = this.$section.find("input[name='survey-results-report']"); + this.$list_may_enroll_csv_btn = this.$section.find("input[name='list-may-enroll-csv']"); + this.$list_problem_responses_csv_input = this.$section.find("input[name='problem-location']"); + this.$list_problem_responses_csv_btn = this.$section.find("input[name='list-problem-responses-csv']"); + this.$list_anon_btn = this.$section.find("input[name='list-anon-ids']"); + this.$grade_config_btn = this.$section.find("input[name='dump-gradeconf']"); + this.$calculate_grades_csv_btn = this.$section.find("input[name='calculate-grades-csv']"); + this.$problem_grade_report_csv_btn = this.$section.find("input[name='problem-grade-report']"); + this.$async_report_btn = this.$section.find("input[class='async-report-btn']"); + this.$download = this.$section.find('.data-download-container'); + this.$download_display_text = this.$download.find('.data-display-text'); + this.$download_request_response_error = this.$download.find('.request-response-error'); + this.$reports = this.$section.find('.reports-download-container'); + this.$download_display_table = this.$reports.find('.profile-data-display-table'); + this.$reports_request_response = this.$reports.find('.request-response'); + this.$reports_request_response_error = this.$reports.find('.request-response-error'); + this.report_downloads = new (ReportDownloads())(this.$section); + this.instructor_tasks = new (PendingInstructorTasks())(this.$section); + this.clear_display(); + this.$list_anon_btn.click(function() { + location.href = dataDownloadObj.$list_anon_btn.data('endpoint'); + }); + this.$proctored_exam_csv_btn.click(function() { + var url = dataDownloadObj.$proctored_exam_csv_btn.data('endpoint'); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + error: function() { + dataDownloadObj.clear_display(); + dataDownloadObj.$reports_request_response_error.text( + gettext('Error generating proctored exam results. Please try again.') + ); + return $('.msg-error').css({ + display: 'block' + }); + }, + success: function(data) { + dataDownloadObj.clear_display(); + dataDownloadObj.$reports_request_response.text(data.status); + return $('.msg-confirm').css({ + display: 'block' + }); + } + }); + }); + this.$survey_results_csv_btn.click(function() { + var url = dataDownloadObj.$survey_results_csv_btn.data('endpoint'); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + error: function() { + dataDownloadObj.clear_display(); + dataDownloadObj.$reports_request_response_error.text( + gettext('Error generating survey results. Please try again.') + ); + return $('.msg-error').css({ + display: 'block' + }); + }, + success: function(data) { + dataDownloadObj.clear_display(); + dataDownloadObj.$reports_request_response.text(data.status); + return $('.msg-confirm').css({ + display: 'block' + }); + } + }); + }); + this.$list_studs_csv_btn.click(function() { + var url = dataDownloadObj.$list_studs_csv_btn.data('endpoint') + '/csv'; + dataDownloadObj.clear_display(); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + error: function() { + dataDownloadObj.$reports_request_response_error.text( + gettext('Error generating student profile information. Please try again.') + ); + return $('.msg-error').css({ + display: 'block' + }); + }, + success: function(data) { + dataDownloadObj.$reports_request_response.text(data.status); + return $('.msg-confirm').css({ + display: 'block' + }); + } + }); + }); + this.$list_studs_btn.click(function() { + var url = dataDownloadObj.$list_studs_btn.data('endpoint'); + dataDownloadObj.clear_display(); + dataDownloadObj.$download_display_table.text(gettext('Loading')); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + error: function() { + dataDownloadObj.clear_display(); + return dataDownloadObj.$download_request_response_error.text( + gettext('Error getting student list.') + ); + }, + success: function(data) { + var $tablePlaceholder, columns, feature, gridData, options; + dataDownloadObj.clear_display(); + options = { + enableCellNavigation: true, + enableColumnReorder: false, + forceFitColumns: true, + rowHeight: 35 + }; + columns = (function() { + var i, len, ref, results; + ref = data.queried_features; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + feature = ref[i]; + results.push({ + id: feature, + field: feature, + name: data.feature_names[feature] + }); + } + return results; + }()); + gridData = data.students; + $tablePlaceholder = $('
', { + class: 'slickgrid' + }); + dataDownloadObj.$download_display_table.append($tablePlaceholder); + return new window.Slick.Grid($tablePlaceholder, gridData, columns, options); + } + }); + }); + this.$list_problem_responses_csv_btn.click(function() { + var url = dataDownloadObj.$list_problem_responses_csv_btn.data('endpoint'); + dataDownloadObj.clear_display(); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + data: { + problem_location: dataDownloadObj.$list_problem_responses_csv_input.val() + }, + error: function(error) { + dataDownloadObj.$reports_request_response_error.text( + JSON.parse(error.responseText) + ); + return $('.msg-error').css({ + display: 'block' + }); + }, + success: function(data) { + dataDownloadObj.$reports_request_response.text(data.status); + return $('.msg-confirm').css({ + display: 'block' + }); + } + }); + }); + this.$list_may_enroll_csv_btn.click(function() { + var url = dataDownloadObj.$list_may_enroll_csv_btn.data('endpoint'); + dataDownloadObj.clear_display(); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + error: function() { + dataDownloadObj.$reports_request_response_error.text( + gettext('Error generating list of students who may enroll. Please try again.') + ); + return $('.msg-error').css({ + display: 'block' + }); + }, + success: function(data) { + dataDownloadObj.$reports_request_response.text(data.status); + return $('.msg-confirm').css({ + display: 'block' + }); + } + }); + }); + this.$grade_config_btn.click(function() { + var url = dataDownloadObj.$grade_config_btn.data('endpoint'); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + error: function() { + dataDownloadObj.clear_display(); + return dataDownloadObj.$download_request_response_error.text( + gettext('Error retrieving grading configuration.') + ); + }, + success: function(data) { + dataDownloadObj.clear_display(); + return edx.HtmlUtils.setHtml( + dataDownloadObj.$download_display_text, edx.HtmlUtils.HTML(data.grading_config_summary)); + } + }); + }); + this.$async_report_btn.click(function(e) { + var url = $(e.target).data('endpoint'); + dataDownloadObj.clear_display(); + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + error: statusAjaxError(function() { + if (e.target.name === 'calculate-grades-csv') { + dataDownloadObj.$grades_request_response_error.text( + gettext('Error generating grades. Please try again.') + ); + } else if (e.target.name === 'problem-grade-report') { + dataDownloadObj.$grades_request_response_error.text( + gettext('Error generating problem grade report. Please try again.') + ); + } else if (e.target.name === 'export-ora2-data') { + dataDownloadObj.$grades_request_response_error.text( + gettext('Error generating ORA data report. Please try again.') + ); + } + return $('.msg-error').css({ + display: 'block' + }); + }), + success: function(data) { + dataDownloadObj.$reports_request_response.text(data.status); + return $('.msg-confirm').css({ + display: 'block' + }); + } + }); + }); + } - clear_ui: -> - # Clear any generated tables, warning messages, etc of certificates. - @$certificate_display_table.empty() - @$certificates_request_response_error.empty() - $(".issued-certificates-error.msg-error").css({"display":"none"}) + InstructorDashboardDataDownload.prototype.onClickTitle = function() { + this.clear_display(); + this.instructor_tasks.task_poller.start(); + return this.report_downloads.downloads_poller.start(); + }; -# Data Download Section -class DataDownload - constructor: (@$section) -> - # attach self to html so that instructor_dashboard.coffee can find - # this object to call event handlers like 'onClickTitle' - @$section.data 'wrapper', @ + InstructorDashboardDataDownload.prototype.onExit = function() { + this.instructor_tasks.task_poller.stop(); + return this.report_downloads.downloads_poller.stop(); + }; - # isolate # initialize DataDownload_Certificate subsection - new DataDownload_Certificate @$section.find '.issued_certificates' + InstructorDashboardDataDownload.prototype.clear_display = function() { + this.$download_display_text.empty(); + this.$download_display_table.empty(); + this.$download_request_response_error.empty(); + this.$reports_request_response.empty(); + this.$reports_request_response_error.empty(); + $('.msg-confirm').css({ + display: 'none' + }); + return $('.msg-error').css({ + display: 'none' + }); + }; - # gather elements - @$list_studs_btn = @$section.find("input[name='list-profiles']") - @$list_studs_csv_btn = @$section.find("input[name='list-profiles-csv']") - @$list_proctored_exam_results_csv_btn = @$section.find("input[name='proctored-exam-results-report']") - @$survey_results_csv_btn = @$section.find("input[name='survey-results-report']") - @$list_may_enroll_csv_btn = @$section.find("input[name='list-may-enroll-csv']") - @$list_problem_responses_csv_input = @$section.find("input[name='problem-location']") - @$list_problem_responses_csv_btn = @$section.find("input[name='list-problem-responses-csv']") - @$list_anon_btn = @$section.find("input[name='list-anon-ids']") - @$grade_config_btn = @$section.find("input[name='dump-gradeconf']") - @$calculate_grades_csv_btn = @$section.find("input[name='calculate-grades-csv']") - @$problem_grade_report_csv_btn = @$section.find("input[name='problem-grade-report']") - @$async_report_btn = @$section.find("input[class='async-report-btn']") + return InstructorDashboardDataDownload; + }()); - # response areas - @$download = @$section.find '.data-download-container' - @$download_display_text = @$download.find '.data-display-text' - @$download_request_response_error = @$download.find '.request-response-error' - @$reports = @$section.find '.reports-download-container' - @$download_display_table = @$reports.find '.profile-data-display-table' - @$reports_request_response = @$reports.find '.request-response' - @$reports_request_response_error = @$reports.find '.request-response-error' + _.defaults(window, { + InstructorDashboard: {} + }); - @report_downloads = new (ReportDownloads()) @$section - @instructor_tasks = new (PendingInstructorTasks()) @$section - @clear_display() + _.defaults(window.InstructorDashboard, { + sections: {} + }); - # attach click handlers - # The list-anon case is always CSV - @$list_anon_btn.click (e) => - url = @$list_anon_btn.data 'endpoint' - location.href = url - - # attach click handlers - # The list_proctored_exam_results case is always CSV - @$list_proctored_exam_results_csv_btn.click (e) => - url = @$list_proctored_exam_results_csv_btn.data 'endpoint' - # display html from proctored exam results config endpoint - $.ajax - type: 'POST' - dataType: 'json' - url: url - error: (std_ajax_err) => - @clear_display() - @$reports_request_response_error.text gettext( - "Error generating proctored exam results. Please try again." - ) - $(".msg-error").css({"display":"block"}) - success: (data) => - @clear_display() - @$reports_request_response.text data['status'] - $(".msg-confirm").css({"display":"block"}) - - # attach click handlers - # The list_proctored_exam_results case is always CSV - @$survey_results_csv_btn.click (e) => - url = @$survey_results_csv_btn.data 'endpoint' - # display html from survey results config endpoint - $.ajax - type: 'POST' - dataType: 'json' - url: url - error: (std_ajax_err) => - @clear_display() - @$reports_request_response_error.text gettext( - "Error generating survey results. Please try again." - ) - $(".msg-error").css({"display":"block"}) - success: (data) => - @clear_display() - @$reports_request_response.text data['status'] - $(".msg-confirm").css({"display":"block"}) - - # this handler binds to both the download - # and the csv button - @$list_studs_csv_btn.click (e) => - @clear_display() - - url = @$list_studs_csv_btn.data 'endpoint' - # handle csv special case - # redirect the document to the csv file. - url += '/csv' - - $.ajax - type: 'POST' - dataType: 'json' - url: url - error: (std_ajax_err) => - @$reports_request_response_error.text gettext("Error generating student profile information. Please try again.") - $(".msg-error").css({"display":"block"}) - success: (data) => - @$reports_request_response.text data['status'] - $(".msg-confirm").css({"display":"block"}) - - @$list_studs_btn.click (e) => - url = @$list_studs_btn.data 'endpoint' - - # Dynamically generate slickgrid table for displaying student profile information - @clear_display() - @$download_display_table.text gettext('Loading') - - # fetch user list - $.ajax - type: 'POST' - dataType: 'json' - url: url - error: (std_ajax_err) => - @clear_display() - @$download_request_response_error.text gettext("Error getting student list.") - success: (data) => - @clear_display() - - # display on a SlickGrid - options = - enableCellNavigation: true - enableColumnReorder: false - forceFitColumns: true - rowHeight: 35 - - columns = ({id: feature, field: feature, name: data.feature_names[feature]} for feature in data.queried_features) - grid_data = data.students - - $table_placeholder = $ '
', class: 'slickgrid' - @$download_display_table.append $table_placeholder - grid = new Slick.Grid($table_placeholder, grid_data, columns, options) - # grid.autosizeColumns() - - @$list_problem_responses_csv_btn.click (e) => - @clear_display() - - url = @$list_problem_responses_csv_btn.data 'endpoint' - $.ajax - type: 'POST' - dataType: 'json' - url: url - data: - problem_location: @$list_problem_responses_csv_input.val() - error: (std_ajax_err) => - @$reports_request_response_error.text JSON.parse(std_ajax_err['responseText']) - $(".msg-error").css({"display":"block"}) - success: (data) => - @$reports_request_response.text data['status'] - $(".msg-confirm").css({"display":"block"}) - - @$list_may_enroll_csv_btn.click (e) => - @clear_display() - - url = @$list_may_enroll_csv_btn.data 'endpoint' - $.ajax - type: 'POST' - dataType: 'json' - url: url - error: (std_ajax_err) => - @$reports_request_response_error.text gettext("Error generating list of students who may enroll. Please try again.") - $(".msg-error").css({"display":"block"}) - success: (data) => - @$reports_request_response.text data['status'] - $(".msg-confirm").css({"display":"block"}) - - @$grade_config_btn.click (e) => - url = @$grade_config_btn.data 'endpoint' - # display html from grading config endpoint - $.ajax - type: 'POST' - dataType: 'json' - url: url - error: (std_ajax_err) => - @clear_display() - @$download_request_response_error.text gettext("Error retrieving grading configuration.") - success: (data) => - @clear_display() - @$download_display_text.html data['grading_config_summary'] - - @$async_report_btn.click (e) => - # Clear any CSS styling from the request-response areas - #$(".msg-confirm").css({"display":"none"}) - #$(".msg-error").css({"display":"none"}) - @clear_display() - url = $(e.target).data 'endpoint' - $.ajax - type: 'POST' - dataType: 'json' - url: url - error: std_ajax_err => - if e.target.name == 'calculate-grades-csv' - @$grades_request_response_error.text gettext("Error generating grades. Please try again.") - else if e.target.name == 'problem-grade-report' - @$grades_request_response_error.text gettext("Error generating problem grade report. Please try again.") - else if e.target.name == 'export-ora2-data' - @$grades_request_response_error.text gettext("Error generating ORA data report. Please try again.") - $(".msg-error").css({"display":"block"}) - success: (data) => - @$reports_request_response.text data['status'] - $(".msg-confirm").css({"display":"block"}) - - # handler for when the section title is clicked. - onClickTitle: -> - # Clear display of anything that was here before - @clear_display() - @instructor_tasks.task_poller.start() - @report_downloads.downloads_poller.start() - - # handler for when the section is closed - onExit: -> - @instructor_tasks.task_poller.stop() - @report_downloads.downloads_poller.stop() - - clear_display: -> - # Clear any generated tables, warning messages, etc. - @$download_display_text.empty() - @$download_display_table.empty() - @$download_request_response_error.empty() - @$reports_request_response.empty() - @$reports_request_response_error.empty() - # Clear any CSS styling from the request-response areas - $(".msg-confirm").css({"display":"none"}) - $(".msg-error").css({"display":"none"}) - -# export for use -# create parent namespaces if they do not already exist. -_.defaults window, InstructorDashboard: {} -_.defaults window.InstructorDashboard, sections: {} -_.defaults window.InstructorDashboard.sections, - DataDownload: DataDownload + _.defaults(window.InstructorDashboard.sections, { + DataDownload: DataDownload + }); +}).call(this); diff --git a/lms/static/js/instructor_dashboard/e-commerce.js b/lms/static/js/instructor_dashboard/e-commerce.js index acc761c887..84ffad189e 100644 --- a/lms/static/js/instructor_dashboard/e-commerce.js +++ b/lms/static/js/instructor_dashboard/e-commerce.js @@ -1,92 +1,96 @@ -### -E-Commerce Section -### +/* globals _ */ -# Load utilities -PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks -ReportDownloads = -> window.InstructorDashboard.util.ReportDownloads +(function() { + 'use strict'; + var ECommerce, PendingInstructorTasks, ReportDownloads; -class ECommerce -# E-Commerce Section - constructor: (@$section) -> - # attach self to html so that instructor_dashboard.coffee can find - # this object to call event handlers like 'onClickTitle' - @$section.data 'wrapper', @ - # gather elements - @$list_sale_csv_btn = @$section.find("input[name='list-sale-csv']") - @$list_order_sale_csv_btn = @$section.find("input[name='list-order-sale-csv']") - @$download_company_name = @$section.find("input[name='download_company_name']") - @$active_company_name = @$section.find("input[name='active_company_name']") - @$spent_company_name = @$section.find('input[name="spent_company_name"]') - @$download_coupon_codes = @$section.find('input[name="download-coupon-codes-csv"]') - - @$download_registration_codes_form = @$section.find("form#download_registration_codes") - @$active_registration_codes_form = @$section.find("form#active_registration_codes") - @$spent_registration_codes_form = @$section.find("form#spent_registration_codes") + PendingInstructorTasks = function() { + return window.InstructorDashboard.util.PendingInstructorTasks; + }; - @$reports = @$section.find '.reports-download-container' - @$reports_request_response = @$reports.find '.request-response' - @$reports_request_response_error = @$reports.find '.request-response-error' + ReportDownloads = function() { + return window.InstructorDashboard.util.ReportDownloads; + }; - @report_downloads = new (ReportDownloads()) @$section - @instructor_tasks = new (PendingInstructorTasks()) @$section + ECommerce = (function() { + function eCommerce($section) { + var eCom = this; + this.$section = $section; + this.$section.data('wrapper', this); + this.$list_sale_csv_btn = this.$section.find("input[name='list-sale-csv']"); + this.$list_order_sale_csv_btn = this.$section.find("input[name='list-order-sale-csv']"); + this.$download_company_name = this.$section.find("input[name='download_company_name']"); + this.$active_company_name = this.$section.find("input[name='active_company_name']"); + this.$spent_company_name = this.$section.find('input[name="spent_company_name"]'); + this.$download_coupon_codes = this.$section.find('input[name="download-coupon-codes-csv"]'); + this.$download_registration_codes_form = this.$section.find('form#download_registration_codes'); + this.$active_registration_codes_form = this.$section.find('form#active_registration_codes'); + this.$spent_registration_codes_form = this.$section.find('form#spent_registration_codes'); + this.$reports = this.$section.find('.reports-download-container'); + this.$reports_request_response = this.$reports.find('.request-response'); + this.$reports_request_response_error = this.$reports.find('.request-response-error'); + this.report_downloads = new (ReportDownloads())(this.$section); + this.instructor_tasks = new (PendingInstructorTasks())(this.$section); + this.$error_msg = this.$section.find('#error-msg'); + this.$list_sale_csv_btn.click(function() { + location.href = eCom.$list_sale_csv_btn.data('endpoint') + '/csv'; + return location.href; + }); + this.$list_order_sale_csv_btn.click(function() { + location.href = eCom.$list_order_sale_csv_btn.data('endpoint'); + return location.href; + }); + this.$download_coupon_codes.click(function() { + location.href = eCom.$download_coupon_codes.data('endpoint'); + return location.href; + }); + this.$download_registration_codes_form.submit(function() { + eCom.$error_msg.attr('style', 'display: none'); + return true; + }); + this.$active_registration_codes_form.submit(function() { + eCom.$error_msg.attr('style', 'display: none'); + return true; + }); + this.$spent_registration_codes_form.submit(function() { + eCom.$error_msg.attr('style', 'display: none'); + return true; + }); + } - @$error_msg = @$section.find('#error-msg') - - # attach click handlers - # this handler binds to both the download - # and the csv button - @$list_sale_csv_btn.click (e) => - url = @$list_sale_csv_btn.data 'endpoint' - url += '/csv' - location.href = url + eCommerce.prototype.onClickTitle = function() { + this.clear_display(); + this.instructor_tasks.task_poller.start(); + return this.report_downloads.downloads_poller.start(); + }; - @$list_order_sale_csv_btn.click (e) => - url = @$list_order_sale_csv_btn.data 'endpoint' - location.href = url + eCommerce.prototype.onExit = function() { + this.clear_display(); + this.instructor_tasks.task_poller.stop(); + return this.report_downloads.downloads_poller.stop(); + }; - @$download_coupon_codes.click (e) => - url = @$download_coupon_codes.data 'endpoint' - location.href = url + eCommerce.prototype.clear_display = function() { + this.$error_msg.attr('style', 'display: none'); + this.$download_company_name.val(''); + this.$reports_request_response.empty(); + this.$reports_request_response_error.empty(); + this.$active_company_name.val(''); + return this.$spent_company_name.val(''); + }; - @$download_registration_codes_form.submit (e) => - @$error_msg.attr('style', 'display: none') - return true + return eCommerce; + }()); - @$active_registration_codes_form.submit (e) => - @$error_msg.attr('style', 'display: none') - return true + _.defaults(window, { + InstructorDashboard: {} + }); - @$spent_registration_codes_form.submit (e) => - @$error_msg.attr('style', 'display: none') - return true + _.defaults(window.InstructorDashboard, { + sections: {} + }); - # handler for when the section title is clicked. - onClickTitle: -> - @clear_display() - @instructor_tasks.task_poller.start() - @report_downloads.downloads_poller.start() - - # handler for when the section is closed - onExit: -> - @clear_display() - @instructor_tasks.task_poller.stop() - @report_downloads.downloads_poller.stop() - - clear_display: -> - @$error_msg.attr('style', 'display: none') - @$download_company_name.val('') - @$reports_request_response.empty() - @$reports_request_response_error.empty() - @$active_company_name.val('') - @$spent_company_name.val('') - - isInt = (n) -> return n % 1 == 0; - # Clear any generated tables, warning messages, etc. - -# export for use -# create parent namespaces if they do not already exist. -_.defaults window, InstructorDashboard: {} -_.defaults window.InstructorDashboard, sections: {} -_.defaults window.InstructorDashboard.sections, - ECommerce: ECommerce + _.defaults(window.InstructorDashboard.sections, { + ECommerce: ECommerce + }); +}).call(this); diff --git a/lms/static/js/instructor_dashboard/extensions.js b/lms/static/js/instructor_dashboard/extensions.js index a45cffce67..21e5ee9273 100644 --- a/lms/static/js/instructor_dashboard/extensions.js +++ b/lms/static/js/instructor_dashboard/extensions.js @@ -1,155 +1,192 @@ -### -Extensions Section +/* globals _ */ -imports from other modules. -wrap in (-> ... apply) to defer evaluation -such that the value can be defined later than this assignment (file load order). -### +(function() { + 'use strict'; + var Extensions; -plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments -std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments + Extensions = (function() { + function extensions($section) { + var $gridDisplay, + ext = this; + this.$section = $section; + this.$section.data('wrapper', this); + this.$change_due_date = this.$section.find("input[name='change-due-date']"); + this.$reset_due_date = this.$section.find("input[name='reset-due-date']"); + this.$show_unit_ext = this.$section.find("input[name='show-unit-extensions']"); + this.$show_student_ext = this.$section.find("input[name='show-student-extensions']"); + this.$section.find('.request-response').hide(); + this.$section.find('.request-response-error').hide(); + $gridDisplay = this.$section.find('.data-display'); + this.$grid_text = $gridDisplay.find('.data-display-text'); + this.$grid_table = $gridDisplay.find('.data-display-table'); + this.$change_due_date.click(function() { + var sendData; + ext.clear_display(); + ext.$student_input = ext.$section.find("#set-extension input[name='student']"); + ext.$url_input = ext.$section.find("#set-extension select[name='url']"); + ext.$due_datetime_input = ext.$section.find("#set-extension input[name='due_datetime']"); + sendData = { + student: ext.$student_input.val(), + url: ext.$url_input.val(), + due_datetime: ext.$due_datetime_input.val() + }; + return $.ajax({ + type: 'POST', + dataType: 'json', + url: ext.$change_due_date.data('endpoint'), + data: sendData, + success: function(data) { + return ext.display_response('set-extension', data); + }, + error: function(xhr) { + return ext.fail_with_error('set-extension', 'Error changing due date', xhr); + } + }); + }); + this.$reset_due_date.click(function() { + var sendData; + ext.clear_display(); + ext.$student_input = ext.$section.find("#reset-extension input[name='student']"); + ext.$url_input = ext.$section.find("#reset-extension select[name='url']"); + sendData = { + student: ext.$student_input.val(), + url: ext.$url_input.val() + }; + return $.ajax({ + type: 'POST', + dataType: 'json', + url: ext.$reset_due_date.data('endpoint'), + data: sendData, + success: function(data) { + return ext.display_response('reset-extension', data); + }, + error: function(xhr) { + return ext.fail_with_error('reset-extension', 'Error reseting due date', xhr); + } + }); + }); + this.$show_unit_ext.click(function() { + var sendData, url; + ext.clear_display(); + ext.$grid_table.text('Loading'); + ext.$url_input = ext.$section.find("#view-granted-extensions select[name='url']"); + url = ext.$show_unit_ext.data('endpoint'); + sendData = { + url: ext.$url_input.val() + }; + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + data: sendData, + error: function(xhr) { + return ext.fail_with_error('view-granted-extensions', 'Error getting due dates', xhr); + }, + success: function(data) { + return ext.display_grid(data); + } + }); + }); + this.$show_student_ext.click(function() { + var sendData, url; + ext.clear_display(); + ext.$grid_table.text('Loading'); + url = ext.$show_student_ext.data('endpoint'); + ext.$student_input = ext.$section.find("#view-granted-extensions input[name='student']"); + sendData = { + student: ext.$student_input.val() + }; + return $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + data: sendData, + error: function(xhr) { + return ext.fail_with_error('view-granted-extensions', 'Error getting due dates', xhr); + }, + success: function(data) { + return ext.display_grid(data); + } + }); + }); + } -# Extensions Section -class Extensions + extensions.prototype.onClickTitle = function() {}; - constructor: (@$section) -> - # attach self to html - # so that instructor_dashboard.coffee can find this object - # to call event handlers like 'onClickTitle' - @$section.data 'wrapper', @ + extensions.prototype.fail_with_error = function(id, msg, xhr) { + var $taskError, $taskResponse, data, + message = msg; + $taskError = this.$section.find('#' + id + ' .request-response-error'); + $taskResponse = this.$section.find('#' + id + ' .request-response'); + this.clear_display(); + data = $.parseJSON(xhr.responseText); + message += ': ' + data.error; + $taskResponse.empty(); + $taskError.empty(); + $taskError.text(message); + return $taskError.show(); + }; - # Gather buttons - @$change_due_date = @$section.find("input[name='change-due-date']") - @$reset_due_date = @$section.find("input[name='reset-due-date']") - @$show_unit_extensions = @$section.find("input[name='show-unit-extensions']") - @$show_student_extensions = @$section.find("input[name='show-student-extensions']") + extensions.prototype.display_response = function(id, data) { + var $taskError, $taskResponse; + $taskError = this.$section.find('#' + id + ' .request-response-error'); + $taskResponse = this.$section.find('#' + id + ' .request-response'); + $taskError.empty().hide(); + $taskResponse.empty().text(data); + return $taskResponse.show(); + }; - # Gather notification areas - @$section.find(".request-response").hide() - @$section.find(".request-response-error").hide() + extensions.prototype.display_grid = function(data) { + var $tablePlaceholder, col, columns, gridData, options; + this.clear_display(); + this.$grid_text.text(data.title); + options = { + enableCellNavigation: true, + enableColumnReorder: false, + forceFitColumns: true + }; + columns = (function() { + var i, len, ref, results; + ref = data.header; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + col = ref[i]; + results.push({ + id: col, + field: col, + name: col + }); + } + return results; + }()); + gridData = data.data; + $tablePlaceholder = $('
', { + class: 'slickgrid', + style: 'min-height: 400px' + }); + this.$grid_table.append($tablePlaceholder); + return new window.Slick.Grid($tablePlaceholder, gridData, columns, options); + }; - # Gather grid elements - $grid_display = @$section.find '.data-display' - @$grid_text = $grid_display.find '.data-display-text' - @$grid_table = $grid_display.find '.data-display-table' + extensions.prototype.clear_display = function() { + this.$grid_text.empty(); + this.$grid_table.empty(); + this.$section.find('.request-response-error').empty().hide(); + return this.$section.find('.request-response').empty().hide(); + }; - # Click handlers - @$change_due_date.click => - @clear_display() - @$student_input = @$section.find("#set-extension input[name='student']") - @$url_input = @$section.find("#set-extension select[name='url']") - @$due_datetime_input = @$section.find("#set-extension input[name='due_datetime']") - send_data = - student: @$student_input.val() - url: @$url_input.val() - due_datetime: @$due_datetime_input.val() + return extensions; + }()); - $.ajax - type: 'POST' - dataType: 'json' - url: @$change_due_date.data 'endpoint' - data: send_data - success: (data) => @display_response "set-extension", data - error: (xhr) => @fail_with_error "set-extension", "Error changing due date", xhr + _.defaults(window, { + InstructorDashboard: {} + }); - @$reset_due_date.click => - @clear_display() - @$student_input = @$section.find("#reset-extension input[name='student']") - @$url_input = @$section.find("#reset-extension select[name='url']") - send_data = - student: @$student_input.val() - url: @$url_input.val() + _.defaults(window.InstructorDashboard, { + sections: {} + }); - $.ajax - type: 'POST' - dataType: 'json' - url: @$reset_due_date.data 'endpoint' - data: send_data - success: (data) => @display_response "reset-extension", data - error: (xhr) => @fail_with_error "reset-extension", "Error reseting due date", xhr - - @$show_unit_extensions.click => - @clear_display() - @$grid_table.text 'Loading' - - @$url_input = @$section.find("#view-granted-extensions select[name='url']") - url = @$show_unit_extensions.data 'endpoint' - send_data = - url: @$url_input.val() - $.ajax - type: 'POST' - dataType: 'json' - url: url - data: send_data - error: (xhr) => @fail_with_error "view-granted-extensions", "Error getting due dates", xhr - success: (data) => @display_grid data - - @$show_student_extensions.click => - @clear_display() - @$grid_table.text 'Loading' - - url = @$show_student_extensions.data 'endpoint' - @$student_input = @$section.find("#view-granted-extensions input[name='student']") - send_data = - student: @$student_input.val() - $.ajax - type: 'POST' - dataType: 'json' - url: url - data: send_data - error: (xhr) => @fail_with_error "view-granted-extensions", "Error getting due dates", xhr - success: (data) => @display_grid data - - # handler for when the section title is clicked. - onClickTitle: -> - - fail_with_error: (id, msg, xhr) -> - $task_error = @$section.find("#" + id + " .request-response-error") - $task_response = @$section.find("#" + id + " .request-response") - @clear_display() - data = $.parseJSON xhr.responseText - msg += ": " + data['error'] - console.warn msg - $task_response.empty() - $task_error.empty() - $task_error.text msg - $task_error.show() - - display_response: (id, data) -> - $task_error = @$section.find("#" + id + " .request-response-error") - $task_response = @$section.find("#" + id + " .request-response") - $task_error.empty().hide() - $task_response.empty().text data - $task_response.show() - - display_grid: (data) -> - @clear_display() - @$grid_text.text data.title - - # display on a SlickGrid - options = - enableCellNavigation: true - enableColumnReorder: false - forceFitColumns: true - - columns = ({id: col, field: col, name: col} for col in data.header) - grid_data = data.data - - $table_placeholder = $ '
', class: 'slickgrid', style: 'min-height: 400px' - @$grid_table.append $table_placeholder - grid = new Slick.Grid($table_placeholder, grid_data, columns, options) - - clear_display: -> - @$grid_text.empty() - @$grid_table.empty() - @$section.find(".request-response-error").empty().hide() - @$section.find(".request-response").empty().hide() - -# export for use -# create parent namespaces if they do not already exist. -# abort if underscore can not be found. -if _? - _.defaults window, InstructorDashboard: {} - _.defaults window.InstructorDashboard, sections: {} - _.defaults window.InstructorDashboard.sections, - Extensions: Extensions + _.defaults(window.InstructorDashboard.sections, { + Extensions: Extensions + }); +}).call(this); diff --git a/lms/static/js/instructor_dashboard/instructor_dashboard.js b/lms/static/js/instructor_dashboard/instructor_dashboard.js index 7beee97056..c075ee2680 100644 --- a/lms/static/js/instructor_dashboard/instructor_dashboard.js +++ b/lms/static/js/instructor_dashboard/instructor_dashboard.js @@ -1,4 +1,4 @@ -### +/* Instructor Dashboard Tab Manager The instructor dashboard is broken into sections. @@ -24,179 +24,192 @@ NOTE: For an example of what a section object should look like imports from other modules wrap in (-> ... apply) to defer evaluation such that the value can be defined later than this assignment (file load order). -### - -plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments -std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments - -# CSS classes -CSS_INSTRUCTOR_CONTENT = 'instructor-dashboard-content-2' -CSS_ACTIVE_SECTION = 'active-section' -CSS_IDASH_SECTION = 'idash-section' -CSS_INSTRUCTOR_NAV = 'instructor-nav' - -# prefix for deep-linking -HASH_LINK_PREFIX = '#view-' - -$active_section = null - -# helper class for queueing and fault isolation. -# Will execute functions marked by waiter.after only after all functions marked by -# waiter.waitFor have been called. -# To guarantee this functionality, waitFor and after must be called -# before the functions passed to waitFor are called. -class SafeWaiter - constructor: -> - @after_handlers = [] - @waitFor_handlers = [] - @fired = false - - after: (f) -> - if @fired - f() - else - @after_handlers.push f - - waitFor: (f) -> - return if @fired - @waitFor_handlers.push f - - # wrap the function so that it notifies the waiter - # and can fire the after handlers. - => - @waitFor_handlers = @waitFor_handlers.filter (g) -> g isnt f - if @waitFor_handlers.length is 0 - @fired = true - @after_handlers.map (cb) -> plantTimeout 0, cb - - f.apply this, arguments +*/ -# waiter for dashboard sections. -# Will only execute after all sections have at least attempted to load. -# This is here to facilitate section constructors isolated by setTimeout -# while still being able to interact with them under the guarantee -# that the sections will be initialized at call time. -sections_have_loaded = new SafeWaiter +(function() { + 'use strict'; + var $activeSection, + CSS_ACTIVE_SECTION, CSS_IDASH_SECTION, CSS_INSTRUCTOR_CONTENT, CSS_INSTRUCTOR_NAV, HASH_LINK_PREFIX, + SafeWaiter, plantTimeout, sectionsHaveLoaded, setupInstructorDashboard, + setupInstructorDashboardSections; -# once we're ready, check if this page is the instructor dashboard -$ => - instructor_dashboard_content = $ ".#{CSS_INSTRUCTOR_CONTENT}" - if instructor_dashboard_content.length > 0 - setup_instructor_dashboard instructor_dashboard_content - setup_instructor_dashboard_sections instructor_dashboard_content + plantTimeout = function() { + return window.InstructorDashboard.util.plantTimeout.apply(this, arguments); + }; + CSS_INSTRUCTOR_CONTENT = 'instructor-dashboard-content-2'; -# enable navigation bar -# handles hiding and showing sections -setup_instructor_dashboard = (idash_content) => - # clickable section titles - $links = idash_content.find(".#{CSS_INSTRUCTOR_NAV}").find('.btn-link') + CSS_ACTIVE_SECTION = 'active-section'; - # attach link click handlers - $links.each (i, link) -> - $(link).click (e) -> - e.preventDefault() + CSS_IDASH_SECTION = 'idash-section'; - # deactivate all link & section styles - idash_content.find(".#{CSS_INSTRUCTOR_NAV} li").children().removeClass CSS_ACTIVE_SECTION - idash_content.find(".#{CSS_INSTRUCTOR_NAV} li").children().attr('aria-pressed', 'false') - idash_content.find(".#{CSS_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION + CSS_INSTRUCTOR_NAV = 'instructor-nav'; - # discover section paired to link - section_name = $(this).data 'section' - $section = idash_content.find "##{section_name}" + HASH_LINK_PREFIX = '#view-'; - # activate link & section styling - $(this).addClass CSS_ACTIVE_SECTION - $(this).attr('aria-pressed','true') - $section.addClass CSS_ACTIVE_SECTION + $activeSection = null; - # tracking - analytics.pageview "instructor_section:#{section_name}" + SafeWaiter = (function() { + function safeWaiter() { + this.after_handlers = []; + this.waitFor_handlers = []; + this.fired = false; + } - # deep linking - # write to url - location.hash = "#{HASH_LINK_PREFIX}#{section_name}" + safeWaiter.prototype.afterFor = function(f) { + if (this.fired) { + return f(); + } else { + return this.after_handlers.push(f); + } + }; - sections_have_loaded.after -> - $section.data('wrapper').onClickTitle() + safeWaiter.prototype.waitFor = function(f) { + var safeWait = this; + if (!this.fired) { + this.waitFor_handlers.push(f); + return function() { + safeWait.waitFor_handlers = safeWait.waitFor_handlers.filter(function(g) { + return g !== f; + }); + if (safeWait.waitFor_handlers.length === 0) { + safeWait.fired = true; + safeWait.after_handlers.map(function(cb) { + return plantTimeout(0, cb); + }); + } + return f.apply(safeWait, arguments); + }; + } else { + return false; + } + }; - # call onExit handler if exiting a section to a different section. - unless $section.is $active_section - $active_section?.data('wrapper')?.onExit?() - $active_section = $section + return safeWaiter; + }()); - # TODO enable onExit handler + sectionsHaveLoaded = new SafeWaiter; + $(function() { + var $instructorDashboardContent; + $instructorDashboardContent = $('.' + CSS_INSTRUCTOR_CONTENT); + if ($instructorDashboardContent.length > 0) { + setupInstructorDashboard($instructorDashboardContent); + return setupInstructorDashboardSections($instructorDashboardContent); + } + return setupInstructorDashboardSections($instructorDashboardContent); + }); - # activate an initial section by 'clicking' on it. - # check for a deep-link, or click the first link. - click_first_link = -> - link = $links.eq(0) - link.click() + setupInstructorDashboard = function(idashContent) { + var $links, clickFirstLink, link, rmatch, sectionName; + $links = idashContent.find('.' + CSS_INSTRUCTOR_NAV).find('.btn-link'); + $links.each(function(i, linkItem) { + return $(linkItem).click(function(e) { + var $section, itemSectionName, ref; + e.preventDefault(); + idashContent.find('.' + CSS_INSTRUCTOR_NAV + ' li').children().removeClass(CSS_ACTIVE_SECTION); + idashContent.find('.' + CSS_INSTRUCTOR_NAV + ' li').children().attr('aria-pressed', 'false'); + idashContent.find('.' + CSS_IDASH_SECTION).removeClass(CSS_ACTIVE_SECTION); + itemSectionName = $(this).data('section'); + $section = idashContent.find('#' + itemSectionName); + $(this).addClass(CSS_ACTIVE_SECTION); + $(this).attr('aria-pressed', 'true'); + $section.addClass(CSS_ACTIVE_SECTION); + window.analytics.pageview('instructor_section:' + itemSectionName); + location.hash = '' + HASH_LINK_PREFIX + itemSectionName; + sectionsHaveLoaded.afterFor(function() { + return $section.data('wrapper').onClickTitle(); + }); + if (!$section.is($activeSection)) { + if ($activeSection != null) { + ref = $activeSection.data('wrapper') != null; + if (ref) { + if (typeof ref.onExit === 'function') { + ref.onExit(); + } + } + } + } + $activeSection = $section; + return $activeSection; + }); + }); + clickFirstLink = function() { + var firstLink; + firstLink = $links.eq(0); + return firstLink.click(); + }; + if ((new RegExp('^' + HASH_LINK_PREFIX)).test(location.hash)) { + rmatch = (new RegExp('^' + HASH_LINK_PREFIX + '(.*)')).exec(location.hash); + sectionName = rmatch[1]; + link = $links.filter("[data-section='" + sectionName + "']"); + if (link.length === 1) { + return link.click(); + } else { + return clickFirstLink(); + } + } else { + return clickFirstLink(); + } + }; - if (new RegExp "^#{HASH_LINK_PREFIX}").test location.hash - rmatch = (new RegExp "^#{HASH_LINK_PREFIX}(.*)").exec location.hash - section_name = rmatch[1] - link = $links.filter "[data-section='#{section_name}']" - if link.length == 1 - link.click() - else - click_first_link() - else - click_first_link() - - - -# enable sections -setup_instructor_dashboard_sections = (idash_content) -> - sections_to_initialize = [ - constructor: window.InstructorDashboard.sections.CourseInfo - $element: idash_content.find ".#{CSS_IDASH_SECTION}#course_info" - , - constructor: window.InstructorDashboard.sections.DataDownload - $element: idash_content.find ".#{CSS_IDASH_SECTION}#data_download" - , - constructor: window.InstructorDashboard.sections.ECommerce - $element: idash_content.find ".#{CSS_IDASH_SECTION}#e-commerce" - , - constructor: window.InstructorDashboard.sections.Membership - $element: idash_content.find ".#{CSS_IDASH_SECTION}#membership" - , - constructor: window.InstructorDashboard.sections.StudentAdmin - $element: idash_content.find ".#{CSS_IDASH_SECTION}#student_admin" - , - constructor: window.InstructorDashboard.sections.Extensions - $element: idash_content.find ".#{CSS_IDASH_SECTION}#extensions" - , - constructor: window.InstructorDashboard.sections.Email - $element: idash_content.find ".#{CSS_IDASH_SECTION}#send_email" - , - constructor: window.InstructorDashboard.sections.InstructorAnalytics - $element: idash_content.find ".#{CSS_IDASH_SECTION}#instructor_analytics" - , - constructor: window.InstructorDashboard.sections.Metrics - $element: idash_content.find ".#{CSS_IDASH_SECTION}#metrics" - , - constructor: window.InstructorDashboard.sections.CohortManagement - $element: idash_content.find ".#{CSS_IDASH_SECTION}#cohort_management" - , - constructor: window.InstructorDashboard.sections.Certificates - $element: idash_content.find ".#{CSS_IDASH_SECTION}#certificates" - ] - # proctoring can be feature disabled - if edx.instructor_dashboard.proctoring != undefined - sections_to_initialize = sections_to_initialize.concat [ - constructor: edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView - $element: idash_content.find ".#{CSS_IDASH_SECTION}#special_exams" - , - constructor: edx.instructor_dashboard.proctoring.ProctoredExamAttemptView - $element: idash_content.find ".#{CSS_IDASH_SECTION}#special_exams" - ] - - sections_to_initialize.map ({constructor, $element}) -> - # See fault isolation NOTE at top of file. - # If an error is thrown in one section, it will not stop other sections from exectuing. - plantTimeout 0, sections_have_loaded.waitFor -> - new constructor $element + setupInstructorDashboardSections = function(idashContent) { + var sectionsToInitialize; + sectionsToInitialize = [ + { + constructor: window.InstructorDashboard.sections.CourseInfo, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#course_info') + }, { + constructor: window.InstructorDashboard.sections.DataDownload, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#data_download') + }, { + constructor: window.InstructorDashboard.sections.ECommerce, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#e-commerce') + }, { + constructor: window.InstructorDashboard.sections.Membership, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#membership') + }, { + constructor: window.InstructorDashboard.sections.StudentAdmin, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#student_admin') + }, { + constructor: window.InstructorDashboard.sections.Extensions, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#extensions') + }, { + constructor: window.InstructorDashboard.sections.Email, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#send_email') + }, { + constructor: window.InstructorDashboard.sections.InstructorAnalytics, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#instructor_analytics') + }, { + constructor: window.InstructorDashboard.sections.Metrics, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#metrics') + }, { + constructor: window.InstructorDashboard.sections.CohortManagement, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#cohort_management') + }, { + constructor: window.InstructorDashboard.sections.Certificates, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates') + } + ]; + if (edx.instructor_dashboard.proctoring !== void 0) { + sectionsToInitialize = sectionsToInitialize.concat([ + { + constructor: edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#special_exams') + }, { + constructor: edx.instructor_dashboard.proctoring.ProctoredExamAttemptView, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#special_exams') + } + ]); + } + return sectionsToInitialize.map(function(_arg) { + var $element, constructor; + constructor = _arg.constructor; + $element = _arg.$element; + return plantTimeout(0, sectionsHaveLoaded.waitFor(function() { + return new constructor($element); + })); + }); + }; +}).call(this); diff --git a/lms/static/js/instructor_dashboard/membership.js b/lms/static/js/instructor_dashboard/membership.js index 1f1febb087..e45bb8998b 100644 --- a/lms/static/js/instructor_dashboard/membership.js +++ b/lms/static/js/instructor_dashboard/membership.js @@ -1,740 +1,976 @@ -### +/* globals _, AutoEnrollmentViaCsv, NotificationModel, NotificationView */ + +/* Membership Section imports from other modules. wrap in (-> ... apply) to defer evaluation such that the value can be defined later than this assignment (file load order). -### - -plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments -std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments -emailStudents = false +*/ -class MemberListWidget - # create a MemberListWidget `$container` is a jquery object to embody. - # `params` holds template parameters. `params` should look like the defaults below. - constructor: (@$container, params={}) -> - params = _.defaults params, - title: "Member List" - info: """ - Use this list to manage members. - """ - labels: ["field1", "field2", "field3"] - add_placeholder: "Enter name" - add_btn_label: "Add Member" - add_handler: (input) -> +(function() { + 'use strict'; + var AuthListWidget, BatchEnrollment, BetaTesterBulkAddition, + MemberListWidget, Membership, emailStudents, plantTimeout, statusAjaxError; - template_html = $("#member-list-widget-template").html() - @$container.html Mustache.render template_html, params + plantTimeout = function() { + return window.InstructorDashboard.util.plantTimeout.apply(this, arguments); + }; - # bind add button - @$('input[type="button"].add').click => - params.add_handler? @$('.add-field').val() + statusAjaxError = function() { + return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments); + }; - # clear the input text field - clear_input: -> @$('.add-field').val '' + emailStudents = false; - # clear all table rows - clear_rows: -> @$('table tbody').empty() + MemberListWidget = (function() { + function memberListWidget($container, params) { + var templateHtml, condition, + memberListParams = params || {}, + memberlistwidget = this; + this.$container = $container; + memberListParams = _.defaults(memberListParams, { + title: 'Member List', + info: 'Use this list to manage members.', + labels: ['field1', 'field2', 'field3'], + add_placeholder: 'Enter name', + add_btn_label: 'Add Member', + add_handler: function() {} + }); + templateHtml = $('#member-list-widget-template').html(); + edx.HtmlUtils.setHtml( + this.$container, window.Mustache.render(templateHtml, edx.HtmlUtils.HTML(memberListParams)) + ); + this.$('input[type="button"].add').click(function() { + condition = typeof memberListParams.add_handler === 'function'; + return condition ? memberListParams.add_handler(memberlistwidget.$('.add-field').val()) : undefined; + }); + } - # takes a table row as an array items are inserted as text, unless detected - # as a jquery objects in which case they are inserted directly. if an - # element is a jquery object - add_row: (row_array) -> - $tbody = @$('table tbody') - $tr = $ '' - for item in row_array - $td = $ '' - if item instanceof jQuery - $td.append item - else - $td.text item - $tr.append $td - $tbody.append $tr + memberListWidget.prototype.clear_input = function() { + return this.$('.add-field').val(''); + }; - # local selector - $: (selector) -> - if @debug? - s = @$container.find selector - if s?.length != 1 - console.warn "local selector '#{selector}' found (#{s.length}) results" - s - else - @$container.find selector + memberListWidget.prototype.clear_rows = function() { + return this.$('table tbody').empty(); + }; + memberListWidget.prototype.add_row = function(rowArray) { + var $tbody, $td, $tr, item, i, len; + $tbody = this.$('table tbody'); + $tr = $(''); + for (i = 0, len = rowArray.length; i < len; i++) { + item = rowArray[i]; + $td = $(''); + if (item instanceof jQuery) { + edx.HtmlUtils.append($td, item); + } else { + $td.text(item); + } + $tr.append($td); + } + return $tbody.append($tr); + }; -class AuthListWidget extends MemberListWidget - constructor: ($container, @rolename, @$error_section) -> - super $container, - title: $container.data 'display-name' - info: $container.data 'info-text' - labels: [gettext("Username"), gettext("Email"), gettext("Revoke access")] - add_placeholder: gettext("Enter username or email") - add_btn_label: $container.data 'add-button-label' - add_handler: (input) => @add_handler input + memberListWidget.prototype.$ = function(selector) { + var s; + if (this.debug != null) { + s = this.$container.find(selector); + return s; + } else { + return this.$container.find(selector); + } + }; - @debug = true - @list_endpoint = $container.data 'list-endpoint' - @modify_endpoint = $container.data 'modify-endpoint' - unless @rolename? - throw "AuthListWidget missing @rolename" + return memberListWidget; + }()); - @reload_list() + AuthListWidget = (function() { + function authListWidget($container, rolename, $errorSection) { + var msg, + authlistwidget = this; + this.rolename = rolename; + this.$errorSection = $errorSection; + authListWidget.super.constructor.call(this, $container, { + title: $container.data('display-name'), + info: $container.data('info-text'), + labels: [gettext('Username'), gettext('Email'), gettext('Revoke access')], + add_placeholder: gettext('Enter username or email'), + add_btn_label: $container.data('add-button-label'), + add_handler: function(input) { + return authlistwidget.add_handler(input); + } + }); + this.debug = true; + this.list_endpoint = $container.data('list-endpoint'); + this.modify_endpoint = $container.data('modify-endpoint'); + if (this.rolename == null) { + msg = 'AuthListWidget missing @rolename'; + throw msg; + } + this.reload_list(); + } - # action to do when is reintroduced into user's view - re_view: -> - @clear_errors() - @clear_input() - @reload_list() + authListWidget.prototype.re_view = function() { + this.clear_errors(); + this.clear_input(); + return this.reload_list(); + }; - # handle clicks on the add button - add_handler: (input) -> - if input? and input isnt '' - @modify_member_access input, 'allow', (error) => - # abort on error - return @show_errors error unless error is null - @clear_errors() - @clear_input() - @reload_list() - else - @show_errors gettext "Please enter a username or email." + authListWidget.prototype.add_handler = function(input) { + var authlistwidgetaddhandler = this; + if ((input != null) && input !== '') { + return this.modify_member_access(input, 'allow', function(error) { + if (error !== null) { + return authlistwidgetaddhandler.show_errors(error); + } + authlistwidgetaddhandler.clear_errors(); + authlistwidgetaddhandler.clear_input(); + return authlistwidgetaddhandler.reload_list(); + }); + } else { + return this.show_errors(gettext('Please enter a username or email.')); + } + }; - # reload the list of members - reload_list: -> - # @clear_rows() - @get_member_list (error, member_list) => - # abort on error - return @show_errors error unless error is null + authListWidget.prototype.reload_list = function() { + var authlistwidgetreloadlist = this; + return this.get_member_list(function(error, memberList) { + if (error !== null) { + return authlistwidgetreloadlist.show_errors(error); + } + authlistwidgetreloadlist.clear_rows(); + return _.each(memberList, function(member) { + var $revokeBtn, labelTrans; + labelTrans = gettext('Revoke access'); + $revokeBtn = $(_.template('
<%- label %>
')({ // eslint-disable-line max-len + label: labelTrans + }), { + class: 'revoke' + }); + $revokeBtn.click(function() { + return authlistwidgetreloadlist.modify_member_access(member.email, 'revoke', function(err) { + if (err !== null) { + return authlistwidgetreloadlist.show_errors(err); + } + authlistwidgetreloadlist.clear_errors(); + return authlistwidgetreloadlist.reload_list(); + }); + }); + return authlistwidgetreloadlist.add_row([member.username, member.email, $revokeBtn]); + }); + }); + }; - # only show the list of there are members - @clear_rows() + authListWidget.prototype.clear_errors = function() { + var ref, result; + result = (this.$error_section) != null ? ref.text('') : undefined; + return result; + }; - # use _.each instead of 'for' so that member - # is bound in the button callback. - _.each member_list, (member) => - # if there are members, show the list + authListWidget.prototype.show_errors = function(msg) { + var ref, result; + result = (this.$error_section) != null ? ref.text(msg) : undefined; + return result; + }; - # create revoke button and insert it into the row - label_trans = gettext("Revoke access") - $revoke_btn = $ _.template('
<%- label %>
')({label: label_trans}), - class: 'revoke' - $revoke_btn.click => - @modify_member_access member.email, 'revoke', (error) => - # abort on error - return @show_errors error unless error is null - @clear_errors() - @reload_list() - @add_row [member.username, member.email, $revoke_btn] + authListWidget.prototype.get_member_list = function(cb) { + var authlistwidgetgetmemberlist = this; + return $.ajax({ + type: 'POST', + dataType: 'json', + url: this.list_endpoint, + data: { + rolename: this.rolename + }, + success: function(data) { + return typeof cb === 'function' ? cb(null, data[authlistwidgetgetmemberlist.rolename]) : undefined; + } + }); + }; - # clear error display - clear_errors: -> @$error_section?.text '' + authListWidget.prototype.modify_member_access = function(uniqueStudentIdentifier, action, cb) { + var authlistwidgetmemberaccess = this; + return $.ajax({ + type: 'POST', + dataType: 'json', + url: this.modify_endpoint, + data: { + unique_student_identifier: uniqueStudentIdentifier, + rolename: this.rolename, + action: action + }, + success: function(data) { + return authlistwidgetmemberaccess.member_response(data); + }, + error: statusAjaxError(function() { + return typeof cb === 'function' ? cb(gettext("Error changing user's permissions.")) : undefined; + }) + }); + }; - # set error display - show_errors: (msg) -> @$error_section?.text msg + authListWidget.prototype.member_response = function(data) { + var msg; + this.clear_errors(); + this.clear_input(); + if (data.userDoesNotExist) { + msg = gettext("Could not find a user with username or email address '<%- identifier %>'."); + return this.show_errors(_.template(msg, { + identifier: data.unique_student_identifier + })); + } else if (data.inactiveUser) { + msg = gettext("Error: User '<%- username %>' has not yet activated their account. Users must create and activate their accounts before they can be assigned a role."); // eslint-disable-line max-len + return this.show_errors(_.template(msg, { + username: data.unique_student_identifier + })); + } else if (data.removingSelfAsInstructor) { + return this.show_errors( + gettext('Error: You cannot remove yourself from the Instructor group!') + ); + } else { + return this.reload_list(); + } + }; - # send ajax request to list members - # `cb` is called with cb(error, member_list) - get_member_list: (cb) -> - $.ajax - type: 'POST' - dataType: 'json' - url: @list_endpoint - data: rolename: @rolename - success: (data) => cb? null, data[@rolename] - error: std_ajax_err => - `// Translators: A rolename appears this sentence. A rolename is something like "staff" or "beta tester".` - cb? gettext("Error fetching list for role") + " '#{@rolename}'" + return authListWidget; + }(MemberListWidget)); - # send ajax request to modify access - # (add or remove them from the list) - # `action` can be 'allow' or 'revoke' - # `cb` is called with cb(error, data) - modify_member_access: (unique_student_identifier, action, cb) -> - $.ajax - type: 'POST' - dataType: 'json' - url: @modify_endpoint - data: - unique_student_identifier: unique_student_identifier - rolename: @rolename - action: action - success: (data) => @member_response data - error: std_ajax_err => cb? gettext "Error changing user's permissions." + this.AutoEnrollmentViaCsv = (function() { + function AutoEnrollmentViaCsv($container) { + var autoenrollviacsv = this; + this.$container = $container; + this.$student_enrollment_form = this.$container.find('#student-auto-enroll-form'); + this.$enrollment_signup_button = this.$container.find('#submitBtn-auto_enroll_csv'); + this.$students_list_file = this.$container.find("input[name='students_list']"); + this.$csrf_token = this.$container.find("input[name='csrfmiddlewaretoken']"); + this.$results = this.$container.find('div.results'); + this.$browse_button = this.$container.find('#browseBtn-auto-enroll'); + this.$browse_file = this.$container.find('#browseFile'); + this.processing = false; + this.$browse_button.on('change', function(event) { + if (event.currentTarget.files.length === 1) { + return autoenrollviacsv.$browse_file.val( + event.currentTarget.value.substring(event.currentTarget.value.lastIndexOf('\\') + 1) + ); + } + return false; + }); + this.$enrollment_signup_button.click(function() { + return autoenrollviacsv.$student_enrollment_form.submit(function(event) { + var data; + if (autoenrollviacsv.processing) { + return false; + } + autoenrollviacsv.processing = true; + event.preventDefault(); + data = new FormData(event.currentTarget); + $.ajax({ + dataType: 'json', + type: 'POST', + url: event.currentTarget.action, + data: data, + processData: false, + contentType: false, + success: function(responsedata) { + autoenrollviacsv.processing = false; + return autoenrollviacsv.display_response(responsedata); + } + }); + return false; + }); + }); + } - member_response: (data) -> - @clear_errors() - @clear_input() - if data.userDoesNotExist - msg = gettext("Could not find a user with username or email address '<%- identifier %>'.") - @show_errors _.template(msg, {identifier: data.unique_student_identifier}) - else if data.inactiveUser - msg = gettext("Error: User '<%- username %>' has not yet activated their account. Users must create and activate their accounts before they can be assigned a role.") - @show_errors _.template(msg, {username: data.unique_student_identifier}) - else if data.removingSelfAsInstructor - @show_errors gettext "Error: You cannot remove yourself from the Instructor group!" - else - @reload_list() + AutoEnrollmentViaCsv.prototype.display_response = function(dataFromServer) { + var error, errors, generalError, renderResponse, + resultFromServerIsSuccess, warning, warnings, + i, j, k, len, len1, len2, ref, ref1, ref2, + displayResponse = this; + this.$results.empty(); + errors = []; + warnings = []; + resultFromServerIsSuccess = true; + if (dataFromServer.general_errors.length) { + resultFromServerIsSuccess = false; + ref = dataFromServer.general_errors; + for (i = 0, len = ref.length; i < len; i++) { + generalError = ref[i]; + generalError.is_general_error = true; + errors.push(generalError); + } + } + if (dataFromServer.row_errors.length) { + resultFromServerIsSuccess = false; + ref1 = dataFromServer.row_errors; + for (j = 0, len1 = ref1.length; j < len1; j++) { + error = ref1[j]; + error.is_general_error = false; + errors.push(error); + } + } + if (dataFromServer.warnings.length) { + resultFromServerIsSuccess = false; + ref2 = dataFromServer.warnings; + for (k = 0, len2 = ref2.length; k < len2; k++) { + warning = ref2[k]; + warning.is_general_error = false; + warnings.push(warning); + } + } + renderResponse = function(title, message, type, studentResults) { + var details, responseMessage, studentResult, l, len3; + details = []; + for (l = 0, len3 = studentResults.length; l < len3; l++) { + studentResult = studentResults[l]; + if (studentResult.is_general_error) { + details.push(studentResult.response); + } else { + responseMessage = studentResult.username + ' (' + studentResult.email + '): ' + ' (' + studentResult.response + ')'; // eslint-disable-line max-len, no-useless-concat + details.push(responseMessage); + } + } + return edx.HtmlUtils.append(displayResponse.$results, + edx.HtmlUtils.HTML(displayResponse.render_notification_view(type, title, message, details)) + ); + }; + if (errors.length) { + renderResponse(gettext('Errors'), + gettext('The following errors were generated:'), 'error', errors + ); + } + if (warnings.length) { + renderResponse(gettext('Warnings'), + gettext('The following warnings were generated:'), 'warning', warnings + ); + } + if (resultFromServerIsSuccess) { + return renderResponse(gettext('Success'), + gettext('All accounts were created successfully.'), 'confirmation', [] + ); + } + return renderResponse(); + }; -class @AutoEnrollmentViaCsv - constructor: (@$container) -> - # Wrapper for the AutoEnrollmentViaCsv subsection. - # This object handles buttons, success and failure reporting, - # and server communication. - @$student_enrollment_form = @$container.find("#student-auto-enroll-form") - @$enrollment_signup_button = @$container.find("#submitBtn-auto_enroll_csv") - @$students_list_file = @$container.find("input[name='students_list']") - @$csrf_token = @$container.find("input[name='csrfmiddlewaretoken']") - @$results = @$container.find("div.results") - @$browse_button = @$container.find("#browseBtn-auto-enroll") - @$browse_file = @$container.find("#browseFile") + AutoEnrollmentViaCsv.prototype.render_notification_view = function(type, title, message, details) { + var notificationModel, view; + notificationModel = new NotificationModel(); + notificationModel.set({ + type: type, + title: title, + message: message, + details: details + }); + view = new NotificationView({ + model: notificationModel + }); + view.render(); + return view.$el.html(); + }; - @processing = false + return AutoEnrollmentViaCsv; + }()); - @$browse_button.on "change", (event) => - if event.currentTarget.files.length == 1 - @$browse_file.val(event.currentTarget.value.substring(event.currentTarget.value.lastIndexOf("\\") + 1)) + BetaTesterBulkAddition = (function() { + function betaTesterBulkAddition($container) { + var betatest = this; + this.$container = $container; + this.$identifier_input = this.$container.find("textarea[name='student-ids-for-beta']"); + this.$btn_beta_testers = this.$container.find("input[name='beta-testers']"); + this.$checkbox_autoenroll = this.$container.find("input[name='auto-enroll']"); + this.$checkbox_emailstudents = this.$container.find("input[name='email-students-beta']"); + this.$task_response = this.$container.find('.request-response'); + this.$request_response_error = this.$container.find('.request-response-error'); + this.$btn_beta_testers.click(function(event) { + var autoEnroll, sendData; + emailStudents = betatest.$checkbox_emailstudents.is(':checked'); + autoEnroll = betatest.$checkbox_autoenroll.is(':checked'); + sendData = { + action: $(event.target).data('action'), + identifiers: betatest.$identifier_input.val(), + email_students: emailStudents, + auto_enroll: autoEnroll + }; + return $.ajax({ + dataType: 'json', + type: 'POST', + url: betatest.$btn_beta_testers.data('endpoint'), + data: sendData, + success: function(data) { + return betatest.display_response(data); + }, + error: statusAjaxError(function() { + return betatest.fail_with_error(gettext('Error adding/removing users as beta testers.')); + }) + }); + }); + } - # attach click handler for @$enrollment_signup_button - @$enrollment_signup_button.click => - @$student_enrollment_form.submit (event) => - if @processing - return false + betaTesterBulkAddition.prototype.clear_input = function() { + this.$identifier_input.val(''); + this.$checkbox_emailstudents.attr('checked', true); + return this.$checkbox_autoenroll.attr('checked', true); + }; - @processing = true + betaTesterBulkAddition.prototype.fail_with_error = function(msg) { + this.clear_input(); + this.$task_response.empty(); + this.$request_response_error.empty(); + return this.$request_response_error.text(msg); + }; - event.preventDefault() - data = new FormData(event.currentTarget) - $.ajax - dataType: 'json' - type: 'POST' - url: event.currentTarget.action - data: data - processData: false - contentType: false - success: (data) => - @processing = false - @display_response data + betaTesterBulkAddition.prototype.display_response = function(dataFromServer) { + var errors, noUsers, renderList, sr, studentResults, successes, i, len, ref, + displayResponse = this; + this.clear_input(); + this.$task_response.empty(); + this.$request_response_error.empty(); + errors = []; + successes = []; + noUsers = []; + ref = dataFromServer.results; + for (i = 0, len = ref.length; i < len; i++) { + studentResults = ref[i]; + if (studentResults.userDoesNotExist) { + noUsers.push(studentResults); + } else if (studentResults.error) { + errors.push(studentResults); + } else { + successes.push(studentResults); + } + } + renderList = function(label, ids) { + var identifier, $idsList, $taskResSection, j, len1; + $taskResSection = $('
', { + class: 'request-res-section' + }); + $taskResSection.append($('

', { + text: label + })); + $idsList = $('
    '); + $taskResSection.append($idsList); + for (j = 0, len1 = ids.length; j < len1; j++) { + identifier = ids[j]; + $idsList.append($('
  • ', { + text: identifier + })); + } + return displayResponse.$task_response.append($taskResSection); + }; + if (successes.length && dataFromServer.action === 'add') { + // Translators: A list of users appears after this sentence; + renderList(gettext('These users were successfully added as beta testers:'), (function() { + var j, len1, results; + results = []; + for (j = 0, len1 = successes.length; j < len1; j++) { + sr = successes[j]; + results.push(sr.identifier); + } + return results; + }())); + } + if (successes.length && dataFromServer.action === 'remove') { + // Translators: A list of users appears after this sentence; + renderList(gettext('These users were successfully removed as beta testers:'), (function() { + var j, len1, results; + results = []; + for (j = 0, len1 = successes.length; j < len1; j++) { + sr = successes[j]; + results.push(sr.identifier); + } + return results; + }())); + } + if (errors.length && dataFromServer.action === 'add') { + // Translators: A list of users appears after this sentence; + renderList(gettext('These users were not added as beta testers:'), (function() { + var j, len1, results; + results = []; + for (j = 0, len1 = errors.length; j < len1; j++) { + sr = errors[j]; + results.push(sr.identifier); + } + return results; + }())); + } + if (errors.length && dataFromServer.action === 'remove') { + // Translators: A list of users appears after this sentence; + renderList(gettext('These users were not removed as beta testers:'), (function() { + var j, len1, results; + results = []; + for (j = 0, len1 = errors.length; j < len1; j++) { + sr = errors[j]; + results.push(sr.identifier); + } + return results; + }())); + } + if (noUsers.length) { + noUsers.push($( + gettext('Users must create and activate their account before they can be promoted to beta tester.')) + ); + return renderList(gettext('Could not find users associated with the following identifiers:'), (function() { // eslint-disable-line max-len + var j, len1, results; + results = []; + for (j = 0, len1 = noUsers.length; j < len1; j++) { + sr = noUsers[j]; + results.push(sr.identifier); + } + return results; + }())); + } + return renderList(); + }; - return false + return betaTesterBulkAddition; + }()); - display_response: (data_from_server) -> - @$results.empty() - errors = [] - warnings = [] - result_from_server_is_success = true + BatchEnrollment = (function() { + function batchEnrollment($container) { + var batchEnroll = this; + this.$container = $container; + this.$identifier_input = this.$container.find("textarea[name='student-ids']"); + this.$enrollment_button = this.$container.find('.enrollment-button'); + this.$is_course_white_label = this.$container.find('#is_course_white_label').val(); + this.$reason_field = this.$container.find("textarea[name='reason-field']"); + this.$checkbox_autoenroll = this.$container.find("input[name='auto-enroll']"); + this.$checkbox_emailstudents = this.$container.find("input[name='email-students']"); + this.$task_response = this.$container.find('.request-response'); + this.$request_response_error = this.$container.find('.request-response-error'); + this.$enrollment_button.click(function(event) { + var sendData; + if (batchEnroll.$is_course_white_label === 'True') { + if (!batchEnroll.$reason_field.val()) { + batchEnroll.fail_with_error(gettext('Reason field should not be left blank.')); + return false; + } + } + emailStudents = batchEnroll.$checkbox_emailstudents.is(':checked'); + sendData = { + action: $(event.target).data('action'), + identifiers: batchEnroll.$identifier_input.val(), + auto_enroll: batchEnroll.$checkbox_autoenroll.is(':checked'), + email_students: emailStudents, + reason: batchEnroll.$reason_field.val() + }; + return $.ajax({ + dataType: 'json', + type: 'POST', + url: $(event.target).data('endpoint'), + data: sendData, + success: function(data) { + return batchEnroll.display_response(data); + }, + error: statusAjaxError(function() { + return batchEnroll.fail_with_error(gettext('Error enrolling/unenrolling users.')); + }) + }); + }); + } - if data_from_server.general_errors.length - result_from_server_is_success = false - for general_error in data_from_server.general_errors - general_error['is_general_error'] = true - errors.push general_error + batchEnrollment.prototype.clear_input = function() { + this.$identifier_input.val(''); + this.$reason_field.val(''); + this.$checkbox_emailstudents.attr('checked', true); + return this.$checkbox_autoenroll.attr('checked', true); + }; - if data_from_server.row_errors.length - result_from_server_is_success = false - for error in data_from_server.row_errors - error['is_general_error'] = false - errors.push error + batchEnrollment.prototype.fail_with_error = function(msg) { + this.clear_input(); + this.$task_response.empty(); + this.$request_response_error.empty(); + return this.$request_response_error.text(msg); + }; - if data_from_server.warnings.length - result_from_server_is_success = false - for warning in data_from_server.warnings - warning['is_general_error'] = false - warnings.push warning + batchEnrollment.prototype.display_response = function(dataFromServer) { + var allowed, autoenrolled, enrolled, errors, errorsLabel, + invalidIdentifier, notenrolled, notunenrolled, renderList, sr, studentResults, + i, j, len, len1, ref, renderIdsLists, + displayResponse = this; + this.clear_input(); + this.$task_response.empty(); + this.$request_response_error.empty(); + invalidIdentifier = []; + errors = []; + enrolled = []; + allowed = []; + autoenrolled = []; + notenrolled = []; + notunenrolled = []; + ref = dataFromServer.results; + for (i = 0, len = ref.length; i < len; i++) { + studentResults = ref[i]; + if (studentResults.invalidIdentifier) { + invalidIdentifier.push(studentResults); + } else if (studentResults.error) { + errors.push(studentResults); + } else if (studentResults.after.enrollment) { + enrolled.push(studentResults); + } else if (studentResults.after.allowed) { + if (studentResults.after.auto_enroll) { + autoenrolled.push(studentResults); + } else { + allowed.push(studentResults); + } + } else if (dataFromServer.action === 'unenroll' && + !studentResults.before.enrollment && + !studentResults.before.allowed) { + notunenrolled.push(studentResults); + } else if (!studentResults.after.enrollment) { + notenrolled.push(studentResults); + } else { + console.warn('student results not reported to user'); // eslint-disable-line no-console + } + } + renderList = function(label, ids) { + var identifier, $idsList, $taskResSection, h, len3; + $taskResSection = $('
    ', { + class: 'request-res-section' + }); + $taskResSection.append($('

    ', { + text: label + })); + $idsList = $('
      '); + $taskResSection.append($idsList); + for (h = 0, len3 = ids.length; h < len3; h++) { + identifier = ids[h]; + $idsList.append($('
    • ', { + text: identifier + })); + } + return displayResponse.$task_response.append($taskResSection); + }; + if (invalidIdentifier.length) { + renderList(gettext('The following email addresses and/or usernames are invalid:'), (function() { + var m, len4, results; + results = []; + for (m = 0, len4 = invalidIdentifier.length; m < len4; m++) { + sr = invalidIdentifier[m]; + results.push(sr.identifier); + } + return results; + }())); + } + if (errors.length) { + errorsLabel = (function() { + if (dataFromServer.action === 'enroll') { + return 'There was an error enrolling:'; + } else if (dataFromServer.action === 'unenroll') { + return 'There was an error unenrolling:'; + } else { + console.warn("unknown action from server '" + dataFromServer.action + "'"); // eslint-disable-line no-console, max-len + return 'There was an error processing:'; + } + }()); + renderIdsLists = function(errs) { + var srItem, + k = 0, + results = []; + for (k = 0, len = errs.length; k < len; k++) { + srItem = errs[k]; + results.push(srItem.identifier); + } + return results; + }; + for (j = 0, len1 = errors.length; j < len1; j++) { + studentResults = errors[j]; + renderList(errorsLabel, renderIdsLists(errors)); + } + } + if (enrolled.length && emailStudents) { + renderList(gettext('Successfully enrolled and sent email to the following users:'), (function() { + var k, len2, results; + results = []; + for (k = 0, len2 = enrolled.length; k < len2; k++) { + sr = enrolled[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (enrolled.length && !emailStudents) { + // Translators: A list of users appears after this sentence; + renderList(gettext('Successfully enrolled the following users:'), (function() { + var k, len2, results; + results = []; + for (k = 0, len2 = enrolled.length; k < len2; k++) { + sr = enrolled[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (allowed.length && emailStudents) { + // Translators: A list of users appears after this sentence; + renderList(gettext('Successfully sent enrollment emails to the following users. They will be allowed to enroll once they register:'), (function() { // eslint-disable-line max-len + var k, len2, results; + results = []; + for (k = 0, len2 = allowed.length; k < len2; k++) { + sr = allowed[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (allowed.length && !emailStudents) { + // Translators: A list of users appears after this sentence; + renderList(gettext('These users will be allowed to enroll once they register:'), (function() { + var k, len2, results; + results = []; + for (k = 0, len2 = allowed.length; k < len2; k++) { + sr = allowed[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (autoenrolled.length && emailStudents) { + // Translators: A list of users appears after this sentence; + renderList(gettext('Successfully sent enrollment emails to the following users. They will be enrolled once they register:'), (function() { // eslint-disable-line max-len + var k, len2, results; + results = []; + for (k = 0, len2 = autoenrolled.length; k < len2; k++) { + sr = autoenrolled[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (autoenrolled.length && !emailStudents) { + // Translators: A list of users appears after this sentence; + renderList(gettext('These users will be enrolled once they register:'), (function() { + var k, len2, results; + results = []; + for (k = 0, len2 = autoenrolled.length; k < len2; k++) { + sr = autoenrolled[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (notenrolled.length && emailStudents) { + // Translators: A list of users appears after this sentence; + renderList(gettext('Emails successfully sent. The following users are no longer enrolled in the course:'), (function() { // eslint-disable-line max-len + var k, len2, results; + results = []; + for (k = 0, len2 = notenrolled.length; k < len2; k++) { + sr = notenrolled[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (notenrolled.length && !emailStudents) { + // Translators: A list of users appears after this sentence; + renderList(gettext('The following users are no longer enrolled in the course:'), (function() { + var k, len2, results; + results = []; + for (k = 0, len2 = notenrolled.length; k < len2; k++) { + sr = notenrolled[k]; + results.push(sr.identifier); + } + return results; + }())); + } + if (notunenrolled.length) { + return renderList(gettext('These users were not affiliated with the course so could not be unenrolled:'), (function() { // eslint-disable-line max-len + var k, len2, results; + results = []; + for (k = 0, len2 = notunenrolled.length; k < len2; k++) { + sr = notunenrolled[k]; + results.push(sr.identifier); + } + return results; + }())); + } + return renderList(); + }; - render_response = (title, message, type, student_results) => - details = [] - for student_result in student_results - if student_result.is_general_error - details.push student_result.response - else - response_message = student_result.username + ' ('+ student_result.email + '): ' + ' (' + student_result.response + ')' - details.push response_message + return batchEnrollment; + }()); - @$results.append @render_notification_view type, title, message, details + this.AuthList = (function() { + function authList($container, rolename) { + var authlist = this; + this.$container = $container; + this.rolename = rolename; + this.$display_table = this.$container.find('.auth-list-table'); + this.$request_response_error = this.$container.find('.request-response-error'); + this.$add_section = this.$container.find('.auth-list-add'); + this.$allow_field = this.$add_section.find("input[name='email']"); + this.$allow_button = this.$add_section.find("input[name='allow']"); + this.$allow_button.click(function() { + authlist.access_change(authlist.$allow_field.val(), 'allow', function() { + return authlist.reload_auth_list(); + }); + return authlist.$allow_field.val(''); + }); + this.reload_auth_list(); + } - if errors.length - render_response gettext('Errors'), gettext("The following errors were generated:"), 'error', errors - if warnings.length - render_response gettext('Warnings'), gettext("The following warnings were generated:"), 'warning', warnings - if result_from_server_is_success - render_response gettext('Success'), gettext("All accounts were created successfully."), 'confirmation', [] + authList.prototype.reload_auth_list = function() { + var loadAuthList, + ths = this; + loadAuthList = function(data) { + var $tablePlaceholder, WHICH_CELL_IS_REVOKE, columns, grid, options, tableData; + ths.$request_response_error.empty(); + ths.$display_table.empty(); + options = { + enableCellNavigation: true, + enableColumnReorder: false, + forceFitColumns: true + }; + WHICH_CELL_IS_REVOKE = 3; + columns = [ + { + id: 'username', + field: 'username', + name: 'Username' + }, { + id: 'email', + field: 'email', + name: 'Email' + }, { + id: 'first_name', + field: 'first_name', + name: 'First Name' + }, { + id: 'revoke', + field: 'revoke', + name: 'Revoke', + formatter: function() { + return "Revoke Access"; + } + } + ]; + tableData = data[ths.rolename]; + $tablePlaceholder = $('
      ', { + class: 'slickgrid' + }); + ths.$display_table.append($tablePlaceholder); + grid = new window.Slick.Grid($tablePlaceholder, tableData, columns, options); + return grid.onClick.subscribe(function(e, args) { + var item; + item = args.grid.getDataItem(args.row); + if (args.cell === WHICH_CELL_IS_REVOKE) { + return ths.access_change(item.email, 'revoke', function() { + return ths.reload_auth_list(); + }); + } + return false; + }); + }; + return $.ajax({ + dataType: 'json', + type: 'POST', + url: this.$display_table.data('endpoint'), + data: { + rolename: this.rolename + }, + success: loadAuthList, + error: statusAjaxError(function() { + return ths.$request_response_error.text("Error fetching list for '" + ths.rolename + "'"); + }) + }); + }; - render_notification_view: (type, title, message, details) -> - notification_model = new NotificationModel() - notification_model.set({ - 'type': type, - 'title': title, - 'message': message, - 'details': details, + authList.prototype.refresh = function() { + this.$display_table.empty(); + return this.reload_auth_list(); + }; + + authList.prototype.access_change = function(email, action, cb) { + var ths = this; + return $.ajax({ + dataType: 'json', + type: 'POST', + url: this.$add_section.data('endpoint'), + data: { + email: email, + rolename: this.rolename, + action: action + }, + success: function(data) { + return typeof cb === 'function' ? cb(data) : undefined; + }, + error: statusAjaxError(function() { + return ths.$request_response_error.text(gettext("Error changing user's permissions.")); + }) + }); + }; + + return authList; + }()); + + Membership = (function() { + function membership($section) { + var authList, i, len, ref, + thismembership = this; + this.$section = $section; + this.$section.data('wrapper', this); + plantTimeout(0, function() { + return new BatchEnrollment(thismembership.$section.find('.batch-enrollment')); + }); + plantTimeout(0, function() { + return new AutoEnrollmentViaCsv(thismembership.$section.find('.auto_enroll_csv')); + }); + plantTimeout(0, function() { + return new BetaTesterBulkAddition(thismembership.$section.find('.batch-beta-testers')); + }); + this.$list_selector = this.$section.find('select#member-lists-selector'); + this.$auth_list_containers = this.$section.find('.auth-list-container'); + this.$auth_list_errors = this.$section.find('.member-lists-management .request-response-error'); + this.auth_lists = _.map(this.$auth_list_containers, function(authListContainer) { + var rolename; + rolename = $(authListContainer).data('rolename'); + return new AuthListWidget($(authListContainer), rolename, thismembership.$auth_list_errors); + }); + this.$list_selector.empty(); + ref = this.auth_lists; + for (i = 0, len = ref.length; i < len; i++) { + authList = ref[i]; + this.$list_selector.append($('