create gradebook2
uses coffeescript, slickgrid, actual canvas APIs reachable at /courses/x/gradebook2 does not include commenting or things external to the grid like filtering and sorting options Change-Id: I6967c2dbdd16f7ea4d8c1ad1995511d7c498226a Reviewed-on: https://gerrit.instructure.com/4371 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
|
@ -0,0 +1,109 @@
|
|||
class GradeCalculator
|
||||
# each submission needs fields: score, points_possible, assignment_id, assignment_group_id
|
||||
# to represent assignments that the student hasn't submitted, pass a
|
||||
# submission with score == null
|
||||
#
|
||||
# each group needs fields: id, rules, group_weight
|
||||
# rules is { drop_lowest: n, drop_highest: n, never_drop: [id...] }
|
||||
#
|
||||
# if weighting_scheme is "percent", group weights are used, otherwise no weighting is applied
|
||||
@calculate: (submissions, groups, weighting_scheme) ->
|
||||
result = {}
|
||||
# NOTE: purposely using $.map because it can handle array or object, old gradebook sends array
|
||||
# new gradebook sends object, needs jquery >1.6's version of $.map, since it can handle both
|
||||
result.group_sums = $.map groups, (group) =>
|
||||
group: group
|
||||
current: @create_group_sum(group, submissions, true)
|
||||
'final': @create_group_sum(group, submissions, false)
|
||||
result.current = @calculate_total(result.group_sums, true, weighting_scheme)
|
||||
result['final'] = @calculate_total(result.group_sums, false, weighting_scheme)
|
||||
result
|
||||
|
||||
@create_group_sum: (group, submissions, ignore_ungraded) ->
|
||||
sum = { submissions: [], score: 0, possible: 0, submission_count: 0 }
|
||||
for submission in submissions
|
||||
do (submission) =>
|
||||
# handle if the submisison comes to us without an assignment_group_id
|
||||
unless submission.assignment_group_id
|
||||
assignment = $.detect(group.assignments, () -> submission.assignment_id == this.id)
|
||||
if assignment
|
||||
submission.assignment_group_id = group.id
|
||||
# if submission doesn't have points possible, try to use assignment's
|
||||
submission.points_possible ?= assignment?.points_possible
|
||||
if submission.assignment_group_id == group.id
|
||||
data = { submission: submission, score: 0, possible: 0, percent: 0, drop: false, submitted: false }
|
||||
sum.submissions.push data
|
||||
unless ignore_ungraded and (!submission.score || submission.score == '')
|
||||
data.score = @parse submission.score
|
||||
data.possible = @parse submission.points_possible
|
||||
data.percent = @parse(data.score / data.possible)
|
||||
data.submitted = (submission.score and submission.score != '')
|
||||
sum.submission_count += 1 if data.submitted
|
||||
|
||||
# sort the submissions by assigned score
|
||||
sum.submissions.sort (a,b) -> a.percent - b.percent
|
||||
rules = $.extend({ drop_lowest: 0, drop_highest: 0, never_drop: [] }, group.rules)
|
||||
|
||||
dropped = 0
|
||||
|
||||
# drop the lowest and highest assignments
|
||||
for lowOrHigh in ['low', 'high']
|
||||
for data in sum.submissions
|
||||
if !data.drop and rules["drop_#{lowOrHigh}est"] > 0 and $.inArray(data.assignment_id, rules.never_drop) == -1 and data.possible > 0 and data.submitted
|
||||
data.drop = true
|
||||
# TODO: do I want to do this, it actually modifies the passed in submission object but it
|
||||
# it seems like the best way to tell it it should be dropped.
|
||||
data.submission?.drop = true
|
||||
rules["drop_#{lowOrHigh}est"] -= 1
|
||||
dropped += 1
|
||||
|
||||
# if everything was dropped, un-drop the highest single submission
|
||||
if dropped > 0 and dropped == sum.submission_count
|
||||
sum.submissions[sum.submissions.length - 1].drop = false
|
||||
# see TODO above
|
||||
sum.submissions[sum.submissions.length - 1].submission?.drop = true
|
||||
dropped -= 1
|
||||
|
||||
sum.submission_count -= dropped
|
||||
|
||||
sum.score += s.score for s in sum.submissions when !s.drop
|
||||
sum.possible += s.possible for s in sum.submissions when !s.drop
|
||||
sum
|
||||
|
||||
@calculate_total: (group_sums, ignore_ungraded, weighting_scheme) ->
|
||||
data_idx = if ignore_ungraded then 'current' else 'final'
|
||||
if weighting_scheme == 'percent'
|
||||
score = 0.0
|
||||
possible_weight_from_submissions = 0.0
|
||||
total_possible_weight = 0.0
|
||||
for data in group_sums when data.group.group_weight > 0
|
||||
if data[data_idx].submission_count > 0
|
||||
tally = data[data_idx].score / data[data_idx].possible
|
||||
score += data.group.group_weight * tally
|
||||
possible_weight_from_submissions += data.group.group_weight
|
||||
total_possible_weight += data.group.group_weight
|
||||
|
||||
if ignore_ungraded and possible_weight_from_submissions < 1.0
|
||||
possible = if total_possible_weight < 1.0 then total_possible_weight else 1.0
|
||||
score = score * possible / possible_weight_from_submissions
|
||||
{
|
||||
score: score
|
||||
possible: 1.0
|
||||
}
|
||||
else
|
||||
{
|
||||
score: @sum(data[data_idx].score for data in group_sums)
|
||||
possible: @sum(data[data_idx].possible for data in group_sums)
|
||||
}
|
||||
|
||||
@sum: (values) ->
|
||||
result = 0
|
||||
result += value for value in values
|
||||
result
|
||||
|
||||
@parse: (score) ->
|
||||
result = parseFloat score
|
||||
if result && isFinite(result) then result else 0
|
||||
|
||||
|
||||
window.INST.GradeCalculator = GradeCalculator
|
|
@ -0,0 +1,322 @@
|
|||
# This class both creates the slickgrid instance, and acts as the data source
|
||||
# for that instance.
|
||||
I18n.scoped 'gradebook2', (I18n) ->
|
||||
this.Gradebook = class Gradebook
|
||||
constructor: (@options) ->
|
||||
@chunk_start = 0
|
||||
@students = {}
|
||||
@rows = []
|
||||
@filterFn = (student) -> true
|
||||
@sortFn = (student) -> student.display_name
|
||||
@init()
|
||||
@includeUngradedAssignments = false
|
||||
|
||||
init: () ->
|
||||
if @options.assignment_groups
|
||||
return @gotAssignmentGroups(@options.assignment_groups)
|
||||
$.ajaxJSON( @options.assignment_groups_url, "GET", {}, @gotAssignmentGroups )
|
||||
|
||||
gotAssignmentGroups: (assignment_groups) =>
|
||||
@assignment_groups = {}
|
||||
@assignments = {}
|
||||
for group in assignment_groups
|
||||
$.htmlEscapeValues(group)
|
||||
@assignment_groups[group.id] = group
|
||||
for assignment in group.assignments
|
||||
$.htmlEscapeValues(assignment)
|
||||
assignment.due_at = $.parseFromISO(assignment.due_at) if assignment.due_at
|
||||
@assignments[assignment.id] = assignment
|
||||
if @options.sections
|
||||
return @gotStudents(@options.sections)
|
||||
$.ajaxJSON( @options.sections_and_students_url, "GET", {}, @gotStudents )
|
||||
|
||||
gotStudents: (sections) =>
|
||||
@sections = {}
|
||||
@rows = []
|
||||
for section in sections
|
||||
$.htmlEscapeValues(section)
|
||||
@sections[section.id] = section
|
||||
for student in section.students
|
||||
$.htmlEscapeValues(student)
|
||||
student.computed_current_score ||= 0
|
||||
student.computed_final_score ||= 0
|
||||
@students[student.id] = student
|
||||
student.section = section
|
||||
# fill in dummy submissions, so there's something there even if the
|
||||
# student didn't submit anything for that assignment
|
||||
for id, assignment of @assignments
|
||||
student["assignment_#{id}"] ||= { assignment_id: id, user_id: student.id }
|
||||
@rows.push(student)
|
||||
|
||||
@sections_enabled = sections.length > 1
|
||||
|
||||
for id, student of @students
|
||||
student.display_name = "<div class='student-name'>#{student.name}</div>"
|
||||
student.display_name += "<div class='student-section'>#{student.section.name}</div>" if @sections_enabled
|
||||
|
||||
@initGrid()
|
||||
@buildRows()
|
||||
@getSubmissionsChunk()
|
||||
|
||||
# filter, sort, and build the dataset for slickgrid to read from, then force
|
||||
# a full redraw
|
||||
buildRows: () ->
|
||||
@rows.length = 0
|
||||
sortables = {}
|
||||
|
||||
for id, student of @students
|
||||
student.row = -1
|
||||
if @filterFn(student)
|
||||
@rows.push(student)
|
||||
sortables[student.id] = @sortFn(student)
|
||||
|
||||
@rows.sort (a, b) ->
|
||||
if sortables[a.id] < sortables[b.id] then -1
|
||||
else if sortables[a.id] > sortables[b.id] then 1
|
||||
else 0
|
||||
|
||||
student.row = i for student, i in @rows
|
||||
@multiGrid.removeAllRows()
|
||||
@multiGrid.updateRowCount()
|
||||
@multiGrid.render()
|
||||
|
||||
sortBy: (sort) ->
|
||||
@sortFn = switch sort
|
||||
when "display_name" then (student) -> student.display_name
|
||||
when "section" then (student) -> student.section.name
|
||||
when "grade_desc" then (student) -> -student.computed_current_score
|
||||
when "grade_asc" then (student) -> student.computed_current_score
|
||||
this.buildRows()
|
||||
|
||||
getSubmissionsChunk: (student_id) ->
|
||||
if @options.submissions
|
||||
return this.gotSubmissionsChunk(@options.submissions)
|
||||
students = @rows[@chunk_start...(@chunk_start+@options.chunk_size)]
|
||||
params = {
|
||||
student_ids: (student.id for student in students)
|
||||
assignment_ids: (id for id, assignment of @assignments)
|
||||
response_fields: ['user_id', 'url', 'score', 'grade', 'submission_type', 'submitted_at', 'assignment_id', 'grade_matches_current_submission']
|
||||
}
|
||||
if students.length > 0
|
||||
$.ajaxJSON(@options.submissions_url, "GET", params, @gotSubmissionsChunk)
|
||||
|
||||
gotSubmissionsChunk: (student_submissions) =>
|
||||
for data in student_submissions
|
||||
student = @students[data.user_id]
|
||||
student.submissionsAsArray = []
|
||||
for submission in data.submissions
|
||||
submission.submitted_at = $.parseFromISO(submission.submitted_at) if submission.submitted_at
|
||||
student["assignment_#{submission.assignment_id}"] = submission
|
||||
student.submissionsAsArray.push(submission)
|
||||
student.loaded = true
|
||||
@multiGrid.removeRow(student.row)
|
||||
@calculateStudentGrade(student)
|
||||
@multiGrid.render()
|
||||
@chunk_start += @options.chunk_size
|
||||
@getSubmissionsChunk()
|
||||
|
||||
cellFormatter: (row, col, submission) =>
|
||||
if !@rows[row].loaded
|
||||
@staticCellFormatter(row, col, '')
|
||||
else if !submission?.grade
|
||||
@staticCellFormatter(row, col, '-')
|
||||
else
|
||||
assignment = @assignments[submission.assignment_id]
|
||||
if !assignment?
|
||||
@staticCellFormatter(row, col, '')
|
||||
else
|
||||
if assignment.grading_type == 'points' && assignment.points_possible
|
||||
SubmissionCell.out_of.formatter(row, col, submission, assignment)
|
||||
else
|
||||
(SubmissionCell[assignment.grading_type] || SubmissionCell).formatter(row, col, submission, assignment)
|
||||
|
||||
staticCellFormatter: (row, col, val) =>
|
||||
"<div class='cell-content gradebook-cell'>#{val}</div>"
|
||||
|
||||
groupTotalFormatter: (row, col, val, columnDef, student) =>
|
||||
return '' unless val?
|
||||
gradeToShow = val[if @includeUngradedAssignments then 'final' else 'current']
|
||||
percentage = if columnDef.field == 'total_grade' then gradeToShow.score else (gradeToShow.score/gradeToShow.possible)*100
|
||||
percentage = Math.round(percentage)
|
||||
percentage = 0 if isNaN(percentage)
|
||||
if !gradeToShow.possible then percentage = '-' else percentage+= "%"
|
||||
res = """
|
||||
<div class="gradebook-cell">
|
||||
#{if columnDef.field == 'total_grade' then '' else '<div class="gradebook-tooltip">'+ gradeToShow.score + ' / ' + gradeToShow.possible + '</div>'}
|
||||
#{percentage}
|
||||
</div>
|
||||
"""
|
||||
res
|
||||
|
||||
calculateStudentGrade: (student) =>
|
||||
if student.loaded
|
||||
result = INST.GradeCalculator.calculate(student.submissionsAsArray, @assignment_groups, 'percent')
|
||||
for group in result.group_sums
|
||||
student["assignment_group_#{group.group.id}"] = {current: group.current, 'final': group['final']}
|
||||
student["total_grade"] = {current: result.current, 'final': result['final']}
|
||||
|
||||
highlightColumn: (columnIndexOrEvent) =>
|
||||
if isNaN(columnIndexOrEvent)
|
||||
# then assume that columnIndexOrEvent is an event, so figure out which column
|
||||
# it is based on its class name
|
||||
match = columnIndexOrEvent.currentTarget.className.match(/c\d+/)
|
||||
if match
|
||||
columnIndexOrEvent = match.toString().replace('c', '')
|
||||
@$grid.find('.slick-header-column:eq(' + columnIndexOrEvent + ')').addClass('hovered-column')
|
||||
|
||||
unhighlightColumns: () =>
|
||||
@$grid.find('.hovered-column').removeClass('hovered-column')
|
||||
|
||||
showCommentDialog: =>
|
||||
$('<div>TODO: show comments and stuff</div>').dialog()
|
||||
return false
|
||||
|
||||
onGridInit: () ->
|
||||
tooltipTexts = {}
|
||||
@$grid = $('#gradebook_grid')
|
||||
.fillWindowWithMe({
|
||||
alsoResize: '#gradebook_students_grid',
|
||||
onResize: () =>
|
||||
@multiGrid.resizeCanvas()
|
||||
})
|
||||
.delegate('.slick-cell', 'mouseenter.gradebook focusin.gradebook', @highlightColumn)
|
||||
.delegate('.slick-cell', 'mouseleave.gradebook focusout.gradebook', @unhighlightColumns)
|
||||
.delegate('.gradebook-cell', 'hover.gradebook', -> $(this).toggleClass('hover'))
|
||||
.delegate('.gradebook-cell-comment', 'click.gradebook', @showCommentDialog )
|
||||
|
||||
# # debugging stuff, remove
|
||||
# events =
|
||||
# onSort: null,
|
||||
# onHeaderContextMenu: null,
|
||||
# onHeaderClick: null,
|
||||
# onClick: null,
|
||||
# onDblClick: null,
|
||||
# onContextMenu: null,
|
||||
# onKeyDown: null,
|
||||
# onAddNewRow: null,
|
||||
# onValidationError: null,
|
||||
# onViewportChanged: null,
|
||||
# onSelectedRowsChanged: null,
|
||||
# onColumnsReordered: null,
|
||||
# onColumnsResized: null,
|
||||
# onBeforeMoveRows: null,
|
||||
# onMoveRows: null,
|
||||
# # onCellChange: "Raised when cell has been edited. Args: row,cell,dataContext.",
|
||||
# onBeforeEditCell : "Raised before a cell goes into edit mode. Return false to cancel. Args: row,cell,dataContext."
|
||||
# onBeforeCellEditorDestroy: "Raised before a cell editor is destroyed. Args: current cell editor."
|
||||
# onBeforeDestroy: "Raised just before the grid control is destroyed (part of the destroy() method)."
|
||||
# onCurrentCellChanged: "Raised when the selected (active) cell changed. Args: {row:currentRow, cell:currentCell}."
|
||||
# onCellRangeSelected: "Raised when a user selects a range of cells. Args: {from:{row,cell}, to:{row,cell}}."
|
||||
# $.each events, (event, documentation) =>
|
||||
# old = @multiGrid.grids[1][event]
|
||||
# @multiGrid.grids[1][event] = () ->
|
||||
# $.isFunction(old) && old.apply(this, arguments)
|
||||
# console.log(event, documentation, arguments)
|
||||
$('#grid-options').click (event) ->
|
||||
event.preventDefault()
|
||||
$('#sort_rows_dialog').dialog('close').dialog(width: 400, height: 300)
|
||||
# set up row sorting options
|
||||
$('#sort_rows_dialog .by_section').hide() unless @sections_enabled
|
||||
$('#sort_rows_dialog button.sort_rows').click ->
|
||||
gradebook.sortBy($(this).data('sort_by'))
|
||||
$('#sort_rows_dialog').dialog('close')
|
||||
|
||||
initGrid: () ->
|
||||
#this is used to figure out how wide to make each column
|
||||
$widthTester = $('<span style="padding:10px" />').appendTo('#content')
|
||||
testWidth = (text, minWidth) ->
|
||||
Math.max($widthTester.text(text).outerWidth(), minWidth)
|
||||
|
||||
@columns = [{
|
||||
id: 'student',
|
||||
name: "<a href='javascript:void(0)' id='grid-options'>Options</a>",
|
||||
field: 'display_name',
|
||||
width: 150,
|
||||
cssClass: "meta-cell"
|
||||
},
|
||||
{
|
||||
id: 'secondary_identifier',
|
||||
name: 'secondary ID',
|
||||
field: 'secondary_identifier'
|
||||
width: 100,
|
||||
cssClass: "meta-cell secondary_identifier_cell"
|
||||
}]
|
||||
|
||||
for id, assignment of @assignments when assignment.submission_types isnt "not_graded"
|
||||
html = "<div class='assignment-name'>#{assignment.name}</div>"
|
||||
html += "<div class='assignment-points-possible'>#{I18n.t 'points_out_of', "out of %{points_possible}", points_possible: assignment.points_possible}</div>" if assignment.points_possible?
|
||||
outOfFormatter = assignment &&
|
||||
assignment.grading_type == 'points' &&
|
||||
assignment.points_possible? &&
|
||||
SubmissionCell.out_of
|
||||
minWidth = if outOfFormatter then 70 else 50
|
||||
|
||||
@columns.push
|
||||
id: "assignment_#{id}"
|
||||
field: "assignment_#{id}"
|
||||
name: html
|
||||
object: assignment
|
||||
formatter: this.cellFormatter
|
||||
editor: outOfFormatter ||
|
||||
SubmissionCell[assignment.grading_type] ||
|
||||
SubmissionCell
|
||||
minWidth: minWidth,
|
||||
maxWidth:200,
|
||||
width: testWidth(assignment.name, minWidth)
|
||||
|
||||
for id, group of @assignment_groups
|
||||
html = "#{group.name}"
|
||||
html += "<div class='assignment-points-possible'>#{I18n.t 'percent_of_grade', "%{percentage} of grade", percentage: I18n.toPercentage(group.group_weight, precision: 0)}</div>" if group.group_weight?
|
||||
|
||||
@columns.push
|
||||
id: "assignment_group_#{id}"
|
||||
field: "assignment_group_#{id}"
|
||||
formatter: @groupTotalFormatter
|
||||
name: html
|
||||
object: group
|
||||
minWidth: 35,
|
||||
maxWidth:200,
|
||||
width: testWidth(group.name, 35)
|
||||
cssClass: "meta-cell assignment-group-cell"
|
||||
|
||||
@columns.push
|
||||
id: "total_grade"
|
||||
field: "total_grade"
|
||||
formatter: @groupTotalFormatter
|
||||
name: "Total"
|
||||
minWidth: 50,
|
||||
maxWidth: 100,
|
||||
width: testWidth("Total", 50)
|
||||
cssClass: "total-cell"
|
||||
|
||||
$widthTester.remove()
|
||||
|
||||
options = $.extend({
|
||||
enableCellNavigation: false
|
||||
enableColumnReorder: false
|
||||
enableAsyncPostRender: true
|
||||
asyncPostRenderDelay: 1
|
||||
autoEdit: true # whether to go into edit-mode as soon as you tab to a cell
|
||||
rowHeight: 35
|
||||
}, @options)
|
||||
|
||||
grids = [{
|
||||
selector: '#gradebook_students_grid'
|
||||
columns: @columns[0..1]
|
||||
}, {
|
||||
selector: '#gradebook_grid'
|
||||
columns: @columns[2...@columns.length]
|
||||
options:
|
||||
enableCellNavigation: true
|
||||
editable: true
|
||||
syncColumnCellResize: true
|
||||
}]
|
||||
|
||||
@multiGrid = new MultiGrid(@rows, options, grids, 1)
|
||||
# this is the magic that actually updates group and final grades when you edit a cell
|
||||
@multiGrid.grids[1].onCellChange = (row, col, student) =>
|
||||
@calculateStudentGrade(student)
|
||||
@multiGrid.parent_grid.onKeyDown = () =>
|
||||
# TODO: start editing automatically when a number or letter is typed
|
||||
false
|
||||
@onGridInit?()
|
|
@ -0,0 +1,29 @@
|
|||
# this class coordinates multiple slick grids to behave as if they are
|
||||
# different views on the same data -- if one scrolls vertically, the others
|
||||
# scroll vertically as well.
|
||||
this.MultiGrid = class MultiGrid
|
||||
constructor: (data, default_options, grids, parent_grid) ->
|
||||
@data = data
|
||||
@parent_grid_idx = parent_grid
|
||||
@grids = for grid_opts in grids
|
||||
options = $.extend({}, default_options, grid_opts.options)
|
||||
grid = new Slick.Grid(grid_opts.selector, @data, grid_opts.columns, options)
|
||||
grid.multiview_grid_opts = grid_opts
|
||||
grid_opts.$viewport = $(grid_opts.selector).find('.slick-viewport')
|
||||
if grid_opts == grids[@parent_grid_idx]
|
||||
@parent_grid = grid
|
||||
else
|
||||
grid_opts.$viewport.css('overflow-y', 'hidden')
|
||||
grid
|
||||
@parent_grid.onViewportChanged = () =>
|
||||
for grid in @grids
|
||||
if grid != @parent_grid
|
||||
grid.multiview_grid_opts.$viewport[0].scrollTop =
|
||||
@parent_grid.multiview_grid_opts.$viewport[0].scrollTop
|
||||
grid.multiview_grid_opts.$viewport.trigger('scroll.slickgrid')
|
||||
|
||||
# simple delegation
|
||||
for method in ['render', 'removeRow', 'removeAllRows', 'updateRowCount', 'autosizeColumns', 'resizeCanvas']
|
||||
do (method) ->
|
||||
MultiGrid::[method] = () ->
|
||||
grid[method].apply(grid, arguments) for grid in @grids
|
|
@ -0,0 +1,173 @@
|
|||
this.SubmissionCell = class SubmissionCell
|
||||
|
||||
tooltipTexts = {}
|
||||
|
||||
constructor: (@opts) ->
|
||||
@init()
|
||||
|
||||
init: () ->
|
||||
submission = @opts.item[@opts.column.field]
|
||||
@$wrapper = $(@cellWrapper('<input class="grade"/>')).appendTo(@opts.container)
|
||||
@$input = @$wrapper.find('input').focus().select()
|
||||
|
||||
destroy: () ->
|
||||
@$input.remove()
|
||||
|
||||
focus: () ->
|
||||
@$input.focus()
|
||||
|
||||
loadValue: () ->
|
||||
@val = @opts.item[@opts.column.field].grade || ""
|
||||
@$input.val(@val)
|
||||
@$input[0].defaultValue = @val
|
||||
@$input.select()
|
||||
|
||||
serializeValue: () ->
|
||||
@$input.val()
|
||||
|
||||
|
||||
applyValue: (item, state) ->
|
||||
item[@opts.column.field].grade = state
|
||||
@wrapper?.remove()
|
||||
@postValue(item, state)
|
||||
# TODO: move selection down to the next row, same column
|
||||
|
||||
postValue: (item, state) ->
|
||||
submission = item[@opts.column.field]
|
||||
url = @opts.grid.getOptions().change_grade_url
|
||||
url = url.replace(":assignment", submission.assignment_id).replace(":submission", submission.user_id)
|
||||
$.ajaxJSON url, "PUT", { "submission[posted_grade]": state }
|
||||
|
||||
isValueChanged: () ->
|
||||
@val != @$input.val()
|
||||
|
||||
validate: () ->
|
||||
{ valid: true, msg: null }
|
||||
|
||||
@formatter: (row, col, submission, assignment) ->
|
||||
this.prototype.cellWrapper(submission.grade, {submission: submission, assignment: assignment, editable: false})
|
||||
# classes = []
|
||||
# "<div class='cell-content gradebook-cell #{classes.join(' ')}'>#{submission.grade}</div>"
|
||||
|
||||
@imageForCell: (image_id) ->
|
||||
$(image_id)[0].outerHTML
|
||||
|
||||
cellWrapper: (innerContents, options = {}) ->
|
||||
opts = $.extend({}, {
|
||||
innerContents: '',
|
||||
classes: '',
|
||||
editable: true
|
||||
}, options)
|
||||
opts.submission ||= @opts.item[@opts.column.field]
|
||||
opts.assignment ||= @opts.column.object
|
||||
specialClasses = SubmissionCell.classesBasedOnSubmission(opts.submission, opts.assignment)
|
||||
tooltipText = $.map(specialClasses, (c)->
|
||||
tooltipTexts[c] ?= $("#submission_tooltip_#{c}").text()
|
||||
).join(', ')
|
||||
|
||||
"""
|
||||
<div class="gradebook-cell #{ if opts.editable then 'gradebook-cell-editable focus' else ''} #{opts.classes} #{specialClasses.join(' ')}">
|
||||
#{ if tooltipText then '<div class="gradebook-tooltip">'+ tooltipText + '</div>' else ''}
|
||||
<a href="#" class="gradebook-cell-comment"><span class="gradebook-cell-comment-label">submission comments</span></a>
|
||||
#{innerContents}
|
||||
</div>
|
||||
"""
|
||||
|
||||
@classesBasedOnSubmission: (submission={}, assignment={}) ->
|
||||
classes = []
|
||||
classes.push('resubmitted') if submission.grade_matches_current_submission == false
|
||||
classes.push('late') if assignment.due_at && submission.submitted_at && (submission.submitted_at.timestamp > assignment.due_at.timestamp)
|
||||
classes.push('dropped') if submission.drop
|
||||
classes
|
||||
|
||||
class SubmissionCell.out_of extends SubmissionCell
|
||||
init: () ->
|
||||
submission = @opts.item[@opts.column.field]
|
||||
@$wrapper = $(@cellWrapper("""
|
||||
<div class="overflow-wrapper">
|
||||
<div class="grade-and-outof-wrapper">
|
||||
<input type="number" class="grade"/><span class="outof"><span class="divider">/</span>#{@opts.column.object.points_possible}</span>
|
||||
</div>
|
||||
</div>
|
||||
""", { classes: 'gradebook-cell-out-of-formatter' })).appendTo(@opts.container)
|
||||
@$input = @$wrapper.find('input').focus().select()
|
||||
|
||||
class SubmissionCell.pass_fail extends SubmissionCell
|
||||
|
||||
states = ['pass', 'fail', '']
|
||||
classFromSubmission = (submission) ->
|
||||
{ pass: 'pass', complete: 'pass', fail: 'fail', incomplete: 'fail' }[submission.grade] || ''
|
||||
|
||||
htmlFromSubmission: (options={}) ->
|
||||
cssClass = classFromSubmission(options.submission)
|
||||
SubmissionCell::cellWrapper("""
|
||||
<a data-value="#{cssClass}" class="gradebook-checkbox gradebook-checkbox-#{cssClass} #{'editable' if options.editable}" href="#">#{cssClass}</a>
|
||||
""", options)
|
||||
|
||||
# htmlFromSubmission = (submission, editable = false) ->
|
||||
# cssClass = classFromSubmission(submission)
|
||||
# """
|
||||
# <div class="gradebook-cell #{SubmissionCell.classesBasedOnSubmission(submission).join(' ')}">
|
||||
# <a href="#" class="gradebook-cell-comment"><span class="gradebook-cell-comment-label">submission comments</span></a>
|
||||
# <a data-value="#{cssClass}" class="gradebook-checkbox gradebook-checkbox-#{cssClass} #{'editable' if editable}" href="#">#{cssClass}</a>
|
||||
# </div>
|
||||
# """
|
||||
@formatter: (row, col, submission, assignment) ->
|
||||
pass_fail::htmlFromSubmission({ submission, assignment, editable: false})
|
||||
|
||||
|
||||
init: () ->
|
||||
@$wrapper = $(@cellWrapper())
|
||||
@$wrapper = $(@htmlFromSubmission({
|
||||
submission: @opts.item[@opts.column.field],
|
||||
assignment: @opts.column.object,
|
||||
editable: true})
|
||||
).appendTo(@opts.container)
|
||||
@$input = @$wrapper.find('.gradebook-checkbox')
|
||||
.bind('click keypress', (event) =>
|
||||
event.preventDefault()
|
||||
currentValue = @$input.data('value')
|
||||
if currentValue is 'pass'
|
||||
newValue = 'fail'
|
||||
else if currentValue is 'fail'
|
||||
newValue = ''
|
||||
else
|
||||
newValue = 'pass'
|
||||
@transitionValue(newValue)
|
||||
).focus()
|
||||
|
||||
destroy: () ->
|
||||
@$wrapper.remove()
|
||||
|
||||
focus: () ->
|
||||
@$input.focus()
|
||||
|
||||
transitionValue: (newValue) ->
|
||||
@$input
|
||||
.removeClass('gradebook-checkbox-pass gradebook-checkbox-fail')
|
||||
.addClass('gradebook-checkbox-' + classFromSubmission(grade: newValue))
|
||||
.data('value', newValue)
|
||||
|
||||
loadValue: () ->
|
||||
@val = @opts.item[@opts.column.field].grade || ""
|
||||
|
||||
|
||||
serializeValue: () ->
|
||||
@$input.data('value')
|
||||
|
||||
applyValue: (item, state) ->
|
||||
item[@opts.column.field].grade = state
|
||||
this.postValue(item, state)
|
||||
# TODO: move selection down to the next row, same column
|
||||
|
||||
postValue: (item, state) ->
|
||||
submission = item[@opts.column.field]
|
||||
url = @opts.grid.getOptions().change_grade_url
|
||||
url = url.replace(":assignment", submission.assignment_id).replace(":submission", submission.user_id)
|
||||
$.ajaxJSON url, "PUT", { "submission[posted_grade]": state }
|
||||
@$input.effect('highlight') #TODO: also highlight the group total and the TotalScore
|
||||
|
||||
isValueChanged: () ->
|
||||
@val != @$input.data('value')
|
||||
|
||||
class SubmissionCell.points extends SubmissionCell
|
|
@ -43,19 +43,25 @@ class AssignmentGroupsController < ApplicationController
|
|||
# "position": 7,
|
||||
# "name": "group2",
|
||||
# "id": 1,
|
||||
# "assignments": [...]
|
||||
# "group_weight": 20,
|
||||
# "assignments": [...],
|
||||
# "rules" : {...}
|
||||
# },
|
||||
# {
|
||||
# "position": 10,
|
||||
# "name": "group1",
|
||||
# "id": 2,
|
||||
# "assignments": [...]
|
||||
# "group_weight": 20,
|
||||
# "assignments": [...],
|
||||
# "rules" : {...}
|
||||
# },
|
||||
# {
|
||||
# "position": 12,
|
||||
# "name": "group3",
|
||||
# "id": 3,
|
||||
# "assignments": [...]
|
||||
# "group_weight": 60,
|
||||
# "assignments": [...],
|
||||
# "rules" : {...}
|
||||
# }
|
||||
# ]
|
||||
def index
|
||||
|
@ -73,7 +79,9 @@ class AssignmentGroupsController < ApplicationController
|
|||
format.json {
|
||||
hashes = @groups.map do |group|
|
||||
hash = group.as_json(:include_root => false,
|
||||
:only => %w(id name position))
|
||||
:only => %w(id name position group_weight))
|
||||
# note that 'rules_hash' gets to_jsoned as just 'rules' because that is what GradeCalculator expects.
|
||||
hash['rules'] = group.rules_hash
|
||||
if include_assignments
|
||||
hash['assignments'] = group.assignments.active.map { |a| assignment_json(a, [], @context.user_is_teacher?(@current_user)) }
|
||||
end
|
||||
|
|
|
@ -49,6 +49,7 @@ class AssignmentsApiController < ApplicationController
|
|||
# "name": "some assignment",
|
||||
# "points_possible": 12,
|
||||
# "grading_type": "points",
|
||||
# "due_at": "2011-05-26T23:59:00-06:00",
|
||||
# "submission_types" : [
|
||||
# "online_upload",
|
||||
# "online_text_entry",
|
||||
|
|
|
@ -212,6 +212,17 @@ class CoursesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def api_student_enrollments(context)
|
||||
enrollments = context.student_enrollments
|
||||
enrollments.map do |e|
|
||||
hash = e.user.as_json(:include_root => false, :only => STUDENT_API_FIELDS)
|
||||
hash.merge!(
|
||||
:secondary_identifier => e.user.secondary_identifier,
|
||||
:computed_final_score => e.computed_final_score,
|
||||
:computed_current_score => e.computed_current_score)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@context = Course.find(params[:id])
|
||||
if authorized_action(@context, @current_user, :delete)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
class Gradebook2Controller < ApplicationController
|
||||
before_filter :require_user_for_context
|
||||
add_crumb("Gradebook") { |c| c.send :named_context_url, c.instance_variable_get("@context"), :context_grades_url }
|
||||
before_filter { |c| c.active_tab = "grades" }
|
||||
|
||||
def show
|
||||
if authorized_action(@context, @current_user, :manage_grades)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -177,7 +177,7 @@ class GradebooksController < ApplicationController
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gradebook_init_json
|
||||
# res = "{"
|
||||
if params[:assignments]
|
||||
|
|
|
@ -29,6 +29,8 @@ class CourseSection < ActiveRecord::Base
|
|||
belongs_to :enrollment_term
|
||||
belongs_to :account
|
||||
has_many :enrollments, :include => :user, :conditions => ['enrollments.workflow_state != ?', 'deleted'], :dependent => :destroy
|
||||
has_many :student_enrollments, :class_name => 'StudentEnrollment', :conditions => ['enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?', 'deleted', 'completed', 'rejected', 'inactive'], :include => :user
|
||||
has_many :users, :through => :enrollments
|
||||
has_many :course_account_associations
|
||||
|
||||
adheres_to_policy
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import environment.sass
|
||||
|
||||
=icon_link
|
||||
:background-repeat no-repeat
|
||||
:background-position left center
|
||||
|
@ -237,20 +239,18 @@ a.no-underline,a.no-underline:hover, a.no-underline:focus
|
|||
// atr is short for accessible text replacement
|
||||
// these classes make it so you can have links like <a class="atr-reply">reply to this topic</a> and have only
|
||||
// a reply.png icon show up.
|
||||
=accessible_text_replacement
|
||||
display: inline-block
|
||||
text-indent: 999px
|
||||
overflow: hidden
|
||||
background-repeat: no-repeat
|
||||
|
||||
=atr_link
|
||||
+accessible_text_replacement
|
||||
width: 16px
|
||||
height: 16px
|
||||
|
||||
.atr-reply
|
||||
+accessible_text_replacement
|
||||
+atr_link
|
||||
background-image: url(/images/reply.png)
|
||||
.atr-edit
|
||||
+accessible_text_replacement
|
||||
+atr_link
|
||||
background-image: url(/images/edit.png)
|
||||
.atr-delete
|
||||
+accessible_text_replacement
|
||||
+atr_link
|
||||
background-image: url(/images/delete.png)
|
||||
|
|
|
@ -0,0 +1,281 @@
|
|||
@import environment.sass
|
||||
|
||||
$cell_height: 33px
|
||||
|
||||
// overrides of the default slick.grid.css styles
|
||||
#gradebook_grid
|
||||
.slick-cell
|
||||
border: 0
|
||||
padding: 0
|
||||
overflow: visible
|
||||
&.editable
|
||||
background: transparent
|
||||
border: none
|
||||
|
||||
.gradebook2 #content
|
||||
position: relative
|
||||
padding: 0
|
||||
|
||||
#gradebook-grid-wrapper
|
||||
position: relative
|
||||
|
||||
#gradebook_students_grid
|
||||
float: left
|
||||
width: 250px
|
||||
|
||||
// put some space at the bottom of the student names slickgrid to compensate
|
||||
// for the lack of a scrollbar like the main grade grid so when you scroll
|
||||
// to the bottom it can match the same scrollTop as the main grid.
|
||||
.grid-canvas
|
||||
padding-bottom: 50px
|
||||
.student-name
|
||||
color: #1b7eda
|
||||
text-shadow: #fbf8f8 0 0 1px
|
||||
.student-section
|
||||
font-size: 10px
|
||||
color: #aaa
|
||||
.secondary_identifier_cell
|
||||
color: #333
|
||||
font-size: 10px
|
||||
text-align: center
|
||||
line-height: 35px
|
||||
|
||||
// override the default jquery ui top border
|
||||
.slick-header.ui-state-default
|
||||
border-top: 0
|
||||
.slick-header-column
|
||||
+vertical-gradiant(#f3f4f8, #dbdcde)
|
||||
padding: 10px
|
||||
font-size: 12px
|
||||
text-align: center
|
||||
font-weight: normal
|
||||
// override jqueryUI style
|
||||
&.ui-state-default
|
||||
height: 30px
|
||||
&.hovered-column
|
||||
+vertical-gradiant(#e0ecf9, #bdd2e3)
|
||||
|
||||
.slick-column-name
|
||||
font-weight: bold
|
||||
color: #2f2a34
|
||||
text-shadow: #fff 0 0 1px
|
||||
|
||||
.assignment-name
|
||||
font-weight: normal
|
||||
color: #1b7ecf
|
||||
text-shadow: #fff 0 0 1px
|
||||
|
||||
//this is for the ellises
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
.assignment-points-possible
|
||||
font-weight: normal
|
||||
text-shadow: #84848a 0 0 1px
|
||||
font-size: 10px
|
||||
+opacity(0.5)
|
||||
color: #2f2a34
|
||||
|
||||
|
||||
#sort_rows_dialog
|
||||
.button
|
||||
margin-bottom: 5px
|
||||
|
||||
.odd .slick-cell
|
||||
background-color: #f5f6f7
|
||||
.even .slick-cell
|
||||
background-color: #fcfcfc
|
||||
.odd .meta-cell
|
||||
background-color: #eff3f4
|
||||
.even .meta-cell
|
||||
background-color: #e6eaec
|
||||
.odd .total-cell
|
||||
background-color: #e0e6e8
|
||||
.even .total-cell
|
||||
background-color: #d7dee1
|
||||
|
||||
// overrides of jqueryUI theme
|
||||
.slick-row.ui-state-active
|
||||
color: inherit
|
||||
background-image: none
|
||||
font-weight: normal
|
||||
// my styles
|
||||
.slick-row.ui-state-active .slick-cell
|
||||
background-color: #dfe9f2
|
||||
|
||||
.slick-row.ui-state-active .slick-cell.selected
|
||||
+vertical-gradiant(#d7eaf9, #bedff6)
|
||||
:background-color #CDF
|
||||
|
||||
|
||||
.slick-cell div.cell-content
|
||||
:text-align center
|
||||
:vertical-align middle
|
||||
|
||||
.slick-cell.editable
|
||||
border-color: #2fa1ff
|
||||
|
||||
.gradebook-tooltip
|
||||
display: none
|
||||
background-color: #444
|
||||
color: #fff
|
||||
+border-radius(3px)
|
||||
padding: 5px 10px
|
||||
z-index: 5
|
||||
position: absolute
|
||||
font-size: 0.8em
|
||||
left: 0
|
||||
top: -30px
|
||||
&:after
|
||||
border-color: #444 transparent
|
||||
border-style: solid
|
||||
border-width: 5px 5px 0
|
||||
position: absolute
|
||||
bottom: -5px
|
||||
width: 0
|
||||
left: 15px
|
||||
content: ""
|
||||
|
||||
// make the tooltips for the top row pop-under so they fit in the grid
|
||||
.slick-row[row="0"] &
|
||||
bottom: -30px
|
||||
top: auto
|
||||
&:after
|
||||
border-width: 0 5px 5px
|
||||
bottom: auto
|
||||
top: -5px
|
||||
.gradebook-cell.hover &, .gradebook-cell.focus &, .slick-cell.selected &
|
||||
display: block
|
||||
|
||||
|
||||
|
||||
.gradebook-cell
|
||||
padding-top: 8px
|
||||
height: $cell_height - 8px
|
||||
position: relative
|
||||
text-align: center
|
||||
|
||||
border: 1px solid transparent
|
||||
border-right: 1px dotted silver
|
||||
border-bottom-color: silver
|
||||
background-repeat: repeat
|
||||
|
||||
&.late
|
||||
background-image: url("/images/gradebook-late-indicator.png")
|
||||
&.dropped
|
||||
background-image: url("/images/gradebook-dropped-indicator.png")
|
||||
&.resubmitted
|
||||
background-image: url("/images/gradebook-resubmitted-indicator.png")
|
||||
|
||||
|
||||
.gradebook-cell-comment
|
||||
position: absolute
|
||||
top: -1px
|
||||
right: -1px
|
||||
background: url("/images/gradebook-comments-sprite2.png") no-repeat 100% 0
|
||||
height: 12px
|
||||
width: 12px
|
||||
visibility: hidden
|
||||
z-index: 1 //needs to be above "normal but below tooltips"
|
||||
overflow: hidden
|
||||
|
||||
&:hover,
|
||||
&:focus
|
||||
visibility: visible
|
||||
background-position: 100% -88px !important
|
||||
width: 17px
|
||||
height: 17px
|
||||
|
||||
.gradebook-cell.with-comments &
|
||||
visibility: visible
|
||||
.gradebook-cell.focus &,
|
||||
.gradebook-cell.hover &
|
||||
visibility: visible
|
||||
background-position: 100% -41px
|
||||
width: 25px
|
||||
height: 25px
|
||||
|
||||
.gradebook-cell-comment-label
|
||||
+accessible_text_replacement
|
||||
|
||||
|
||||
.gradebook-cell-editable
|
||||
height: $cell_height - 1px -8px
|
||||
padding-top: 8px
|
||||
margin: 0
|
||||
border: 0
|
||||
border: 1px solid #35a5e5
|
||||
background-color: #fff
|
||||
box-shadow: 0 0 5px rgba(81, 203, 238, 1)
|
||||
-webkit-box-shadow: 0 0 5px rgba(81, 203, 238, 1)
|
||||
-moz-box-shadow: 0 0 5px rgba(81, 203, 238, 1)
|
||||
.gradebook-cell
|
||||
.grade
|
||||
border: none
|
||||
text-align: center
|
||||
outline: none
|
||||
font-size: 12px
|
||||
width: 100%
|
||||
padding: 0
|
||||
margin: 0
|
||||
background: none
|
||||
.grade::-webkit-outer-spin-button
|
||||
display: none
|
||||
.gradebook-cell-out-of-formatter
|
||||
padding-top: 0
|
||||
height: $cell_height - 1px
|
||||
.overflow-wrapper
|
||||
overflow: hidden
|
||||
position: relative
|
||||
width: 100%
|
||||
height: $cell_height - 1px
|
||||
.grade-and-outof-wrapper
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
width: 400px
|
||||
margin-left: -200px
|
||||
margin-top: -10px //changeme
|
||||
.divider
|
||||
padding: 0 1px 0 2px
|
||||
.outof, .grade
|
||||
width: 200px
|
||||
display: inline-block
|
||||
background: transparent
|
||||
.outof
|
||||
+unselectable
|
||||
|
||||
text-align: left
|
||||
color: #888
|
||||
.grade
|
||||
border: none
|
||||
text-align: right
|
||||
outline: none
|
||||
font-size: 12px
|
||||
.grade::-webkit-outer-spin-button
|
||||
display: none
|
||||
|
||||
$gradebook_checkbox_width: 16px
|
||||
.gradebook-checkbox
|
||||
+accessible_text_replacement
|
||||
display: block
|
||||
position: absolute
|
||||
left: 50%
|
||||
top: 50%
|
||||
width: $gradebook_checkbox_width
|
||||
height: $gradebook_checkbox_width
|
||||
margin-top: -$gradebook_checkbox_width/2
|
||||
margin-left: -$gradebook_checkbox_width/2
|
||||
|
||||
background: url("/images/checkbox_sprite3.png") no-repeat 0 0
|
||||
&.gradebook-checkbox-pass
|
||||
background-position: -48px 0
|
||||
&.gradebook-checkbox-fail
|
||||
background-position: -64px 0
|
||||
&.editable
|
||||
&.gradebook-checkbox-pass
|
||||
background-position: -16px 0
|
||||
&.gradebook-checkbox-fail
|
||||
background-position: -32px 0
|
||||
|
|
@ -35,4 +35,14 @@
|
|||
background-image: -webkit-gradient(linear,left top,left bottom, color-stop(0, $topColor), color-stop(1, $bottomColor))
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#{$topColor}', EndColorStr='#{$bottomColor}')"
|
||||
|
||||
|
||||
=accessible_text_replacement
|
||||
display: inline-block
|
||||
text-indent: 999px
|
||||
overflow: hidden
|
||||
background-repeat: no-repeat
|
||||
|
||||
|
||||
=unselectable
|
||||
-webkit-user-select: none
|
||||
-moz-user-select: none
|
||||
user-select: none
|
|
@ -0,0 +1,50 @@
|
|||
<%
|
||||
content_for :page_title, "Gradebook - #{@context.name}"
|
||||
@body_classes << "gradebook2"
|
||||
@show_left_side = false
|
||||
jammit_js :slickgrid, :gradebook2
|
||||
jammit_css :slickgrid, :gradebook2
|
||||
options = {
|
||||
:chunk_size => 35,
|
||||
:assignment_groups_url => api_v1_course_assignment_groups_url(@context, :include => [:assignments]),
|
||||
:sections_and_students_url => api_v1_course_sections_url(@context, :include => [:students]),
|
||||
:submissions_url => api_v1_course_student_submissions_url(@context, :grouped => '1'),
|
||||
:change_grade_url => api_v1_course_assignment_submission_url(@context, ":assignment", ":submission"),
|
||||
#:assignment_groups => @context.assignment_groups.all(:include => :assignments),
|
||||
#:sections => @context.course_sections.all(:include => :users),
|
||||
#:submissions => @context.submissions.all(),
|
||||
}
|
||||
%>
|
||||
<div id="gradebook-toolbar">
|
||||
|
||||
</div>
|
||||
<div id="gradebook-grid-wrapper">
|
||||
<div id="gradebook_students_grid"></div>
|
||||
<div id="gradebook_grid"></div>
|
||||
</div>
|
||||
|
||||
<!-- gradebook images -->
|
||||
<div style="display:none;">
|
||||
<span id="submission_tooltip_dropped"><%= t 'dropped_for_grading', 'Dropped for grading purposes' %></span>
|
||||
<span id="submission_tooltip_late"><%= t 'submitted_late', 'Submitted late' %></span>
|
||||
<span id="submission_tooltip_resubmitted"><%= t 'resubmitted', 'Resubmitted since last graded' %></span>
|
||||
<%= image_tag "pass.png", :id => "submission_entry_pass_image", :alt => "Pass", :title => "Pass", :class => "graded_icon" %>
|
||||
<%= image_tag "pass.png", :id => "submission_entry_complete_image", :alt => "Complete", :title => "Complete", :class => "graded_icon" %>
|
||||
<%= image_tag "fail.png", :id => "submission_entry_fail_image", :alt => "Fail", :title => "Fail", :class => "graded_icon" %>
|
||||
<%= image_tag "fail.png", :id => "submission_entry_incomplete_image", :alt => "Incomplete", :title => "Incomplete", :class => "graded_icon" %>
|
||||
</div>
|
||||
|
||||
<div id="gradebook_dialogs" style="display:none;">
|
||||
<div id="sort_rows_dialog" title="Sort Gradebook Rows">
|
||||
<button type="button" class="button sort_gradebook sort_rows" data-sort_by="display_name" title="By Student Name" style="width: 300px;">By Student Name</button>
|
||||
<button type="button" class="button sort_gradebook sort_rows" data-sort_by="section" title="By Section Name" style="width: 300px;">By Section Name</button>
|
||||
<button type="button" class="button sort_gradebook sort_rows by_grade" data-sort_by="grade_desc" title="By Total (Highest First)" style="width: 300px;">By Total (Highest First)</button>
|
||||
<button type="button" class="button sort_gradebook sort_rows by_grade" data-sort_by="grade_asc" title="By Total (Lowest First)" style="width: 300px;">By Total (Lowest First)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% js_block do %>
|
||||
<script>
|
||||
new Gradebook(<%= options.to_json.html_safe %>);
|
||||
</script>
|
||||
<% end %>
|
|
@ -69,7 +69,7 @@ javascripts:
|
|||
- public/javascripts/jquery.metadata.js
|
||||
- public/javascripts/gradebook-history.js
|
||||
jobs:
|
||||
- public/javascripts/jobs.js
|
||||
- public/javascripts/compiled/jobs.js
|
||||
profile:
|
||||
- public/javascripts/profile.js
|
||||
topics:
|
||||
|
@ -124,9 +124,15 @@ javascripts:
|
|||
- public/javascripts/email_lists.js
|
||||
datagrid:
|
||||
- public/javascripts/datagrid.js
|
||||
gradebook2:
|
||||
- public/javascripts/compiled/multi_grid.js
|
||||
- public/javascripts/compiled/grade_calculator.js
|
||||
- public/javascripts/compiled/gradebook2.js
|
||||
- public/javascripts/compiled/submission_cell.js
|
||||
gradebooks:
|
||||
- public/javascripts/gradebooks.js
|
||||
- public/javascripts/message_students.js
|
||||
- public/javascripts/compiled/grade_calculator.js
|
||||
attendance:
|
||||
- public/javascripts/datagrid.js
|
||||
- public/javascripts/attendance.js
|
||||
|
@ -258,6 +264,8 @@ stylesheets:
|
|||
gradebooks:
|
||||
- public/stylesheets/compiled/gradebooks.css
|
||||
- public/stylesheets/compiled/message_students.css
|
||||
gradebook2:
|
||||
- public/stylesheets/compiled/gradebook2.css
|
||||
attendance:
|
||||
- public/stylesheets/compiled/attendance.css
|
||||
quizzes:
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# Configure barista.
|
||||
Barista.configure do |c|
|
||||
c.add_preamble = false
|
||||
|
||||
# Change the root to use app/scripts
|
||||
# c.root = Rails.root.join("app", "scripts")
|
||||
|
||||
# Change the output root, causing Barista to compile into public/coffeescripts
|
||||
# c.output_root = Rails.root.join("public", "coffeescripts")
|
||||
# Change the output root, causing Barista to compile into javascripts/compiled
|
||||
c.output_root = Rails.root+'public/javascripts/compiled'
|
||||
|
||||
# Set the compiler
|
||||
|
||||
|
|
|
@ -91,6 +91,8 @@ ActionController::Routing::Routes.draw do |map|
|
|||
} do |gradebook|
|
||||
gradebook.submissions_upload 'submissions_upload/:assignment_id', :controller => 'gradebooks', :action => 'submissions_zip_upload', :conditions => { :method => :post }
|
||||
end
|
||||
course.resource :gradebook2,
|
||||
:controller => 'gradebook2'
|
||||
course.attendance 'attendance', :controller => 'gradebooks', :action => 'attendance'
|
||||
course.attendance_user 'attendance/:user_id', :controller => 'gradebooks', :action => 'attendance'
|
||||
course.imports 'imports', :controller => 'content_imports', :action => 'intro'
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
module Api::V1::Assignment
|
||||
def assignment_json(assignment, includes = [], show_admin_fields = false)
|
||||
# no includes supported right now
|
||||
hash = assignment.as_json(:include_root => false, :only => %w(id grading_type points_possible position))
|
||||
hash = assignment.as_json(:include_root => false, :only => %w(id grading_type points_possible position due_at))
|
||||
|
||||
hash['name'] = assignment.title
|
||||
|
||||
|
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 140 B |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 966 B |
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1,187 @@
|
|||
(function() {
|
||||
var GradeCalculator;
|
||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
GradeCalculator = (function() {
|
||||
function GradeCalculator() {}
|
||||
GradeCalculator.calculate = function(submissions, groups, weighting_scheme) {
|
||||
var result;
|
||||
result = {};
|
||||
result.group_sums = $.map(groups, __bind(function(group) {
|
||||
return {
|
||||
group: group,
|
||||
current: this.create_group_sum(group, submissions, true),
|
||||
'final': this.create_group_sum(group, submissions, false)
|
||||
};
|
||||
}, this));
|
||||
result.current = this.calculate_total(result.group_sums, true, weighting_scheme);
|
||||
result['final'] = this.calculate_total(result.group_sums, false, weighting_scheme);
|
||||
return result;
|
||||
};
|
||||
GradeCalculator.create_group_sum = function(group, submissions, ignore_ungraded) {
|
||||
var data, dropped, lowOrHigh, rules, s, submission, sum, _fn, _i, _j, _k, _l, _len, _len2, _len3, _len4, _len5, _m, _ref, _ref2, _ref3, _ref4, _ref5, _ref6;
|
||||
sum = {
|
||||
submissions: [],
|
||||
score: 0,
|
||||
possible: 0,
|
||||
submission_count: 0
|
||||
};
|
||||
_fn = __bind(function(submission) {
|
||||
var assignment, data, _ref;
|
||||
if (!submission.assignment_group_id) {
|
||||
assignment = $.detect(group.assignments, function() {
|
||||
return submission.assignment_id === this.id;
|
||||
});
|
||||
if (assignment) {
|
||||
submission.assignment_group_id = group.id;
|
||||
if ((_ref = submission.points_possible) != null) {
|
||||
_ref;
|
||||
} else {
|
||||
submission.points_possible = assignment != null ? assignment.points_possible : void 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
if (submission.assignment_group_id === group.id) {
|
||||
data = {
|
||||
submission: submission,
|
||||
score: 0,
|
||||
possible: 0,
|
||||
percent: 0,
|
||||
drop: false,
|
||||
submitted: false
|
||||
};
|
||||
sum.submissions.push(data);
|
||||
if (!(ignore_ungraded && (!submission.score || submission.score === ''))) {
|
||||
data.score = this.parse(submission.score);
|
||||
data.possible = this.parse(submission.points_possible);
|
||||
data.percent = this.parse(data.score / data.possible);
|
||||
data.submitted = submission.score && submission.score !== '';
|
||||
if (data.submitted) {
|
||||
return sum.submission_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
for (_i = 0, _len = submissions.length; _i < _len; _i++) {
|
||||
submission = submissions[_i];
|
||||
_fn(submission);
|
||||
}
|
||||
sum.submissions.sort(function(a, b) {
|
||||
return a.percent - b.percent;
|
||||
});
|
||||
rules = $.extend({
|
||||
drop_lowest: 0,
|
||||
drop_highest: 0,
|
||||
never_drop: []
|
||||
}, group.rules);
|
||||
dropped = 0;
|
||||
_ref = ['low', 'high'];
|
||||
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
|
||||
lowOrHigh = _ref[_j];
|
||||
_ref2 = sum.submissions;
|
||||
for (_k = 0, _len3 = _ref2.length; _k < _len3; _k++) {
|
||||
data = _ref2[_k];
|
||||
if (!data.drop && rules["drop_" + lowOrHigh + "est"] > 0 && $.inArray(data.assignment_id, rules.never_drop) === -1 && data.possible > 0 && data.submitted) {
|
||||
data.drop = true;
|
||||
if ((_ref3 = data.submission) != null) {
|
||||
_ref3.drop = true;
|
||||
}
|
||||
rules["drop_" + lowOrHigh + "est"] -= 1;
|
||||
dropped += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dropped > 0 && dropped === sum.submission_count) {
|
||||
sum.submissions[sum.submissions.length - 1].drop = false;
|
||||
if ((_ref4 = sum.submissions[sum.submissions.length - 1].submission) != null) {
|
||||
_ref4.drop = true;
|
||||
}
|
||||
dropped -= 1;
|
||||
}
|
||||
sum.submission_count -= dropped;
|
||||
_ref5 = sum.submissions;
|
||||
for (_l = 0, _len4 = _ref5.length; _l < _len4; _l++) {
|
||||
s = _ref5[_l];
|
||||
if (!s.drop) {
|
||||
sum.score += s.score;
|
||||
}
|
||||
}
|
||||
_ref6 = sum.submissions;
|
||||
for (_m = 0, _len5 = _ref6.length; _m < _len5; _m++) {
|
||||
s = _ref6[_m];
|
||||
if (!s.drop) {
|
||||
sum.possible += s.possible;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
GradeCalculator.calculate_total = function(group_sums, ignore_ungraded, weighting_scheme) {
|
||||
var data, data_idx, possible, possible_weight_from_submissions, score, tally, total_possible_weight, _i, _len;
|
||||
data_idx = ignore_ungraded ? 'current' : 'final';
|
||||
if (weighting_scheme === 'percent') {
|
||||
score = 0.0;
|
||||
possible_weight_from_submissions = 0.0;
|
||||
total_possible_weight = 0.0;
|
||||
for (_i = 0, _len = group_sums.length; _i < _len; _i++) {
|
||||
data = group_sums[_i];
|
||||
if (data.group.group_weight > 0) {
|
||||
if (data[data_idx].submission_count > 0) {
|
||||
tally = data[data_idx].score / data[data_idx].possible;
|
||||
score += data.group.group_weight * tally;
|
||||
possible_weight_from_submissions += data.group.group_weight;
|
||||
}
|
||||
total_possible_weight += data.group.group_weight;
|
||||
}
|
||||
}
|
||||
if (ignore_ungraded && possible_weight_from_submissions < 1.0) {
|
||||
possible = total_possible_weight < 1.0 ? total_possible_weight : 1.0;
|
||||
score = score * possible / possible_weight_from_submissions;
|
||||
}
|
||||
return {
|
||||
score: score,
|
||||
possible: 1.0
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
score: this.sum((function() {
|
||||
var _j, _len2, _results;
|
||||
_results = [];
|
||||
for (_j = 0, _len2 = group_sums.length; _j < _len2; _j++) {
|
||||
data = group_sums[_j];
|
||||
_results.push(data[data_idx].score);
|
||||
}
|
||||
return _results;
|
||||
})()),
|
||||
possible: this.sum((function() {
|
||||
var _j, _len2, _results;
|
||||
_results = [];
|
||||
for (_j = 0, _len2 = group_sums.length; _j < _len2; _j++) {
|
||||
data = group_sums[_j];
|
||||
_results.push(data[data_idx].possible);
|
||||
}
|
||||
return _results;
|
||||
})())
|
||||
};
|
||||
}
|
||||
};
|
||||
GradeCalculator.sum = function(values) {
|
||||
var result, value, _i, _len;
|
||||
result = 0;
|
||||
for (_i = 0, _len = values.length; _i < _len; _i++) {
|
||||
value = values[_i];
|
||||
result += value;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
GradeCalculator.parse = function(score) {
|
||||
var result;
|
||||
result = parseFloat(score);
|
||||
if (result && isFinite(result)) {
|
||||
return result;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
return GradeCalculator;
|
||||
})();
|
||||
window.INST.GradeCalculator = GradeCalculator;
|
||||
}).call(this);
|
|
@ -0,0 +1,424 @@
|
|||
(function() {
|
||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
I18n.scoped('gradebook2', function(I18n) {
|
||||
var Gradebook;
|
||||
return this.Gradebook = Gradebook = (function() {
|
||||
function Gradebook(options) {
|
||||
this.options = options;
|
||||
this.showCommentDialog = __bind(this.showCommentDialog, this);
|
||||
this.unhighlightColumns = __bind(this.unhighlightColumns, this);
|
||||
this.highlightColumn = __bind(this.highlightColumn, this);
|
||||
this.calculateStudentGrade = __bind(this.calculateStudentGrade, this);
|
||||
this.groupTotalFormatter = __bind(this.groupTotalFormatter, this);
|
||||
this.staticCellFormatter = __bind(this.staticCellFormatter, this);
|
||||
this.cellFormatter = __bind(this.cellFormatter, this);
|
||||
this.gotSubmissionsChunk = __bind(this.gotSubmissionsChunk, this);
|
||||
this.gotStudents = __bind(this.gotStudents, this);
|
||||
this.gotAssignmentGroups = __bind(this.gotAssignmentGroups, this);
|
||||
this.chunk_start = 0;
|
||||
this.students = {};
|
||||
this.rows = [];
|
||||
this.filterFn = function(student) {
|
||||
return true;
|
||||
};
|
||||
this.sortFn = function(student) {
|
||||
return student.display_name;
|
||||
};
|
||||
this.init();
|
||||
this.includeUngradedAssignments = false;
|
||||
}
|
||||
Gradebook.prototype.init = function() {
|
||||
if (this.options.assignment_groups) {
|
||||
return this.gotAssignmentGroups(this.options.assignment_groups);
|
||||
}
|
||||
return $.ajaxJSON(this.options.assignment_groups_url, "GET", {}, this.gotAssignmentGroups);
|
||||
};
|
||||
Gradebook.prototype.gotAssignmentGroups = function(assignment_groups) {
|
||||
var assignment, group, _i, _j, _len, _len2, _ref;
|
||||
this.assignment_groups = {};
|
||||
this.assignments = {};
|
||||
for (_i = 0, _len = assignment_groups.length; _i < _len; _i++) {
|
||||
group = assignment_groups[_i];
|
||||
$.htmlEscapeValues(group);
|
||||
this.assignment_groups[group.id] = group;
|
||||
_ref = group.assignments;
|
||||
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
|
||||
assignment = _ref[_j];
|
||||
$.htmlEscapeValues(assignment);
|
||||
if (assignment.due_at) {
|
||||
assignment.due_at = $.parseFromISO(assignment.due_at);
|
||||
}
|
||||
this.assignments[assignment.id] = assignment;
|
||||
}
|
||||
}
|
||||
if (this.options.sections) {
|
||||
return this.gotStudents(this.options.sections);
|
||||
}
|
||||
return $.ajaxJSON(this.options.sections_and_students_url, "GET", {}, this.gotStudents);
|
||||
};
|
||||
Gradebook.prototype.gotStudents = function(sections) {
|
||||
var assignment, id, section, student, _i, _j, _len, _len2, _name, _ref, _ref2, _ref3;
|
||||
this.sections = {};
|
||||
this.rows = [];
|
||||
for (_i = 0, _len = sections.length; _i < _len; _i++) {
|
||||
section = sections[_i];
|
||||
$.htmlEscapeValues(section);
|
||||
this.sections[section.id] = section;
|
||||
_ref = section.students;
|
||||
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
|
||||
student = _ref[_j];
|
||||
$.htmlEscapeValues(student);
|
||||
student.computed_current_score || (student.computed_current_score = 0);
|
||||
student.computed_final_score || (student.computed_final_score = 0);
|
||||
this.students[student.id] = student;
|
||||
student.section = section;
|
||||
_ref2 = this.assignments;
|
||||
for (id in _ref2) {
|
||||
assignment = _ref2[id];
|
||||
student[_name = "assignment_" + id] || (student[_name] = {
|
||||
assignment_id: id,
|
||||
user_id: student.id
|
||||
});
|
||||
}
|
||||
this.rows.push(student);
|
||||
}
|
||||
}
|
||||
this.sections_enabled = sections.length > 1;
|
||||
_ref3 = this.students;
|
||||
for (id in _ref3) {
|
||||
student = _ref3[id];
|
||||
student.display_name = "<div class='student-name'>" + student.name + "</div>";
|
||||
if (this.sections_enabled) {
|
||||
student.display_name += "<div class='student-section'>" + student.section.name + "</div>";
|
||||
}
|
||||
}
|
||||
this.initGrid();
|
||||
this.buildRows();
|
||||
return this.getSubmissionsChunk();
|
||||
};
|
||||
Gradebook.prototype.buildRows = function() {
|
||||
var i, id, sortables, student, _len, _ref, _ref2;
|
||||
this.rows.length = 0;
|
||||
sortables = {};
|
||||
_ref = this.students;
|
||||
for (id in _ref) {
|
||||
student = _ref[id];
|
||||
student.row = -1;
|
||||
if (this.filterFn(student)) {
|
||||
this.rows.push(student);
|
||||
sortables[student.id] = this.sortFn(student);
|
||||
}
|
||||
}
|
||||
this.rows.sort(function(a, b) {
|
||||
if (sortables[a.id] < sortables[b.id]) {
|
||||
return -1;
|
||||
} else if (sortables[a.id] > sortables[b.id]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
_ref2 = this.rows;
|
||||
for (i = 0, _len = _ref2.length; i < _len; i++) {
|
||||
student = _ref2[i];
|
||||
student.row = i;
|
||||
}
|
||||
this.multiGrid.removeAllRows();
|
||||
this.multiGrid.updateRowCount();
|
||||
return this.multiGrid.render();
|
||||
};
|
||||
Gradebook.prototype.sortBy = function(sort) {
|
||||
this.sortFn = (function() {
|
||||
switch (sort) {
|
||||
case "display_name":
|
||||
return function(student) {
|
||||
return student.display_name;
|
||||
};
|
||||
case "section":
|
||||
return function(student) {
|
||||
return student.section.name;
|
||||
};
|
||||
case "grade_desc":
|
||||
return function(student) {
|
||||
return -student.computed_current_score;
|
||||
};
|
||||
case "grade_asc":
|
||||
return function(student) {
|
||||
return student.computed_current_score;
|
||||
};
|
||||
}
|
||||
})();
|
||||
return this.buildRows();
|
||||
};
|
||||
Gradebook.prototype.getSubmissionsChunk = function(student_id) {
|
||||
var assignment, id, params, student, students;
|
||||
if (this.options.submissions) {
|
||||
return this.gotSubmissionsChunk(this.options.submissions);
|
||||
}
|
||||
students = this.rows.slice(this.chunk_start, this.chunk_start + this.options.chunk_size);
|
||||
params = {
|
||||
student_ids: (function() {
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = students.length; _i < _len; _i++) {
|
||||
student = students[_i];
|
||||
_results.push(student.id);
|
||||
}
|
||||
return _results;
|
||||
})(),
|
||||
assignment_ids: (function() {
|
||||
var _ref, _results;
|
||||
_ref = this.assignments;
|
||||
_results = [];
|
||||
for (id in _ref) {
|
||||
assignment = _ref[id];
|
||||
_results.push(id);
|
||||
}
|
||||
return _results;
|
||||
}).call(this),
|
||||
response_fields: ['user_id', 'url', 'score', 'grade', 'submission_type', 'submitted_at', 'assignment_id', 'grade_matches_current_submission']
|
||||
};
|
||||
if (students.length > 0) {
|
||||
return $.ajaxJSON(this.options.submissions_url, "GET", params, this.gotSubmissionsChunk);
|
||||
}
|
||||
};
|
||||
Gradebook.prototype.gotSubmissionsChunk = function(student_submissions) {
|
||||
var data, student, submission, _i, _j, _len, _len2, _ref;
|
||||
for (_i = 0, _len = student_submissions.length; _i < _len; _i++) {
|
||||
data = student_submissions[_i];
|
||||
student = this.students[data.user_id];
|
||||
student.submissionsAsArray = [];
|
||||
_ref = data.submissions;
|
||||
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
|
||||
submission = _ref[_j];
|
||||
if (submission.submitted_at) {
|
||||
submission.submitted_at = $.parseFromISO(submission.submitted_at);
|
||||
}
|
||||
student["assignment_" + submission.assignment_id] = submission;
|
||||
student.submissionsAsArray.push(submission);
|
||||
}
|
||||
student.loaded = true;
|
||||
this.multiGrid.removeRow(student.row);
|
||||
this.calculateStudentGrade(student);
|
||||
}
|
||||
this.multiGrid.render();
|
||||
this.chunk_start += this.options.chunk_size;
|
||||
return this.getSubmissionsChunk();
|
||||
};
|
||||
Gradebook.prototype.cellFormatter = function(row, col, submission) {
|
||||
var assignment;
|
||||
if (!this.rows[row].loaded) {
|
||||
return this.staticCellFormatter(row, col, '');
|
||||
} else if (!(submission != null ? submission.grade : void 0)) {
|
||||
return this.staticCellFormatter(row, col, '-');
|
||||
} else {
|
||||
assignment = this.assignments[submission.assignment_id];
|
||||
if (!(assignment != null)) {
|
||||
return this.staticCellFormatter(row, col, '');
|
||||
} else {
|
||||
if (assignment.grading_type === 'points' && assignment.points_possible) {
|
||||
return SubmissionCell.out_of.formatter(row, col, submission, assignment);
|
||||
} else {
|
||||
return (SubmissionCell[assignment.grading_type] || SubmissionCell).formatter(row, col, submission, assignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Gradebook.prototype.staticCellFormatter = function(row, col, val) {
|
||||
return "<div class='cell-content gradebook-cell'>" + val + "</div>";
|
||||
};
|
||||
Gradebook.prototype.groupTotalFormatter = function(row, col, val, columnDef, student) {
|
||||
var gradeToShow, percentage, res;
|
||||
if (val == null) {
|
||||
return '';
|
||||
}
|
||||
gradeToShow = val[this.includeUngradedAssignments ? 'final' : 'current'];
|
||||
percentage = columnDef.field === 'total_grade' ? gradeToShow.score : (gradeToShow.score / gradeToShow.possible) * 100;
|
||||
percentage = Math.round(percentage);
|
||||
if (isNaN(percentage)) {
|
||||
percentage = 0;
|
||||
}
|
||||
if (!gradeToShow.possible) {
|
||||
percentage = '-';
|
||||
} else {
|
||||
percentage += "%";
|
||||
}
|
||||
res = "<div class=\"gradebook-cell\">\n " + (columnDef.field === 'total_grade' ? '' : '<div class="gradebook-tooltip">' + gradeToShow.score + ' / ' + gradeToShow.possible + '</div>') + "\n " + percentage + "\n</div>";
|
||||
return res;
|
||||
};
|
||||
Gradebook.prototype.calculateStudentGrade = function(student) {
|
||||
var group, result, _i, _len, _ref;
|
||||
if (student.loaded) {
|
||||
result = INST.GradeCalculator.calculate(student.submissionsAsArray, this.assignment_groups, 'percent');
|
||||
_ref = result.group_sums;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
group = _ref[_i];
|
||||
student["assignment_group_" + group.group.id] = {
|
||||
current: group.current,
|
||||
'final': group['final']
|
||||
};
|
||||
}
|
||||
return student["total_grade"] = {
|
||||
current: result.current,
|
||||
'final': result['final']
|
||||
};
|
||||
}
|
||||
};
|
||||
Gradebook.prototype.highlightColumn = function(columnIndexOrEvent) {
|
||||
var match;
|
||||
if (isNaN(columnIndexOrEvent)) {
|
||||
match = columnIndexOrEvent.currentTarget.className.match(/c\d+/);
|
||||
if (match) {
|
||||
columnIndexOrEvent = match.toString().replace('c', '');
|
||||
}
|
||||
}
|
||||
return this.$grid.find('.slick-header-column:eq(' + columnIndexOrEvent + ')').addClass('hovered-column');
|
||||
};
|
||||
Gradebook.prototype.unhighlightColumns = function() {
|
||||
return this.$grid.find('.hovered-column').removeClass('hovered-column');
|
||||
};
|
||||
Gradebook.prototype.showCommentDialog = function() {
|
||||
$('<div>TODO: show comments and stuff</div>').dialog();
|
||||
return false;
|
||||
};
|
||||
Gradebook.prototype.onGridInit = function() {
|
||||
var tooltipTexts;
|
||||
tooltipTexts = {};
|
||||
this.$grid = $('#gradebook_grid').fillWindowWithMe({
|
||||
alsoResize: '#gradebook_students_grid',
|
||||
onResize: __bind(function() {
|
||||
return this.multiGrid.resizeCanvas();
|
||||
}, this)
|
||||
}).delegate('.slick-cell', 'mouseenter.gradebook focusin.gradebook', this.highlightColumn).delegate('.slick-cell', 'mouseleave.gradebook focusout.gradebook', this.unhighlightColumns).delegate('.gradebook-cell', 'hover.gradebook', function() {
|
||||
return $(this).toggleClass('hover');
|
||||
}).delegate('.gradebook-cell-comment', 'click.gradebook', this.showCommentDialog);
|
||||
$('#grid-options').click(function(event) {
|
||||
event.preventDefault();
|
||||
return $('#sort_rows_dialog').dialog('close').dialog({
|
||||
width: 400,
|
||||
height: 300
|
||||
});
|
||||
});
|
||||
if (!this.sections_enabled) {
|
||||
$('#sort_rows_dialog .by_section').hide();
|
||||
}
|
||||
return $('#sort_rows_dialog button.sort_rows').click(function() {
|
||||
gradebook.sortBy($(this).data('sort_by'));
|
||||
return $('#sort_rows_dialog').dialog('close');
|
||||
});
|
||||
};
|
||||
Gradebook.prototype.initGrid = function() {
|
||||
var $widthTester, assignment, grids, group, html, id, minWidth, options, outOfFormatter, testWidth, _ref, _ref2;
|
||||
$widthTester = $('<span style="padding:10px" />').appendTo('#content');
|
||||
testWidth = function(text, minWidth) {
|
||||
return Math.max($widthTester.text(text).outerWidth(), minWidth);
|
||||
};
|
||||
this.columns = [
|
||||
{
|
||||
id: 'student',
|
||||
name: "<a href='javascript:void(0)' id='grid-options'>Options</a>",
|
||||
field: 'display_name',
|
||||
width: 150,
|
||||
cssClass: "meta-cell"
|
||||
}, {
|
||||
id: 'secondary_identifier',
|
||||
name: 'secondary ID',
|
||||
field: 'secondary_identifier',
|
||||
width: 100,
|
||||
cssClass: "meta-cell secondary_identifier_cell"
|
||||
}
|
||||
];
|
||||
_ref = this.assignments;
|
||||
for (id in _ref) {
|
||||
assignment = _ref[id];
|
||||
if (assignment.submission_types !== "not_graded") {
|
||||
html = "<div class='assignment-name'>" + assignment.name + "</div>";
|
||||
if (assignment.points_possible != null) {
|
||||
html += "<div class='assignment-points-possible'>" + (I18n.t('points_out_of', "out of %{points_possible}", {
|
||||
points_possible: assignment.points_possible
|
||||
})) + "</div>";
|
||||
}
|
||||
outOfFormatter = assignment && assignment.grading_type === 'points' && (assignment.points_possible != null) && SubmissionCell.out_of;
|
||||
minWidth = outOfFormatter ? 70 : 50;
|
||||
this.columns.push({
|
||||
id: "assignment_" + id,
|
||||
field: "assignment_" + id,
|
||||
name: html,
|
||||
object: assignment,
|
||||
formatter: this.cellFormatter,
|
||||
editor: outOfFormatter || SubmissionCell[assignment.grading_type] || SubmissionCell,
|
||||
minWidth: minWidth,
|
||||
maxWidth: 200,
|
||||
width: testWidth(assignment.name, minWidth)
|
||||
});
|
||||
}
|
||||
}
|
||||
_ref2 = this.assignment_groups;
|
||||
for (id in _ref2) {
|
||||
group = _ref2[id];
|
||||
html = "" + group.name;
|
||||
if (group.group_weight != null) {
|
||||
html += "<div class='assignment-points-possible'>" + (I18n.t('percent_of_grade', "%{percentage} of grade", {
|
||||
percentage: I18n.toPercentage(group.group_weight, {
|
||||
precision: 0
|
||||
})
|
||||
})) + "</div>";
|
||||
}
|
||||
this.columns.push({
|
||||
id: "assignment_group_" + id,
|
||||
field: "assignment_group_" + id,
|
||||
formatter: this.groupTotalFormatter,
|
||||
name: html,
|
||||
object: group,
|
||||
minWidth: 35,
|
||||
maxWidth: 200,
|
||||
width: testWidth(group.name, 35),
|
||||
cssClass: "meta-cell assignment-group-cell"
|
||||
});
|
||||
}
|
||||
this.columns.push({
|
||||
id: "total_grade",
|
||||
field: "total_grade",
|
||||
formatter: this.groupTotalFormatter,
|
||||
name: "Total",
|
||||
minWidth: 50,
|
||||
maxWidth: 100,
|
||||
width: testWidth("Total", 50),
|
||||
cssClass: "total-cell"
|
||||
});
|
||||
$widthTester.remove();
|
||||
options = $.extend({
|
||||
enableCellNavigation: false,
|
||||
enableColumnReorder: false,
|
||||
enableAsyncPostRender: true,
|
||||
asyncPostRenderDelay: 1,
|
||||
autoEdit: true,
|
||||
rowHeight: 35
|
||||
}, this.options);
|
||||
grids = [
|
||||
{
|
||||
selector: '#gradebook_students_grid',
|
||||
columns: this.columns.slice(0, 2)
|
||||
}, {
|
||||
selector: '#gradebook_grid',
|
||||
columns: this.columns.slice(2, this.columns.length),
|
||||
options: {
|
||||
enableCellNavigation: true,
|
||||
editable: true,
|
||||
syncColumnCellResize: true
|
||||
}
|
||||
}
|
||||
];
|
||||
this.multiGrid = new MultiGrid(this.rows, options, grids, 1);
|
||||
this.multiGrid.grids[1].onCellChange = __bind(function(row, col, student) {
|
||||
return this.calculateStudentGrade(student);
|
||||
}, this);
|
||||
this.multiGrid.parent_grid.onKeyDown = __bind(function() {
|
||||
return false;
|
||||
}, this);
|
||||
return typeof this.onGridInit === "function" ? this.onGridInit() : void 0;
|
||||
};
|
||||
return Gradebook;
|
||||
})();
|
||||
});
|
||||
}).call(this);
|
|
@ -1,7 +1,3 @@
|
|||
/* DO NOT MODIFY. This file was compiled Wed, 22 Jun 2011 20:46:48 GMT from
|
||||
* /Users/jon/Dropbox/git/canvas-lms/app/coffeescripts/jobs.coffee
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
|
||||
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
|
@ -0,0 +1,57 @@
|
|||
(function() {
|
||||
var MultiGrid, method, _fn, _i, _len, _ref;
|
||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
this.MultiGrid = MultiGrid = (function() {
|
||||
function MultiGrid(data, default_options, grids, parent_grid) {
|
||||
var grid, grid_opts, options;
|
||||
this.data = data;
|
||||
this.parent_grid_idx = parent_grid;
|
||||
this.grids = (function() {
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = grids.length; _i < _len; _i++) {
|
||||
grid_opts = grids[_i];
|
||||
options = $.extend({}, default_options, grid_opts.options);
|
||||
grid = new Slick.Grid(grid_opts.selector, this.data, grid_opts.columns, options);
|
||||
grid.multiview_grid_opts = grid_opts;
|
||||
grid_opts.$viewport = $(grid_opts.selector).find('.slick-viewport');
|
||||
if (grid_opts === grids[this.parent_grid_idx]) {
|
||||
this.parent_grid = grid;
|
||||
} else {
|
||||
grid_opts.$viewport.css('overflow-y', 'hidden');
|
||||
}
|
||||
_results.push(grid);
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
this.parent_grid.onViewportChanged = __bind(function() {
|
||||
var grid, _i, _len, _ref, _results;
|
||||
_ref = this.grids;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
grid = _ref[_i];
|
||||
_results.push(grid !== this.parent_grid ? (grid.multiview_grid_opts.$viewport[0].scrollTop = this.parent_grid.multiview_grid_opts.$viewport[0].scrollTop, grid.multiview_grid_opts.$viewport.trigger('scroll.slickgrid')) : void 0);
|
||||
}
|
||||
return _results;
|
||||
}, this);
|
||||
}
|
||||
return MultiGrid;
|
||||
})();
|
||||
_ref = ['render', 'removeRow', 'removeAllRows', 'updateRowCount', 'autosizeColumns', 'resizeCanvas'];
|
||||
_fn = function(method) {
|
||||
return MultiGrid.prototype[method] = function() {
|
||||
var grid, _j, _len2, _ref2, _results;
|
||||
_ref2 = this.grids;
|
||||
_results = [];
|
||||
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
||||
grid = _ref2[_j];
|
||||
_results.push(grid[method].apply(grid, arguments));
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
};
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
method = _ref[_i];
|
||||
_fn(method);
|
||||
}
|
||||
}).call(this);
|
|
@ -0,0 +1,225 @@
|
|||
(function() {
|
||||
var SubmissionCell;
|
||||
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
|
||||
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
||||
function ctor() { this.constructor = child; }
|
||||
ctor.prototype = parent.prototype;
|
||||
child.prototype = new ctor;
|
||||
child.__super__ = parent.prototype;
|
||||
return child;
|
||||
}, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
this.SubmissionCell = SubmissionCell = (function() {
|
||||
var tooltipTexts;
|
||||
tooltipTexts = {};
|
||||
function SubmissionCell(opts) {
|
||||
this.opts = opts;
|
||||
this.init();
|
||||
}
|
||||
SubmissionCell.prototype.init = function() {
|
||||
var submission;
|
||||
submission = this.opts.item[this.opts.column.field];
|
||||
this.$wrapper = $(this.cellWrapper('<input class="grade"/>')).appendTo(this.opts.container);
|
||||
return this.$input = this.$wrapper.find('input').focus().select();
|
||||
};
|
||||
SubmissionCell.prototype.destroy = function() {
|
||||
return this.$input.remove();
|
||||
};
|
||||
SubmissionCell.prototype.focus = function() {
|
||||
return this.$input.focus();
|
||||
};
|
||||
SubmissionCell.prototype.loadValue = function() {
|
||||
this.val = this.opts.item[this.opts.column.field].grade || "";
|
||||
this.$input.val(this.val);
|
||||
this.$input[0].defaultValue = this.val;
|
||||
return this.$input.select();
|
||||
};
|
||||
SubmissionCell.prototype.serializeValue = function() {
|
||||
return this.$input.val();
|
||||
};
|
||||
SubmissionCell.prototype.applyValue = function(item, state) {
|
||||
var _ref;
|
||||
item[this.opts.column.field].grade = state;
|
||||
if ((_ref = this.wrapper) != null) {
|
||||
_ref.remove();
|
||||
}
|
||||
return this.postValue(item, state);
|
||||
};
|
||||
SubmissionCell.prototype.postValue = function(item, state) {
|
||||
var submission, url;
|
||||
submission = item[this.opts.column.field];
|
||||
url = this.opts.grid.getOptions().change_grade_url;
|
||||
url = url.replace(":assignment", submission.assignment_id).replace(":submission", submission.user_id);
|
||||
return $.ajaxJSON(url, "PUT", {
|
||||
"submission[posted_grade]": state
|
||||
});
|
||||
};
|
||||
SubmissionCell.prototype.isValueChanged = function() {
|
||||
return this.val !== this.$input.val();
|
||||
};
|
||||
SubmissionCell.prototype.validate = function() {
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
SubmissionCell.formatter = function(row, col, submission, assignment) {
|
||||
return this.prototype.cellWrapper(submission.grade, {
|
||||
submission: submission,
|
||||
assignment: assignment,
|
||||
editable: false
|
||||
});
|
||||
};
|
||||
SubmissionCell.imageForCell = function(image_id) {
|
||||
return $(image_id)[0].outerHTML;
|
||||
};
|
||||
SubmissionCell.prototype.cellWrapper = function(innerContents, options) {
|
||||
var opts, specialClasses, tooltipText;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
opts = $.extend({}, {
|
||||
innerContents: '',
|
||||
classes: '',
|
||||
editable: true
|
||||
}, options);
|
||||
opts.submission || (opts.submission = this.opts.item[this.opts.column.field]);
|
||||
opts.assignment || (opts.assignment = this.opts.column.object);
|
||||
specialClasses = SubmissionCell.classesBasedOnSubmission(opts.submission, opts.assignment);
|
||||
tooltipText = $.map(specialClasses, function(c) {
|
||||
var _ref;
|
||||
return (_ref = tooltipTexts[c]) != null ? _ref : tooltipTexts[c] = $("#submission_tooltip_" + c).text();
|
||||
}).join(', ');
|
||||
return "<div class=\"gradebook-cell " + (opts.editable ? 'gradebook-cell-editable focus' : '') + " " + opts.classes + " " + (specialClasses.join(' ')) + "\">\n " + (tooltipText ? '<div class="gradebook-tooltip">' + tooltipText + '</div>' : '') + "\n <a href=\"#\" class=\"gradebook-cell-comment\"><span class=\"gradebook-cell-comment-label\">submission comments</span></a>\n " + innerContents + "\n</div>";
|
||||
};
|
||||
SubmissionCell.classesBasedOnSubmission = function(submission, assignment) {
|
||||
var classes;
|
||||
if (submission == null) {
|
||||
submission = {};
|
||||
}
|
||||
if (assignment == null) {
|
||||
assignment = {};
|
||||
}
|
||||
classes = [];
|
||||
if (submission.grade_matches_current_submission === false) {
|
||||
classes.push('resubmitted');
|
||||
}
|
||||
if (assignment.due_at && submission.submitted_at && (submission.submitted_at.timestamp > assignment.due_at.timestamp)) {
|
||||
classes.push('late');
|
||||
}
|
||||
if (submission.drop) {
|
||||
classes.push('dropped');
|
||||
}
|
||||
return classes;
|
||||
};
|
||||
return SubmissionCell;
|
||||
})();
|
||||
SubmissionCell.out_of = (function() {
|
||||
__extends(out_of, SubmissionCell);
|
||||
function out_of() {
|
||||
out_of.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
out_of.prototype.init = function() {
|
||||
var submission;
|
||||
submission = this.opts.item[this.opts.column.field];
|
||||
this.$wrapper = $(this.cellWrapper("<div class=\"overflow-wrapper\">\n <div class=\"grade-and-outof-wrapper\">\n <input type=\"number\" class=\"grade\"/><span class=\"outof\"><span class=\"divider\">/</span>" + this.opts.column.object.points_possible + "</span>\n </div>\n</div>", {
|
||||
classes: 'gradebook-cell-out-of-formatter'
|
||||
})).appendTo(this.opts.container);
|
||||
return this.$input = this.$wrapper.find('input').focus().select();
|
||||
};
|
||||
return out_of;
|
||||
})();
|
||||
SubmissionCell.pass_fail = (function() {
|
||||
var classFromSubmission, states;
|
||||
__extends(pass_fail, SubmissionCell);
|
||||
function pass_fail() {
|
||||
pass_fail.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
states = ['pass', 'fail', ''];
|
||||
classFromSubmission = function(submission) {
|
||||
return {
|
||||
pass: 'pass',
|
||||
complete: 'pass',
|
||||
fail: 'fail',
|
||||
incomplete: 'fail'
|
||||
}[submission.grade] || '';
|
||||
};
|
||||
pass_fail.prototype.htmlFromSubmission = function(options) {
|
||||
var cssClass;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
cssClass = classFromSubmission(options.submission);
|
||||
return SubmissionCell.prototype.cellWrapper("<a data-value=\"" + cssClass + "\" class=\"gradebook-checkbox gradebook-checkbox-" + cssClass + " " + (options.editable ? 'editable' : void 0) + "\" href=\"#\">" + cssClass + "</a>", options);
|
||||
};
|
||||
pass_fail.formatter = function(row, col, submission, assignment) {
|
||||
return pass_fail.prototype.htmlFromSubmission({
|
||||
submission: submission,
|
||||
assignment: assignment,
|
||||
editable: false
|
||||
});
|
||||
};
|
||||
pass_fail.prototype.init = function() {
|
||||
this.$wrapper = $(this.cellWrapper());
|
||||
this.$wrapper = $(this.htmlFromSubmission({
|
||||
submission: this.opts.item[this.opts.column.field],
|
||||
assignment: this.opts.column.object,
|
||||
editable: true
|
||||
})).appendTo(this.opts.container);
|
||||
return this.$input = this.$wrapper.find('.gradebook-checkbox').bind('click keypress', __bind(function(event) {
|
||||
var currentValue, newValue;
|
||||
event.preventDefault();
|
||||
currentValue = this.$input.data('value');
|
||||
if (currentValue === 'pass') {
|
||||
newValue = 'fail';
|
||||
} else if (currentValue === 'fail') {
|
||||
newValue = '';
|
||||
} else {
|
||||
newValue = 'pass';
|
||||
}
|
||||
return this.transitionValue(newValue);
|
||||
}, this)).focus();
|
||||
};
|
||||
pass_fail.prototype.destroy = function() {
|
||||
return this.$wrapper.remove();
|
||||
};
|
||||
pass_fail.prototype.focus = function() {
|
||||
return this.$input.focus();
|
||||
};
|
||||
pass_fail.prototype.transitionValue = function(newValue) {
|
||||
return this.$input.removeClass('gradebook-checkbox-pass gradebook-checkbox-fail').addClass('gradebook-checkbox-' + classFromSubmission({
|
||||
grade: newValue
|
||||
})).data('value', newValue);
|
||||
};
|
||||
pass_fail.prototype.loadValue = function() {
|
||||
return this.val = this.opts.item[this.opts.column.field].grade || "";
|
||||
};
|
||||
pass_fail.prototype.serializeValue = function() {
|
||||
return this.$input.data('value');
|
||||
};
|
||||
pass_fail.prototype.applyValue = function(item, state) {
|
||||
item[this.opts.column.field].grade = state;
|
||||
return this.postValue(item, state);
|
||||
};
|
||||
pass_fail.prototype.postValue = function(item, state) {
|
||||
var submission, url;
|
||||
submission = item[this.opts.column.field];
|
||||
url = this.opts.grid.getOptions().change_grade_url;
|
||||
url = url.replace(":assignment", submission.assignment_id).replace(":submission", submission.user_id);
|
||||
$.ajaxJSON(url, "PUT", {
|
||||
"submission[posted_grade]": state
|
||||
});
|
||||
return this.$input.effect('highlight');
|
||||
};
|
||||
pass_fail.prototype.isValueChanged = function() {
|
||||
return this.val !== this.$input.data('value');
|
||||
};
|
||||
return pass_fail;
|
||||
})();
|
||||
SubmissionCell.points = (function() {
|
||||
__extends(points, SubmissionCell);
|
||||
function points() {
|
||||
points.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
return points;
|
||||
})();
|
||||
}).call(this);
|
|
@ -77,6 +77,54 @@
|
|||
} while (prefix && $('#' + id).length);
|
||||
return id;
|
||||
};
|
||||
|
||||
// Return the first value which passes a truth test
|
||||
$.detect = function(collection, callback) {
|
||||
var result;
|
||||
$.each(collection, function(index, value) {
|
||||
if (callback.call(value, index, collection)) {
|
||||
result = value;
|
||||
return false; // we found it, break the $.each() loop iteration by returning false
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// this is just pulled from jquery 1.6 because jquery 1.5 could not do .map on an object
|
||||
$.map = function (elems, callback, arg) {
|
||||
var value, key, ret = [],
|
||||
i = 0,
|
||||
length = elems.length,
|
||||
|
||||
|
||||
// jquery objects are treated as arrays
|
||||
isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ((length > 0 && elems[0] && elems[length - 1]) || length === 0 || jQuery.isArray(elems));
|
||||
|
||||
// Go through the array, translating each of the items to their
|
||||
if (isArray) {
|
||||
for (; i < length; i++) {
|
||||
value = callback(elems[i], i, arg);
|
||||
|
||||
if (value != null) {
|
||||
ret[ret.length] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Go through every key on the object,
|
||||
} else {
|
||||
for (key in elems) {
|
||||
value = callback(elems[key], key, arg);
|
||||
|
||||
if (value != null) {
|
||||
ret[ret.length] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten any nested arrays
|
||||
return ret.concat.apply([], ret);
|
||||
}
|
||||
|
||||
// Intercepts the default form submission process. Uses the form tag's
|
||||
// current action and method attributes to know where to submit to.
|
||||
|
@ -3315,20 +3363,26 @@
|
|||
};
|
||||
|
||||
// this is used if you want to fill the browser window with something inside #content but you want to also leave the footer and header on the page.
|
||||
$.fn.fillWindowWithMe = function(){
|
||||
var $this = $(this),
|
||||
$.fn.fillWindowWithMe = function(options){
|
||||
var opts = $.extend({minHeight: 400}, options),
|
||||
$this = $(this),
|
||||
$wrapper_container = $('#wrapper-container'),
|
||||
$main = $('#main'),
|
||||
$not_right_side = $('#not_right_side'),
|
||||
$window = $(window);
|
||||
$window = $(window),
|
||||
$toResize = $(this).add(opts.alsoResize);
|
||||
|
||||
function fillWindowWithThisElement(){
|
||||
$this.height(0);
|
||||
$toResize.height(0);
|
||||
var spaceLeftForThis = $window.height()
|
||||
- ($wrapper_container.offset().top + $wrapper_container.height())
|
||||
+ ($main.height() - $not_right_side.height());
|
||||
+ ($main.height() - $not_right_side.height()),
|
||||
newHeight = Math.max(400, spaceLeftForThis);
|
||||
|
||||
$this.height( Math.max(400, spaceLeftForThis) );
|
||||
$toResize.height(newHeight);
|
||||
if ($.isFunction(opts.onResize)) {
|
||||
opts.onResize.call($this, newHeight);
|
||||
}
|
||||
}
|
||||
fillWindowWithThisElement();
|
||||
$window
|
||||
|
|
|
@ -40,16 +40,22 @@ describe AssignmentGroupsController, :type => :integration do
|
|||
'id' => group2.id,
|
||||
'name' => 'group2',
|
||||
'position' => 7,
|
||||
'rules' => {},
|
||||
'group_weight' => 0
|
||||
},
|
||||
{
|
||||
'id' => group1.id,
|
||||
'name' => 'group1',
|
||||
'position' => 10,
|
||||
'rules' => {},
|
||||
'group_weight' => 0
|
||||
},
|
||||
{
|
||||
'id' => group3.id,
|
||||
'name' => 'group3',
|
||||
'position' => 12,
|
||||
'rules' => {},
|
||||
'group_weight' => 0
|
||||
},
|
||||
]
|
||||
end
|
||||
|
@ -58,8 +64,10 @@ describe AssignmentGroupsController, :type => :integration do
|
|||
course_with_teacher(:active_all => true)
|
||||
group1 = @course.assignment_groups.create!(:name => 'group1')
|
||||
group1.update_attribute(:position, 10)
|
||||
group1.update_attribute(:group_weight, 40)
|
||||
group2 = @course.assignment_groups.create!(:name => 'group2')
|
||||
group2.update_attribute(:position, 7)
|
||||
group2.update_attribute(:group_weight, 60)
|
||||
|
||||
a1 = @course.assignments.create!(:title => "test1", :assignment_group => group1, :points_possible => 10)
|
||||
a2 = @course.assignments.create!(:title => "test2", :assignment_group => group1, :points_possible => 12)
|
||||
|
@ -82,9 +90,12 @@ describe AssignmentGroupsController, :type => :integration do
|
|||
'id' => group2.id,
|
||||
'name' => 'group2',
|
||||
'position' => 7,
|
||||
'rules' => {},
|
||||
'group_weight' => 60,
|
||||
'assignments' => [
|
||||
{
|
||||
'id' => a3.id,
|
||||
'due_at' => nil,
|
||||
'name' => 'test3',
|
||||
'position' => 1,
|
||||
'points_possible' => 8,
|
||||
|
@ -113,6 +124,7 @@ describe AssignmentGroupsController, :type => :integration do
|
|||
},
|
||||
{
|
||||
'id' => a4.id,
|
||||
'due_at' => nil,
|
||||
'name' => 'test4',
|
||||
'position' => 2,
|
||||
'points_possible' => 9,
|
||||
|
@ -128,9 +140,12 @@ describe AssignmentGroupsController, :type => :integration do
|
|||
'id' => group1.id,
|
||||
'name' => 'group1',
|
||||
'position' => 10,
|
||||
'rules' => {},
|
||||
'group_weight' => 40,
|
||||
'assignments' => [
|
||||
{
|
||||
'id' => a1.id,
|
||||
'due_at' => nil,
|
||||
'name' => 'test1',
|
||||
'position' => 1,
|
||||
'points_possible' => 10,
|
||||
|
@ -142,6 +157,7 @@ describe AssignmentGroupsController, :type => :integration do
|
|||
},
|
||||
{
|
||||
'id' => a2.id,
|
||||
'due_at' => nil,
|
||||
'name' => 'test2',
|
||||
'position' => 2,
|
||||
'points_possible' => 12,
|
||||
|
|
|
@ -79,6 +79,7 @@ describe AssignmentsApiController, :type => :integration do
|
|||
'use_rubric_for_grading' => true,
|
||||
'free_form_criterion_comments' => true,
|
||||
'needs_grading_count' => 0,
|
||||
'due_at' => nil,
|
||||
'submission_types' => [
|
||||
"online_upload",
|
||||
"online_text_entry",
|
||||
|
@ -145,6 +146,7 @@ describe AssignmentsApiController, :type => :integration do
|
|||
'points_possible' => 12,
|
||||
'grading_type' => 'points',
|
||||
'needs_grading_count' => 0,
|
||||
'due_at' => nil,
|
||||
'submission_types' => [
|
||||
'none',
|
||||
],
|
||||
|
@ -181,6 +183,7 @@ describe AssignmentsApiController, :type => :integration do
|
|||
'points_possible' => 15,
|
||||
'grading_type' => 'points',
|
||||
'needs_grading_count' => 0,
|
||||
'due_at' => nil,
|
||||
'submission_types' => [
|
||||
'none',
|
||||
],
|
||||
|
|