Commit 1e8cb7df by Miles Steele

clean & comment coffeescript, add util.coffee

parent e05d0f97
log = -> console.log.apply console, arguments
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
# Analytics 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
# Analytics Section
class Analytics
constructor: (@$section) ->
log "setting up instructor dashboard section - analytics"
# gather elements
@$display = @$section.find '.distribution-display'
@$display_text = @$display.find '.distribution-display-text'
@$display_graph = @$display.find '.distribution-display-graph'
......@@ -21,20 +19,23 @@ class Analytics
@populate_selector => @$distribution_select.change => @on_selector_change()
reset_display: ->
@$display_text.empty()
@$display_graph.empty()
@$display_table.empty()
@$request_response_error.empty()
# fetch and list available distributions
# `cb` is a callback to be run after
populate_selector: (cb) ->
@get_profile_distributions [],
# on error, print to console and dom.
error: std_ajax_err => @$request_response_error.text "Error getting available distributions."
success: (data) =>
# replace loading text in drop-down with "-- 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
opt = $ '<option/>',
text: data.display_names[feature]
......@@ -43,14 +44,13 @@ class Analytics
@$distribution_select.append opt
# call callback if one was supplied
cb?()
# display data
on_selector_change: ->
# log 'changeargs', arguments
opt = @$distribution_select.children('option:selected')
feature = opt.data 'feature'
log "distribution selected: #{feature}"
@reset_display()
return unless feature
......@@ -64,7 +64,7 @@ class Analytics
@$display_text.text 'Error fetching data'
else
if feature_res.type is 'EASY_CHOICE'
# setup SlickGrid
# display on SlickGrid
options =
enableCellNavigation: true
enableColumnReorder: false
......@@ -89,7 +89,6 @@ class Analytics
table_placeholder = $ '<div/>', class: 'slickgrid'
@$display_table.append table_placeholder
grid = new Slick.Grid(table_placeholder, grid_data, columns, options)
# grid.autosizeColumns()
else if feature is 'year_of_birth'
graph_placeholder = $ '<div/>', class: 'year-of-birth'
@$display_graph.append graph_placeholder
......@@ -104,7 +103,9 @@ class Analytics
@$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) ->
settings =
dataType: 'json'
......@@ -119,7 +120,9 @@ class Analytics
$.ajax settings
# exports
# 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: {}
......
log = -> console.log.apply console, arguments
plantTimeout = (ms, cb) -> setTimeout cb, ms
# Course Info Section
# 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
constructor: (@$section) ->
log "setting up instructor dashboard section - course info"
@$section.data 'wrapper', @
@$course_errors_wrapper = @$section.find '.course-errors-wrapper'
# if there are errors
if @$course_errors_wrapper.length
@$course_error_toggle = @$course_errors_wrapper.find('.toggle-wrapper').eq(0)
@$course_error_toggle_text = @$course_error_toggle.find('h2').eq(0)
@$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')
@$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})"
# toggle .open class on errors
# to show and hide them.
@$course_error_toggle.click (e) =>
e.preventDefault()
if @$course_errors_wrapper.hasClass 'open'
......@@ -24,7 +35,9 @@ class CourseInfo
@$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 _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
......
log = -> console.log.apply console, arguments
plantTimeout = (ms, cb) -> setTimeout cb, ms
# Data Download Section
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
# Data Download Section
class DataDownload
constructor: (@$section) ->
log "setting up instructor dashboard section - data download"
# gather elements
@$display = @$section.find '.data-display'
@$display_text = @$display.find '.data-display-text'
@$display_table = @$display.find '.data-display-table'
@$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']'")
$list_studs_btn.click (e) =>
log "fetching student list"
url = $list_studs_btn.data('endpoint')
# this handler binds to both the download
# and the csv button
@$list_studs_btn.click (e) =>
url = @$list_studs_btn.data 'endpoint'
# handle csv special case
if $(e.target).data 'csv'
# redirect the document to the csv file.
url += '/csv'
location.href = url
else
@clear_display()
@$display_table.text 'Loading...'
# fetch user list
$.ajax
dataType: 'json'
url: url
......@@ -36,7 +44,7 @@ class DataDownload
success: (data) =>
@clear_display()
# setup SlickGrid
# display on a SlickGrid
options =
enableCellNavigation: true
enableColumnReorder: false
......@@ -50,10 +58,9 @@ class DataDownload
grid = new Slick.Grid($table_placeholder, grid_data, columns, options)
# grid.autosizeColumns()
$grade_config_btn = @$section.find("input[name='dump-gradeconf']'")
$grade_config_btn.click (e) =>
log "fetching grading config"
url = $grade_config_btn.data('endpoint')
@$grade_config_btn.click (e) =>
url = @$grade_config_btn.data 'endpoint'
# display html from grading config endpoint
$.ajax
dataType: 'json'
url: url
......@@ -71,7 +78,9 @@ class DataDownload
@$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 _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
......
# Instructor Dashboard Tab Manager
log = -> console.log.apply console, arguments
plantTimeout = (ms, cb) -> setTimeout cb, ms
# # intercepts a jquery method
# # calls the original method after callback
# intercept_jquery_method = (method_name, callback) ->
# original = jQuery.fn[method_name]
# jQuery.fn[method_name] = ->
# callback.apply this, arguments
# original.apply this, arguments
# intercept_jquery_method 'on', (event_name) ->
# this.addClass "has-event-handler-for-#{event_name}"
# The instructor dashboard is broken into sections.
# Only one section is visible at a time,
# and is responsible for its own functionality.
#
# NOTE: plantTimeout (which is just setTimeout from util.coffee)
# is used frequently in the instructor dashboard to isolate
# failures. If one piece of code under a plantTimeout fails
# then it will not crash the rest of the dashboard.
#
# NOTE: The instructor dashboard currently does not
# use backbone. Just lots of jquery. This should be fixed.
#
# NOTE: Server endpoints in the dashboard are stored in
# the 'data-endpoint' attribute of relevant html elements.
# The urls are rendered there by a template.
#
# 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_ACTIVE_SECTION = 'active-section'
CSS_IDASH_SECTION = 'idash-section'
CSS_INSTRUCTOR_NAV = 'instructor-nav'
# prefix for deep-linking
HASH_LINK_PREFIX = '#view-'
# once we're ready, check if this page has the instructor dashboard
# once we're ready, check if this page is the instructor dashboard
$ =>
instructor_dashboard_content = $ ".#{CSS_INSTRUCTOR_CONTENT}"
if instructor_dashboard_content.length != 0
log "setting up instructor dashboard"
console.log 'checking if we are on the 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_sections instructor_dashboard_content
# enable links
# enable navigation bar
# handles hiding and showing sections
setup_instructor_dashboard = (idash_content) =>
# clickable section titles
links = idash_content.find(".#{CSS_INSTRUCTOR_NAV}").find('a')
# setup section header click handlers
for link in ($ link for link in links)
link.click (e) ->
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_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION
# find paired section
# discover section paired to link
section_name = $(this).data 'section'
section = idash_content.find "##{section_name}"
# activate (styling) active
section.addClass CSS_ACTIVE_SECTION
# activate link & section styling
$(this).addClass CSS_ACTIVE_SECTION
section.addClass CSS_ACTIVE_SECTION
# tracking
# analytics.pageview "instructor_#{section_name}"
# write deep link
# deep linking
# write to url
location.hash = "#{HASH_LINK_PREFIX}#{section_name}"
log "clicked section #{section_name}"
plantTimeout 0, -> section.data('wrapper')?.onClickTitle?()
# 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
rmatch = (new RegExp "^#{HASH_LINK_PREFIX}(.*)").exec location.hash
section_name = rmatch[1]
......@@ -78,11 +91,10 @@ setup_instructor_dashboard = (idash_content) =>
# call setup handlers for each section
# enable sections
setup_instructor_dashboard_sections = (idash_content) ->
log "setting up instructor dashboard sections"
# fault isolation
# an error thrown in one section will not block other sections from exectuing
# see fault isolation NOTE at top of file.
# 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.DataDownload idash_content.find ".#{CSS_IDASH_SECTION}#data_download"
plantTimeout 0, -> new window.InstructorDashboard.sections.Membership idash_content.find ".#{CSS_IDASH_SECTION}#membership"
......
log = -> console.log.apply console, arguments
plantTimeout = (ms, cb) -> setTimeout cb, ms
plantInterval = (ms, cb) -> setInterval cb, ms
# Student Admin Section
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
plantInterval = -> window.InstructorDashboard.util.plantInterval.apply this, arguments
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
# get jquery element and assert its existance
find_and_assert = ($root, selector) ->
......@@ -18,6 +16,9 @@ find_and_assert = ($root, selector) ->
else
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) ->
$table_tasks.empty()
......@@ -66,11 +67,11 @@ create_task_list_table = ($table_tasks, tasks_data) ->
class StudentAdmin
constructor: (@$section) ->
log "setting up instructor dashboard section - student admin"
@$section.data 'wrapper', @
# collect buttons
# gather buttons
# 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']"
@$progress_link = find_and_assert @$section, "a.progress-link"
@$btn_enroll = find_and_assert @$section, "input[name='enroll']"
......@@ -82,7 +83,7 @@ class StudentAdmin
@$btn_task_history_single = @$section.find "input[name='task-history-single']"
@$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']"
@$btn_reset_attempts_all = @$section.find "input[name='reset-attempts-all']"
@$btn_rescore_problem_all = @$section.find "input[name='rescore-problem-all']"
......@@ -90,12 +91,16 @@ class StudentAdmin
@$table_task_history_all = @$section.find ".task-history-all-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_all = @$section.find ".course-specific-container .request-response-error"
# start polling for task list
if @$table_running_tasks.length > 0
@start_refresh_running_task_poll_loop()
# attach click handlers
# go to student progress page
@$progress_link.click (e) =>
e.preventDefault()
......@@ -106,7 +111,6 @@ class StudentAdmin
url: @$progress_link.data 'endpoint'
data: student_email: email
success: @clear_errors_then (data) ->
log 'redirecting...'
window.location = data.progress_url
error: std_ajax_err => @$request_response_error_single.text "Error getting student progress url for '#{email}'."
......@@ -148,7 +152,7 @@ class StudentAdmin
dataType: 'json'
url: @$btn_reset_attempts_single.data 'endpoint'
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."
# delete state for student on problem
......@@ -162,7 +166,7 @@ class StudentAdmin
dataType: 'json'
url: @$btn_delete_state_single.data 'endpoint'
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."
# start task to rescore problem for student
......@@ -175,7 +179,7 @@ class StudentAdmin
dataType: 'json'
url: @$btn_rescore_problem_single.data 'endpoint'
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."
# list task history for student+problem
......@@ -207,7 +211,7 @@ class StudentAdmin
dataType: 'json'
url: @$btn_reset_attempts_all.data 'endpoint'
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."
# start task to rescore problem for all students
......@@ -220,7 +224,7 @@ class StudentAdmin
dataType: 'json'
url: @$btn_rescore_problem_all.data 'endpoint'
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."
# list task history for problem
......@@ -253,21 +257,27 @@ class StudentAdmin
if @$section.hasClass 'active-section'
plantTimeout 5000, => @start_refresh_running_task_poll_loop()
# wraps a function, but first clear the error displays
clear_errors_then: (cb) ->
@$request_response_error_single.empty()
@$request_response_error_all.empty()
->
cb?.apply this, arguments
# handler for when the section title is clicked.
onClickTitle: ->
if @$table_running_tasks.length > 0
@start_refresh_running_task_poll_loop()
# handler for when the section is closed
# not working yet.
# onExit: ->
# 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 _?
_.defaults window, InstructorDashboard: {}
_.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 @@
## links which are tied to idash-sections below.
## 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">
% for section_data in sections:
<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