Commit 1e8cb7df by Miles Steele

clean & comment coffeescript, add util.coffee

parent e05d0f97
log = -> console.log.apply console, arguments # Analytics Section
plantTimeout = (ms, cb) -> setTimeout cb, ms
std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) ->
console.warn """ajax error
textStatus: #{textStatus}
errorThrown: #{errorThrown}"""
handler.apply this, arguments
# 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
# Analytics Section
class Analytics class Analytics
constructor: (@$section) -> constructor: (@$section) ->
log "setting up instructor dashboard section - analytics" # gather elements
@$display = @$section.find '.distribution-display' @$display = @$section.find '.distribution-display'
@$display_text = @$display.find '.distribution-display-text' @$display_text = @$display.find '.distribution-display-text'
@$display_graph = @$display.find '.distribution-display-graph' @$display_graph = @$display.find '.distribution-display-graph'
...@@ -21,20 +19,23 @@ class Analytics ...@@ -21,20 +19,23 @@ class Analytics
@populate_selector => @$distribution_select.change => @on_selector_change() @populate_selector => @$distribution_select.change => @on_selector_change()
reset_display: -> reset_display: ->
@$display_text.empty() @$display_text.empty()
@$display_graph.empty() @$display_graph.empty()
@$display_table.empty() @$display_table.empty()
@$request_response_error.empty() @$request_response_error.empty()
# fetch and list available distributions
# `cb` is a callback to be run after
populate_selector: (cb) -> populate_selector: (cb) ->
@get_profile_distributions [], @get_profile_distributions [],
# on error, print to console and dom.
error: std_ajax_err => @$request_response_error.text "Error getting available distributions." error: std_ajax_err => @$request_response_error.text "Error getting available distributions."
success: (data) => success: (data) =>
# replace loading text in drop-down with "-- Select Distribution --"
@$distribution_select.find('option').eq(0).text "-- Select Distribution --" @$distribution_select.find('option').eq(0).text "-- Select Distribution --"
# add all fetched available features to drop-down
for feature in data.available_features for feature in data.available_features
opt = $ '<option/>', opt = $ '<option/>',
text: data.display_names[feature] text: data.display_names[feature]
...@@ -43,14 +44,13 @@ class Analytics ...@@ -43,14 +44,13 @@ class Analytics
@$distribution_select.append opt @$distribution_select.append opt
# call callback if one was supplied
cb?() cb?()
# display data
on_selector_change: -> on_selector_change: ->
# log 'changeargs', arguments
opt = @$distribution_select.children('option:selected') opt = @$distribution_select.children('option:selected')
feature = opt.data 'feature' feature = opt.data 'feature'
log "distribution selected: #{feature}"
@reset_display() @reset_display()
return unless feature return unless feature
...@@ -64,7 +64,7 @@ class Analytics ...@@ -64,7 +64,7 @@ class Analytics
@$display_text.text 'Error fetching data' @$display_text.text 'Error fetching data'
else else
if feature_res.type is 'EASY_CHOICE' if feature_res.type is 'EASY_CHOICE'
# setup SlickGrid # display on SlickGrid
options = options =
enableCellNavigation: true enableCellNavigation: true
enableColumnReorder: false enableColumnReorder: false
...@@ -89,7 +89,6 @@ class Analytics ...@@ -89,7 +89,6 @@ class Analytics
table_placeholder = $ '<div/>', class: 'slickgrid' table_placeholder = $ '<div/>', class: 'slickgrid'
@$display_table.append table_placeholder @$display_table.append table_placeholder
grid = new Slick.Grid(table_placeholder, grid_data, columns, options) grid = new Slick.Grid(table_placeholder, grid_data, columns, options)
# grid.autosizeColumns()
else if feature is 'year_of_birth' else if feature is 'year_of_birth'
graph_placeholder = $ '<div/>', class: 'year-of-birth' graph_placeholder = $ '<div/>', class: 'year-of-birth'
@$display_graph.append graph_placeholder @$display_graph.append graph_placeholder
...@@ -104,7 +103,9 @@ class Analytics ...@@ -104,7 +103,9 @@ class Analytics
@$display_text.text 'Unavailable Metric\n' + JSON.stringify(feature_res) @$display_text.text 'Unavailable Metric\n' + JSON.stringify(feature_res)
# handler can be either a callback for success or a mapping e.g. {success: ->, error: ->, complete: ->} # fetch distribution data from server.
# `handler` can be either a callback for success
# or a mapping e.g. {success: ->, error: ->, complete: ->}
get_profile_distributions: (featurelist, handler) -> get_profile_distributions: (featurelist, handler) ->
settings = settings =
dataType: 'json' dataType: 'json'
...@@ -119,7 +120,9 @@ class Analytics ...@@ -119,7 +120,9 @@ class Analytics
$.ajax settings $.ajax settings
# exports # export for use
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
if _? if _?
_.defaults window, InstructorDashboard: {} _.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {} _.defaults window.InstructorDashboard, sections: {}
......
log = -> console.log.apply console, arguments # Course Info Section
plantTimeout = (ms, cb) -> setTimeout cb, ms # This is the implementation of the simplest section
# of the instructor dashboard.
# 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
# A typical section object.
# constructed with $section, a jquery object
# which holds the section body container.
class CourseInfo class CourseInfo
constructor: (@$section) -> constructor: (@$section) ->
log "setting up instructor dashboard section - course info"
@$section.data 'wrapper', @
@$course_errors_wrapper = @$section.find '.course-errors-wrapper' @$course_errors_wrapper = @$section.find '.course-errors-wrapper'
# if there are errors
if @$course_errors_wrapper.length if @$course_errors_wrapper.length
@$course_error_toggle = @$course_errors_wrapper.find('.toggle-wrapper').eq(0) @$course_error_toggle = @$course_errors_wrapper.find '.toggle-wrapper'
@$course_error_toggle_text = @$course_error_toggle.find('h2').eq(0) @$course_error_toggle_text = @$course_error_toggle.find 'h2'
@$course_error_visibility_wrapper = @$course_errors_wrapper.find '.course-errors-visibility-wrapper' @$course_error_visibility_wrapper = @$course_errors_wrapper.find '.course-errors-visibility-wrapper'
@$course_errors = @$course_errors_wrapper.find('.course-error') @$course_errors = @$course_errors_wrapper.find '.course-error'
# append "(34)" to the course errors label
@$course_error_toggle_text.text @$course_error_toggle_text.text() + " (#{@$course_errors.length})" @$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) => @$course_error_toggle.click (e) =>
e.preventDefault() e.preventDefault()
if @$course_errors_wrapper.hasClass 'open' if @$course_errors_wrapper.hasClass 'open'
...@@ -24,7 +35,9 @@ class CourseInfo ...@@ -24,7 +35,9 @@ class CourseInfo
@$course_errors_wrapper.addClass 'open' @$course_errors_wrapper.addClass 'open'
# exports # export for use
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
if _? if _?
_.defaults window, InstructorDashboard: {} _.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {} _.defaults window.InstructorDashboard, sections: {}
......
log = -> console.log.apply console, arguments # Data Download Section
plantTimeout = (ms, cb) -> setTimeout cb, ms
std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) -> # imports from other modules.
console.warn """ajax error # wrap in (-> ... apply) to defer evaluation
textStatus: #{textStatus} # such that the value can be defined later than this assignment (file load order).
errorThrown: #{errorThrown}""" plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
handler.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
# Data Download Section
class DataDownload class DataDownload
constructor: (@$section) -> constructor: (@$section) ->
log "setting up instructor dashboard section - data download" # gather elements
@$display = @$section.find '.data-display' @$display = @$section.find '.data-display'
@$display_text = @$display.find '.data-display-text' @$display_text = @$display.find '.data-display-text'
@$display_table = @$display.find '.data-display-table' @$display_table = @$display.find '.data-display-table'
@$request_response_error = @$display.find '.request-response-error' @$request_response_error = @$display.find '.request-response-error'
@$list_studs_btn = @$section.find("input[name='list-profiles']'")
@$grade_config_btn = @$section.find("input[name='dump-gradeconf']'")
# attach click handlers
$list_studs_btn = @$section.find("input[name='list-profiles']'") # this handler binds to both the download
$list_studs_btn.click (e) => # and the csv button
log "fetching student list" @$list_studs_btn.click (e) =>
url = $list_studs_btn.data('endpoint') url = @$list_studs_btn.data 'endpoint'
# handle csv special case
if $(e.target).data 'csv' if $(e.target).data 'csv'
# redirect the document to the csv file.
url += '/csv' url += '/csv'
location.href = url location.href = url
else else
@clear_display() @clear_display()
@$display_table.text 'Loading...' @$display_table.text 'Loading...'
# fetch user list
$.ajax $.ajax
dataType: 'json' dataType: 'json'
url: url url: url
...@@ -36,7 +44,7 @@ class DataDownload ...@@ -36,7 +44,7 @@ class DataDownload
success: (data) => success: (data) =>
@clear_display() @clear_display()
# setup SlickGrid # display on a SlickGrid
options = options =
enableCellNavigation: true enableCellNavigation: true
enableColumnReorder: false enableColumnReorder: false
...@@ -50,10 +58,9 @@ class DataDownload ...@@ -50,10 +58,9 @@ class DataDownload
grid = new Slick.Grid($table_placeholder, grid_data, columns, options) grid = new Slick.Grid($table_placeholder, grid_data, columns, options)
# grid.autosizeColumns() # grid.autosizeColumns()
$grade_config_btn = @$section.find("input[name='dump-gradeconf']'") @$grade_config_btn.click (e) =>
$grade_config_btn.click (e) => url = @$grade_config_btn.data 'endpoint'
log "fetching grading config" # display html from grading config endpoint
url = $grade_config_btn.data('endpoint')
$.ajax $.ajax
dataType: 'json' dataType: 'json'
url: url url: url
...@@ -71,7 +78,9 @@ class DataDownload ...@@ -71,7 +78,9 @@ class DataDownload
@$request_response_error.empty() @$request_response_error.empty()
# exports # export for use
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
if _? if _?
_.defaults window, InstructorDashboard: {} _.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {} _.defaults window.InstructorDashboard, sections: {}
......
# Instructor Dashboard Tab Manager # Instructor Dashboard Tab Manager
# The instructor dashboard is broken into sections.
log = -> console.log.apply console, arguments # Only one section is visible at a time,
plantTimeout = (ms, cb) -> setTimeout cb, ms # and is responsible for its own functionality.
#
# NOTE: plantTimeout (which is just setTimeout from util.coffee)
# # intercepts a jquery method # is used frequently in the instructor dashboard to isolate
# # calls the original method after callback # failures. If one piece of code under a plantTimeout fails
# intercept_jquery_method = (method_name, callback) -> # then it will not crash the rest of the dashboard.
# original = jQuery.fn[method_name] #
# jQuery.fn[method_name] = -> # NOTE: The instructor dashboard currently does not
# callback.apply this, arguments # use backbone. Just lots of jquery. This should be fixed.
# original.apply this, arguments #
# NOTE: Server endpoints in the dashboard are stored in
# the 'data-endpoint' attribute of relevant html elements.
# intercept_jquery_method 'on', (event_name) -> # The urls are rendered there by a template.
# this.addClass "has-event-handler-for-#{event_name}" #
# NOTE: For an example of what a section object should look like
# see course_info.coffee
# 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_INSTRUCTOR_CONTENT = 'instructor-dashboard-content-2'
CSS_ACTIVE_SECTION = 'active-section' CSS_ACTIVE_SECTION = 'active-section'
CSS_IDASH_SECTION = 'idash-section' CSS_IDASH_SECTION = 'idash-section'
CSS_INSTRUCTOR_NAV = 'instructor-nav' CSS_INSTRUCTOR_NAV = 'instructor-nav'
# prefix for deep-linking
HASH_LINK_PREFIX = '#view-' HASH_LINK_PREFIX = '#view-'
# once we're ready, check if this page is the instructor dashboard
# once we're ready, check if this page has the instructor dashboard
$ => $ =>
instructor_dashboard_content = $ ".#{CSS_INSTRUCTOR_CONTENT}" instructor_dashboard_content = $ ".#{CSS_INSTRUCTOR_CONTENT}"
if instructor_dashboard_content.length != 0 console.log 'checking if we are on the instructor dashboard'
log "setting up instructor dashboard" if instructor_dashboard_content.length > 0
console.log 'we are on the instructor dashboard'
setup_instructor_dashboard instructor_dashboard_content setup_instructor_dashboard instructor_dashboard_content
setup_instructor_dashboard_sections instructor_dashboard_content setup_instructor_dashboard_sections instructor_dashboard_content
# enable links # enable navigation bar
# handles hiding and showing sections
setup_instructor_dashboard = (idash_content) => setup_instructor_dashboard = (idash_content) =>
# clickable section titles
links = idash_content.find(".#{CSS_INSTRUCTOR_NAV}").find('a') links = idash_content.find(".#{CSS_INSTRUCTOR_NAV}").find('a')
# setup section header click handlers
for link in ($ link for link in links) for link in ($ link for link in links)
link.click (e) -> link.click (e) ->
e.preventDefault() e.preventDefault()
# deactivate (styling) all sections
idash_content.find(".#{CSS_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION # deactivate all link & section styles
idash_content.find(".#{CSS_INSTRUCTOR_NAV}").children().removeClass CSS_ACTIVE_SECTION idash_content.find(".#{CSS_INSTRUCTOR_NAV}").children().removeClass CSS_ACTIVE_SECTION
idash_content.find(".#{CSS_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION
# find paired section # discover section paired to link
section_name = $(this).data 'section' section_name = $(this).data 'section'
section = idash_content.find "##{section_name}" section = idash_content.find "##{section_name}"
# activate (styling) active # activate link & section styling
section.addClass CSS_ACTIVE_SECTION
$(this).addClass CSS_ACTIVE_SECTION $(this).addClass CSS_ACTIVE_SECTION
section.addClass CSS_ACTIVE_SECTION
# tracking # tracking
# analytics.pageview "instructor_#{section_name}" # analytics.pageview "instructor_#{section_name}"
# write deep link # deep linking
# write to url
location.hash = "#{HASH_LINK_PREFIX}#{section_name}" location.hash = "#{HASH_LINK_PREFIX}#{section_name}"
log "clicked section #{section_name}"
plantTimeout 0, -> section.data('wrapper')?.onClickTitle?() plantTimeout 0, -> section.data('wrapper')?.onClickTitle?()
# plantTimeout 0, -> section.data('wrapper')?.onExit?() # plantTimeout 0, -> section.data('wrapper')?.onExit?()
# recover deep link from url
# click default or go to section specified by hash # activate an initial section by programmatically clicking on it.
# check for a deep-link, or click the first link.
if (new RegExp "^#{HASH_LINK_PREFIX}").test location.hash if (new RegExp "^#{HASH_LINK_PREFIX}").test location.hash
rmatch = (new RegExp "^#{HASH_LINK_PREFIX}(.*)").exec location.hash rmatch = (new RegExp "^#{HASH_LINK_PREFIX}(.*)").exec location.hash
section_name = rmatch[1] section_name = rmatch[1]
...@@ -78,11 +91,10 @@ setup_instructor_dashboard = (idash_content) => ...@@ -78,11 +91,10 @@ setup_instructor_dashboard = (idash_content) =>
# call setup handlers for each section # enable sections
setup_instructor_dashboard_sections = (idash_content) -> setup_instructor_dashboard_sections = (idash_content) ->
log "setting up instructor dashboard sections" # see fault isolation NOTE at top of file.
# fault isolation # an error thrown in one section will not block other sections from exectuing.
# an error thrown in one section will not block other sections from exectuing
plantTimeout 0, -> new window.InstructorDashboard.sections.CourseInfo idash_content.find ".#{CSS_IDASH_SECTION}#course_info" plantTimeout 0, -> new window.InstructorDashboard.sections.CourseInfo idash_content.find ".#{CSS_IDASH_SECTION}#course_info"
plantTimeout 0, -> new window.InstructorDashboard.sections.DataDownload idash_content.find ".#{CSS_IDASH_SECTION}#data_download" plantTimeout 0, -> new window.InstructorDashboard.sections.DataDownload idash_content.find ".#{CSS_IDASH_SECTION}#data_download"
plantTimeout 0, -> new window.InstructorDashboard.sections.Membership idash_content.find ".#{CSS_IDASH_SECTION}#membership" plantTimeout 0, -> new window.InstructorDashboard.sections.Membership idash_content.find ".#{CSS_IDASH_SECTION}#membership"
......
log = -> console.log.apply console, arguments # Membership Section
plantTimeout = (ms, cb) -> setTimeout cb, ms
std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) -> # imports from other modules.
console.warn """ajax error # wrap in (-> ... apply) to defer evaluation
textStatus: #{textStatus} # such that the value can be defined later than this assignment (file load order).
errorThrown: #{errorThrown}""" plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
handler.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
# Wrapper for the batch enrollment subsection.
# This object handles buttons, success and failure reporting,
# and server communication.
class BatchEnrollment class BatchEnrollment
constructor: (@$container) -> constructor: (@$container) ->
log "setting up instructor dashboard subsection - batch enrollment" # gather elements
@$emails_input = @$container.find("textarea[name='student-emails']'")
@$btn_enroll = @$container.find("input[name='enroll']'")
@$btn_unenroll = @$container.find("input[name='unenroll']'")
@$checkbox_autoenroll = @$container.find("input[name='auto-enroll']'")
@$task_response = @$container.find(".request-response")
@$request_response_error = @$container.find(".request-response-error")
$emails_input = @$container.find("textarea[name='student-emails']'") # attach click handlers
$btn_enroll = @$container.find("input[name='enroll']'")
$btn_unenroll = @$container.find("input[name='unenroll']'")
$checkbox_autoenroll = @$container.find("input[name='auto-enroll']'")
$task_response = @$container.find(".request-response")
$request_response_error = @$container.find(".request-response-error")
$emails_input.click -> log 'click $emails_input' @$btn_enroll.click =>
$btn_enroll.click -> log 'click $btn_enroll'
$btn_unenroll.click -> log 'click $btn_unenroll'
$btn_enroll.click ->
send_data = send_data =
action: 'enroll' action: 'enroll'
emails: $emails_input.val() emails: @$emails_input.val()
auto_enroll: $checkbox_autoenroll.is(':checked') auto_enroll: @$checkbox_autoenroll.is(':checked')
$.ajax $.ajax
dataType: 'json' dataType: 'json'
url: $btn_enroll.data 'endpoint' url: @$btn_enroll.data 'endpoint'
data: send_data data: send_data
success: (data) -> display_response(data) success: (data) => @display_response data
error: std_ajax_err -> fail_with_error "Error enrolling/unenrolling students." error: std_ajax_err => @fail_with_error "Error enrolling/unenrolling students."
$btn_unenroll.click -> @$btn_unenroll.click =>
send_data = send_data =
action: 'unenroll' action: 'unenroll'
emails: $emails_input.val() emails: @$emails_input.val()
auto_enroll: $checkbox_autoenroll.is(':checked') auto_enroll: @$checkbox_autoenroll.is(':checked')
$.ajax $.ajax
dataType: 'json' dataType: 'json'
url: $btn_unenroll.data 'endpoint' url: @$btn_unenroll.data 'endpoint'
data: send_data data: send_data
success: (data) -> display_response(data) success: (data) => @display_response data
error: std_ajax_err -> fail_with_error "Error enrolling/unenrolling students." error: std_ajax_err => @fail_with_error "Error enrolling/unenrolling students."
fail_with_error = (msg) -> fail_with_error: (msg) ->
console.warn msg console.warn msg
$task_response.empty() @$task_response.empty()
$request_response_error.empty() @$request_response_error.empty()
$request_response_error.text msg @$request_response_error.text msg
display_response = (data_from_server) -> display_response: (data_from_server) ->
$task_response.empty() @$task_response.empty()
$request_response_error.empty() @$request_response_error.empty()
# these results arrays contain student_results # these results arrays contain student_results
# only populated arrays will be rendered # only populated arrays will be rendered
#
# students for which there was an error during the action
errors = []
# students who are now enrolled in the course
enrolled = []
# students who are now allowed to enroll in the course
allowed = []
# students who will be autoenrolled on registration
autoenrolled = []
# students who are now not enrolled in the course
notenrolled = []
# categorize student results into the above arrays.
for student_results in data_from_server.results
# for a successful action.
# student_results is of the form {
# "email": "jd405@edx.org",
# "before": {
# "enrollment": true,
# "auto_enroll": false,
# "user": true,
# "allowed": false
# }
# "after": {
# "enrollment": true,
# "auto_enroll": false,
# "user": true,
# "allowed": false
# },
# }
# #
# students for which there was an error during the action # for an action error.
errors = [] # student_results is of the form {
# students who are now enrolled in the course # 'email': email,
enrolled = [] # 'error': True,
# students who are now allowed to enroll in the course # }
allowed = []
# students who will be autoenrolled on registration if student_results.error
autoenrolled = [] errors.push student_results
# students who are now not enrolled in the course else if student_results.after.enrollment
notenrolled = [] enrolled.push student_results
else if student_results.after.allowed
# categorize student results into the above arrays. if student_results.after.auto_enroll
for student_results in data_from_server.results autoenrolled.push student_results
# for a successful action.
# student_results is of the form {
# "email": "jd405@edx.org",
# "before": {
# "enrollment": true,
# "auto_enroll": false,
# "user": true,
# "allowed": false
# }
# "after": {
# "enrollment": true,
# "auto_enroll": false,
# "user": true,
# "allowed": false
# },
# }
#
# for an action error.
# student_results is of the form {
# 'email': email,
# 'error': True,
# }
if student_results.error != undefined
errors.push student_results
else if student_results.after.enrollment
enrolled.push student_results
else if student_results.after.allowed
if student_results.after.auto_enroll
autoenrolled.push student_results
else
allowed.push student_results
else if not student_results.after.enrollment
notenrolled.push student_results
else else
console.warn 'student results not reported to user' allowed.push student_results
console.warn student_results else if not student_results.after.enrollment
notenrolled.push student_results
# render populated result arrays else
render_list = (label, emails) -> console.warn 'student results not reported to user'
log emails console.warn student_results
task_res_section = $ '<div/>', class: 'request-res-section'
task_res_section.append $ '<h3/>', text: label # render populated result arrays
email_list = $ '<ul/>' render_list = (label, emails) =>
task_res_section.append email_list task_res_section = $ '<div/>', class: 'request-res-section'
task_res_section.append $ '<h3/>', text: label
for email in emails email_list = $ '<ul/>'
email_list.append $ '<li/>', text: email task_res_section.append email_list
$task_response.append task_res_section for email in emails
email_list.append $ '<li/>', text: email
if errors.length
errors_label = do -> @$task_response.append task_res_section
if data_from_server.action is 'enroll'
"There was an error enrolling:" if errors.length
else if data_from_server.action is 'unenroll' errors_label = do ->
"There was an error unenrolling:" if data_from_server.action is 'enroll'
else "There was an error enrolling:"
console.warn "unknown action from server '#{data_from_server.action}'" else if data_from_server.action is 'unenroll'
"There was an error processing:" "There was an error unenrolling:"
else
console.warn "unknown action from server '#{data_from_server.action}'"
"There was an error processing:"
for student_results in errors for student_results in errors
console.log 'error with': student_results.email render_list errors_label, (sr.email for sr in errors)
if enrolled.length if enrolled.length
render_list "Students Enrolled:", (sr.email for sr in enrolled) render_list "Students Enrolled:", (sr.email for sr in enrolled)
if allowed.length if allowed.length
render_list "These students will be allowed to enroll once they register:", render_list "These students will be allowed to enroll once they register:",
(sr.email for sr in allowed) (sr.email for sr in allowed)
if autoenrolled.length if autoenrolled.length
render_list "These students will be enrolled once they register:", render_list "These students will be enrolled once they register:",
(sr.email for sr in autoenrolled) (sr.email for sr in autoenrolled)
if notenrolled.length if notenrolled.length
render_list "These students are now not enrolled:", render_list "These students are now not enrolled:",
(sr.email for sr in notenrolled) (sr.email for sr in notenrolled)
# manages a list of instructors or staff and the control of their access. # Wrapper for auth list subsection.
# manages a list of users who have special access.
# these could be instructors, staff, beta users, or forum roles.
# uses slickgrid to display list.
class AuthList class AuthList
# rolename is in ['instructor', 'staff'] for instructor_staff endpoints # rolename is one of ['instructor', 'staff'] for instructor_staff endpoints
# rolename is the name of Role for forums for the forum endpoints # rolename is the name of Role for forums for the forum endpoints
constructor: (@$container, @rolename) -> constructor: (@$container, @rolename) ->
log "setting up instructor dashboard subsection - authlist management for #{@rolename}" # gather elements
@$display_table = @$container.find('.auth-list-table') @$display_table = @$container.find('.auth-list-table')
@$request_response_error = @$container.find('.request-response-error') @$request_response_error = @$container.find('.request-response-error')
@$add_section = @$container.find('.auth-list-add') @$add_section = @$container.find('.auth-list-add')
@$allow_field = @$add_section.find("input[name='email']") @$allow_field = @$add_section.find("input[name='email']")
@$allow_button = @$add_section.find("input[name='allow']") @$allow_button = @$add_section.find("input[name='allow']")
# attach click handler
@$allow_button.click => @$allow_button.click =>
@access_change @$allow_field.val(), @rolename, 'allow', => @reload_auth_list() @access_change @$allow_field.val(), 'allow', => @reload_auth_list()
@$allow_field.val '' @$allow_field.val ''
@reload_auth_list() @reload_auth_list()
# fetch and display list of users who match criteria
reload_auth_list: -> reload_auth_list: ->
# helper function to display server data in the list
load_auth_list = (data) => load_auth_list = (data) =>
# clear existing data
@$request_response_error.empty() @$request_response_error.empty()
@$display_table.empty() @$display_table.empty()
# setup slickgrid
options = options =
enableCellNavigation: true enableCellNavigation: true
enableColumnReorder: false enableColumnReorder: false
# autoHeight: true # autoHeight: true
forceFitColumns: true forceFitColumns: true
# this is a hack to put a button/link in a slick grid cell
# if you change columns, then you must update
# WHICH_CELL_IS_REVOKE to have the index
# of the revoke column (left to right).
WHICH_CELL_IS_REVOKE = 3 WHICH_CELL_IS_REVOKE = 3
columns = [ columns = [
id: 'username' id: 'username'
...@@ -216,13 +225,15 @@ class AuthList ...@@ -216,13 +225,15 @@ class AuthList
$table_placeholder = $ '<div/>', class: 'slickgrid' $table_placeholder = $ '<div/>', class: 'slickgrid'
@$display_table.append $table_placeholder @$display_table.append $table_placeholder
grid = new Slick.Grid($table_placeholder, table_data, columns, options) grid = new Slick.Grid($table_placeholder, table_data, columns, options)
# grid.autosizeColumns()
# click handler part of the revoke button/link hack.
grid.onClick.subscribe (e, args) => grid.onClick.subscribe (e, args) =>
item = args.grid.getDataItem(args.row) item = args.grid.getDataItem(args.row)
if args.cell is WHICH_CELL_IS_REVOKE if args.cell is WHICH_CELL_IS_REVOKE
@access_change item.email, @rolename, 'revoke', => @reload_auth_list() @access_change item.email, 'revoke', => @reload_auth_list()
# fetch data from the endpoint
# the endpoint comes from data-endpoint of the table
$.ajax $.ajax
dataType: 'json' dataType: 'json'
url: @$display_table.data 'endpoint' url: @$display_table.data 'endpoint'
...@@ -231,33 +242,45 @@ class AuthList ...@@ -231,33 +242,45 @@ class AuthList
error: std_ajax_err => @$request_response_error.text "Error fetching list for '#{@rolename}'" error: std_ajax_err => @$request_response_error.text "Error fetching list for '#{@rolename}'"
# slickgrid collapses when rendered in an invisible div # slickgrid's layout collapses when rendered
# use this method to reload the widget # in an invisible div. use this method to reload
# the AuthList widget
refresh: -> refresh: ->
@$display_table.empty() @$display_table.empty()
@reload_auth_list() @reload_auth_list()
access_change: (email, rolename, mode, cb) -> # update the access of a user.
# (add or remove them from the list)
# mode should be one of ['allow', 'revoke']
access_change: (email, mode, cb) ->
$.ajax $.ajax
dataType: 'json' dataType: 'json'
url: @$add_section.data 'endpoint' url: @$add_section.data 'endpoint'
data: data:
email: email email: email
rolename: rolename rolename: @rolename
mode: mode mode: mode
success: (data) -> cb?(data) success: (data) -> cb?(data)
error: std_ajax_err => @$request_response_error.text "Error changing user's permissions." error: std_ajax_err => @$request_response_error.text "Error changing user's permissions."
# Membership Section
class Membership class Membership
# enable subsections.
constructor: (@$section) -> constructor: (@$section) ->
log "setting up instructor dashboard section - membership" # attach self to html
# so that instructor_dashboard.coffee can find this object
# to call event handlers like 'onClickTitle'
@$section.data 'wrapper', @ @$section.data 'wrapper', @
@$list_selector = @$section.find('select#member-lists-selector') # isolate # initialize BatchEnrollment subsection
plantTimeout 0, => new BatchEnrollment @$section.find '.batch-enrollment'
plantTimeout 0, => @batchenrollment = new BatchEnrollment @$section.find '.batch-enrollment' # gather elements
@$list_selector = @$section.find('select#member-lists-selector')
# initialize & store AuthList subsections
# one for each .auth-list-container in the section.
@auth_lists = _.map (@$section.find '.auth-list-container'), (auth_list_container) -> @auth_lists = _.map (@$section.find '.auth-list-container'), (auth_list_container) ->
rolename = $(auth_list_container).data 'rolename' rolename = $(auth_list_container).data 'rolename'
new AuthList $(auth_list_container), rolename new AuthList $(auth_list_container), rolename
...@@ -279,15 +302,19 @@ class Membership ...@@ -279,15 +302,19 @@ class Membership
auth_list.refresh() auth_list.refresh()
auth_list.$container.addClass 'active' auth_list.$container.addClass 'active'
# one-time first selection of top list.
@$list_selector.change() @$list_selector.change()
# handler for when the section title is clicked.
onClickTitle: -> onClickTitle: ->
for auth_list in @auth_lists for auth_list in @auth_lists
auth_list.refresh() auth_list.refresh()
# exports # export for use
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
if _? if _?
_.defaults window, InstructorDashboard: {} _.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {} _.defaults window.InstructorDashboard, sections: {}
......
log = -> console.log.apply console, arguments # Student Admin Section
plantTimeout = (ms, cb) -> setTimeout cb, ms
plantInterval = (ms, cb) -> setInterval cb, ms
# imports from other modules.
std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) -> # wrap in (-> ... apply) to defer evaluation
console.warn """ajax error # such that the value can be defined later than this assignment (file load order).
textStatus: #{textStatus} plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
errorThrown: #{errorThrown}""" plantInterval = -> window.InstructorDashboard.util.plantInterval.apply this, arguments
handler.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
# get jquery element and assert its existance # get jquery element and assert its existance
find_and_assert = ($root, selector) -> find_and_assert = ($root, selector) ->
...@@ -18,6 +16,9 @@ find_and_assert = ($root, selector) -> ...@@ -18,6 +16,9 @@ find_and_assert = ($root, selector) ->
else else
item item
# render a task list table to the DOM
# `$table_tasks` the $element in which to put the table
# `tasks_data`
create_task_list_table = ($table_tasks, tasks_data) -> create_task_list_table = ($table_tasks, tasks_data) ->
$table_tasks.empty() $table_tasks.empty()
...@@ -66,11 +67,11 @@ create_task_list_table = ($table_tasks, tasks_data) -> ...@@ -66,11 +67,11 @@ create_task_list_table = ($table_tasks, tasks_data) ->
class StudentAdmin class StudentAdmin
constructor: (@$section) -> constructor: (@$section) ->
log "setting up instructor dashboard section - student admin"
@$section.data 'wrapper', @ @$section.data 'wrapper', @
# collect buttons # gather buttons
# some buttons are optional because they can be flipped by the instructor task feature switch # some buttons are optional because they can be flipped by the instructor task feature switch
# student-specific
@$field_student_select = find_and_assert @$section, "input[name='student-select']" @$field_student_select = find_and_assert @$section, "input[name='student-select']"
@$progress_link = find_and_assert @$section, "a.progress-link" @$progress_link = find_and_assert @$section, "a.progress-link"
@$btn_enroll = find_and_assert @$section, "input[name='enroll']" @$btn_enroll = find_and_assert @$section, "input[name='enroll']"
...@@ -82,7 +83,7 @@ class StudentAdmin ...@@ -82,7 +83,7 @@ class StudentAdmin
@$btn_task_history_single = @$section.find "input[name='task-history-single']" @$btn_task_history_single = @$section.find "input[name='task-history-single']"
@$table_task_history_single = @$section.find ".task-history-single-table" @$table_task_history_single = @$section.find ".task-history-single-table"
# course-specific level buttons # course-specific
@$field_problem_select_all = @$section.find "input[name='problem-select-all']" @$field_problem_select_all = @$section.find "input[name='problem-select-all']"
@$btn_reset_attempts_all = @$section.find "input[name='reset-attempts-all']" @$btn_reset_attempts_all = @$section.find "input[name='reset-attempts-all']"
@$btn_rescore_problem_all = @$section.find "input[name='rescore-problem-all']" @$btn_rescore_problem_all = @$section.find "input[name='rescore-problem-all']"
...@@ -90,12 +91,16 @@ class StudentAdmin ...@@ -90,12 +91,16 @@ class StudentAdmin
@$table_task_history_all = @$section.find ".task-history-all-table" @$table_task_history_all = @$section.find ".task-history-all-table"
@$table_running_tasks = @$section.find ".running-tasks-table" @$table_running_tasks = @$section.find ".running-tasks-table"
# response areas
@$request_response_error_single = find_and_assert @$section, ".student-specific-container .request-response-error" @$request_response_error_single = find_and_assert @$section, ".student-specific-container .request-response-error"
@$request_response_error_all = @$section.find ".course-specific-container .request-response-error" @$request_response_error_all = @$section.find ".course-specific-container .request-response-error"
# start polling for task list
if @$table_running_tasks.length > 0 if @$table_running_tasks.length > 0
@start_refresh_running_task_poll_loop() @start_refresh_running_task_poll_loop()
# attach click handlers
# go to student progress page # go to student progress page
@$progress_link.click (e) => @$progress_link.click (e) =>
e.preventDefault() e.preventDefault()
...@@ -106,7 +111,6 @@ class StudentAdmin ...@@ -106,7 +111,6 @@ class StudentAdmin
url: @$progress_link.data 'endpoint' url: @$progress_link.data 'endpoint'
data: student_email: email data: student_email: email
success: @clear_errors_then (data) -> success: @clear_errors_then (data) ->
log 'redirecting...'
window.location = data.progress_url window.location = data.progress_url
error: std_ajax_err => @$request_response_error_single.text "Error getting student progress url for '#{email}'." error: std_ajax_err => @$request_response_error_single.text "Error getting student progress url for '#{email}'."
...@@ -148,7 +152,7 @@ class StudentAdmin ...@@ -148,7 +152,7 @@ class StudentAdmin
dataType: 'json' dataType: 'json'
url: @$btn_reset_attempts_single.data 'endpoint' url: @$btn_reset_attempts_single.data 'endpoint'
data: send_data data: send_data
success: @clear_errors_then -> log 'problem attempts reset' success: @clear_errors_then -> console.log 'problem attempts reset'
error: std_ajax_err => @$request_response_error_single.text "Error resetting problem attempts." error: std_ajax_err => @$request_response_error_single.text "Error resetting problem attempts."
# delete state for student on problem # delete state for student on problem
...@@ -162,7 +166,7 @@ class StudentAdmin ...@@ -162,7 +166,7 @@ class StudentAdmin
dataType: 'json' dataType: 'json'
url: @$btn_delete_state_single.data 'endpoint' url: @$btn_delete_state_single.data 'endpoint'
data: send_data data: send_data
success: @clear_errors_then -> log 'module state deleted' success: @clear_errors_then -> console.log 'module state deleted'
error: std_ajax_err => @$request_response_error_single.text "Error deleting problem state." error: std_ajax_err => @$request_response_error_single.text "Error deleting problem state."
# start task to rescore problem for student # start task to rescore problem for student
...@@ -175,7 +179,7 @@ class StudentAdmin ...@@ -175,7 +179,7 @@ class StudentAdmin
dataType: 'json' dataType: 'json'
url: @$btn_rescore_problem_single.data 'endpoint' url: @$btn_rescore_problem_single.data 'endpoint'
data: send_data data: send_data
success: @clear_errors_then -> log 'started rescore problem task' success: @clear_errors_then -> console.log 'started rescore problem task'
error: std_ajax_err => @$request_response_error_single.text "Error starting a task to rescore student's problem." error: std_ajax_err => @$request_response_error_single.text "Error starting a task to rescore student's problem."
# list task history for student+problem # list task history for student+problem
...@@ -207,7 +211,7 @@ class StudentAdmin ...@@ -207,7 +211,7 @@ class StudentAdmin
dataType: 'json' dataType: 'json'
url: @$btn_reset_attempts_all.data 'endpoint' url: @$btn_reset_attempts_all.data 'endpoint'
data: send_data data: send_data
success: @clear_errors_then -> log 'started reset attempts task' success: @clear_errors_then -> console.log 'started reset attempts task'
error: std_ajax_err => @$request_response_error_all.text "Error starting a task to reset attempts for all students on this problem." error: std_ajax_err => @$request_response_error_all.text "Error starting a task to reset attempts for all students on this problem."
# start task to rescore problem for all students # start task to rescore problem for all students
...@@ -220,7 +224,7 @@ class StudentAdmin ...@@ -220,7 +224,7 @@ class StudentAdmin
dataType: 'json' dataType: 'json'
url: @$btn_rescore_problem_all.data 'endpoint' url: @$btn_rescore_problem_all.data 'endpoint'
data: send_data data: send_data
success: @clear_errors_then -> log 'started rescore problem task' success: @clear_errors_then -> console.log 'started rescore problem task'
error: std_ajax_err => @$request_response_error_all.text "Error starting a task to rescore this problem for all students." error: std_ajax_err => @$request_response_error_all.text "Error starting a task to rescore this problem for all students."
# list task history for problem # list task history for problem
...@@ -253,21 +257,27 @@ class StudentAdmin ...@@ -253,21 +257,27 @@ class StudentAdmin
if @$section.hasClass 'active-section' if @$section.hasClass 'active-section'
plantTimeout 5000, => @start_refresh_running_task_poll_loop() plantTimeout 5000, => @start_refresh_running_task_poll_loop()
# wraps a function, but first clear the error displays
clear_errors_then: (cb) -> clear_errors_then: (cb) ->
@$request_response_error_single.empty() @$request_response_error_single.empty()
@$request_response_error_all.empty() @$request_response_error_all.empty()
-> ->
cb?.apply this, arguments cb?.apply this, arguments
# handler for when the section title is clicked.
onClickTitle: -> onClickTitle: ->
if @$table_running_tasks.length > 0 if @$table_running_tasks.length > 0
@start_refresh_running_task_poll_loop() @start_refresh_running_task_poll_loop()
# handler for when the section is closed
# not working yet.
# onExit: -> # onExit: ->
# clearInterval @reload_running_task_list_slot # clearInterval @reload_running_task_list_slot
# exports # export for use
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
if _? if _?
_.defaults window, InstructorDashboard: {} _.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {} _.defaults window.InstructorDashboard, sections: {}
......
# Common utilities for instructor dashboard components.
# reverse arguments on common functions to enable
# better coffeescript with callbacks at the end.
plantTimeout = (ms, cb) -> setTimeout cb, ms
plantInterval = (ms, cb) -> setInterval cb, ms
# standard ajax error wrapper
#
# wraps a `handler` function so that first
# it prints basic error information to the console.
std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) ->
console.warn """ajax error
textStatus: #{textStatus}
errorThrown: #{errorThrown}"""
handler.apply this, arguments
# export for use
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
if _?
_.defaults window, InstructorDashboard: {}
window.InstructorDashboard.util =
plantTimeout: plantTimeout
plantInterval: plantInterval
std_ajax_err: std_ajax_err
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
## links which are tied to idash-sections below. ## links which are tied to idash-sections below.
## the links are acativated and handled in instructor_dashboard.coffee ## the links are acativated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on idash-default-section ## when the javascript loads, it clicks on the first section
<h2 class="instructor-nav"> <h2 class="instructor-nav">
% for section_data in sections: % for section_data in sections:
<a href="" data-section="${ section_data['section_key'] }">${ section_data['section_display_name'] }</a> <a href="" data-section="${ section_data['section_key'] }">${ section_data['section_display_name'] }</a>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment