GradeSummary.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. /*
  2. * Copyright (C) 2017 - present Instructure, Inc.
  3. *
  4. * This file is part of Canvas.
  5. *
  6. * Canvas is free software: you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License as published by the Free
  8. * Software Foundation, version 3 of the License.
  9. *
  10. * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
  11. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. import _ from 'underscore'
  19. import $ from 'jquery'
  20. import I18n from 'i18n!gradebook'
  21. import htmlEscape from 'str/htmlEscape'
  22. import numberHelper from '../shared/helpers/numberHelper'
  23. import round from 'compiled/util/round'
  24. import CourseGradeCalculator from '../gradebook/CourseGradeCalculator'
  25. import {scopeToUser} from '../gradebook/EffectiveDueDates'
  26. import {scoreToGrade} from '../gradebook/GradingSchemeHelper'
  27. import GradeFormatHelper from '../gradebook/shared/helpers/GradeFormatHelper'
  28. import StatusPill from '../grading/StatusPill'
  29. import gradingPeriodSetsApi from 'compiled/api/gradingPeriodSetsApi'
  30. import 'jquery.ajaxJSON'
  31. import 'jquery.instructure_misc_helpers' /* replaceTags */
  32. import 'jquery.instructure_misc_plugins' /* showIf */
  33. import 'jquery.templateData'
  34. import 'compiled/jquery/mediaCommentThumbnail'
  35. import 'media_comments' /* mediaComment */
  36. const GradeSummary = {
  37. getSelectedGradingPeriodId () {
  38. const $select = document.querySelector('.grading_periods_selector')
  39. return ($select && $select.value !== '0') ? $select.value : null
  40. },
  41. getAssignmentId ($assignment) {
  42. return $assignment.getTemplateData({ textValues: ['assignment_id'] }).assignment_id
  43. },
  44. parseScoreText (text, numericalDefault, formattedDefault) {
  45. const defaultNumericalValue = (typeof numericalDefault === 'number') ? numericalDefault : null
  46. const defaultFormattedValue = (typeof formattedDefault === 'string') ? formattedDefault : '-'
  47. let numericalValue = numberHelper.parse(text)
  48. numericalValue = isNaN(numericalValue) ? defaultNumericalValue : numericalValue
  49. return {
  50. numericalValue,
  51. formattedValue: GradeFormatHelper.formatGrade(numericalValue, { defaultValue: defaultFormattedValue })
  52. }
  53. },
  54. getOriginalScore ($assignment) {
  55. let numericalValue = parseFloat($assignment.find('.original_points').text())
  56. numericalValue = isNaN(numericalValue) ? null : numericalValue
  57. return {
  58. numericalValue,
  59. formattedValue: $assignment.find('.original_score').text()
  60. }
  61. },
  62. onEditWhatIfScore ($assignmentScore, $ariaAnnouncer) {
  63. // Store the original score so that it can be restored when the "What-If" score is reverted.
  64. if (!$assignmentScore.find('.grade').data('originalValue')) {
  65. $assignmentScore.find('.grade').data('originalValue', $assignmentScore.find('.grade').html())
  66. }
  67. const $screenreaderLinkClone = $assignmentScore.find('.screenreader-only').clone(true)
  68. $assignmentScore.find('.grade').data('screenreader_link', $screenreaderLinkClone)
  69. $assignmentScore.find('.grade').empty().append($('#grade_entry'))
  70. $assignmentScore.find('.score_value').hide()
  71. $ariaAnnouncer.text(I18n.t('Enter a What-If score.'))
  72. // Get the current shown score (possibly a "What-If" score)
  73. // and use it as the default value in the text entry field
  74. const whatIfScoreText = $assignmentScore.parents('.student_assignment').find('.what_if_score').text()
  75. const score = GradeSummary.parseScoreText(whatIfScoreText, 0, '0')
  76. $('#grade_entry').val(score.formattedValue)
  77. .show()
  78. .focus()
  79. .select()
  80. },
  81. onScoreChange ($assignment, options) {
  82. const originalScore = GradeSummary.getOriginalScore($assignment)
  83. // parse the score entered by the user
  84. const enteredScoreText = $assignment.find('#grade_entry').val()
  85. let score = GradeSummary.parseScoreText(enteredScoreText)
  86. // if the user cleared the score, use the previous What-If score
  87. if (score.numericalValue == null) {
  88. const previousWhatIfScore = $assignment.find('.what_if_score').text()
  89. score = GradeSummary.parseScoreText(previousWhatIfScore)
  90. }
  91. // if there is no What-If score, use the original score
  92. if (score.numericalValue == null) {
  93. score = originalScore
  94. }
  95. // set 'isChanged' to true if the user entered the score already on the submission
  96. const isChanged = score.numericalValue != originalScore.numericalValue // eslint-disable-line eqeqeq
  97. // update '.what_if_score' with the parsed value from '#grade_entry'
  98. $assignment.find('.what_if_score').text(score.formattedValue)
  99. let shouldUpdate = options.update
  100. if ($assignment.hasClass('dont_update')) {
  101. shouldUpdate = false
  102. $assignment.removeClass('dont_update')
  103. }
  104. const assignmentId = GradeSummary.getAssignmentId($assignment)
  105. if (shouldUpdate) {
  106. const url = $.replaceTags($('.update_submission_url').attr('href'), 'assignment_id', assignmentId)
  107. // if the original score was entered, remove the student entered score
  108. const scoreForUpdate = isChanged ? score.numericalValue : null
  109. $.ajaxJSON(url, 'PUT', { 'submission[student_entered_score]': scoreForUpdate },
  110. (data) => {
  111. const updatedData = {student_entered_score: data.submission.student_entered_score}
  112. $assignment.fillTemplateData({ data: updatedData })
  113. },
  114. $.noop
  115. )
  116. }
  117. $('#grade_entry').hide().appendTo($('body'))
  118. const $assignmentScore = $assignment.find('.assignment_score')
  119. const $scoreTeaser = $assignmentScore.find('.score_teaser')
  120. const $grade = $assignment.find('.grade')
  121. if (isChanged) {
  122. $assignmentScore.attr('title', '')
  123. $scoreTeaser.text(I18n.t('This is a What-If score'))
  124. const $revertScore = $('#revert_score_template').clone(true).attr('id', '').show()
  125. $assignmentScore.find('.score_holder').append($revertScore)
  126. $grade.addClass('changed')
  127. // this is to distinguish between the revert_all_scores_link in the right nav and
  128. // the revert arrows within the grade_summary page grid
  129. if (options.refocus) {
  130. setTimeout(() => { $assignment.find('.revert_score_link').focus() }, 0)
  131. }
  132. } else {
  133. const tooltip = $assignment.data('muted') ?
  134. I18n.t('Instructor is working on grades') :
  135. I18n.t('Click to test a different score')
  136. $assignmentScore.attr('title', I18n.t('Click to test a different score'))
  137. $scoreTeaser.text(tooltip)
  138. $grade.removeClass('changed')
  139. $assignment.find('.revert_score_link').remove()
  140. if (options.refocus) {
  141. setTimeout(() => { $assignment.find('.grade').focus() }, 0)
  142. }
  143. }
  144. if (score.numericalValue == null) {
  145. $grade.html($grade.data('originalValue'))
  146. } else {
  147. $grade.html(htmlEscape(score.formattedValue))
  148. }
  149. if (!isChanged) {
  150. const $screenreaderLinkClone = $assignment.find('.grade').data('screenreader_link')
  151. $assignment.find('.grade').prepend($screenreaderLinkClone)
  152. }
  153. GradeSummary.updateScoreForAssignment(assignmentId, score.numericalValue)
  154. GradeSummary.updateStudentGrades()
  155. },
  156. onScoreRevert ($assignment, options) {
  157. const opts = { refocus: true, skipEval: false, ...options }
  158. const score = GradeSummary.getOriginalScore($assignment)
  159. let tooltip
  160. if ($assignment.data('muted')) {
  161. tooltip = I18n.t('Instructor is working on grades')
  162. } else {
  163. tooltip = I18n.t('Click to test a different score')
  164. }
  165. const $assignmentScore = $assignment.find('.assignment_score')
  166. $assignment.find('.what_if_score').text(score.formattedValue)
  167. $assignmentScore.attr('title', I18n.t('Click to test a different score'))
  168. $assignmentScore.find('.score_teaser').text(tooltip)
  169. $assignmentScore.find('.grade').removeClass('changed')
  170. $assignment.find('.revert_score_link').remove()
  171. $assignment.find('.score_value').text(score.formattedValue)
  172. if ($assignment.data('muted')) {
  173. $assignment.find('.grade').html('<i class="icon-muted muted_icon" aria-hidden="true"></i>')
  174. } else {
  175. $assignment.find('.grade').text(score.formattedValue)
  176. }
  177. const assignmentId = $assignment.getTemplateValue('assignment_id')
  178. GradeSummary.updateScoreForAssignment(assignmentId, score.numericalValue)
  179. if (!opts.skipEval) {
  180. GradeSummary.updateStudentGrades()
  181. }
  182. const $screenreaderLinkClone = $assignment.find('.grade').data('screenreader_link')
  183. $assignment.find('.grade').prepend($screenreaderLinkClone)
  184. if (opts.refocus) {
  185. setTimeout(() => { $assignment.find('.grade').focus() }, 0)
  186. }
  187. }
  188. }
  189. function getGradingPeriodSet () {
  190. if (ENV.grading_period_set) {
  191. return gradingPeriodSetsApi.deserializeSet(ENV.grading_period_set)
  192. }
  193. return null
  194. }
  195. function calculateGrades () {
  196. let grades
  197. if (ENV.effective_due_dates && ENV.grading_period_set) {
  198. grades = CourseGradeCalculator.calculate(
  199. ENV.submissions,
  200. ENV.assignment_groups,
  201. ENV.group_weighting_scheme,
  202. getGradingPeriodSet(),
  203. scopeToUser(ENV.effective_due_dates, ENV.student_id)
  204. )
  205. } else {
  206. grades = CourseGradeCalculator.calculate(
  207. ENV.submissions,
  208. ENV.assignment_groups,
  209. ENV.group_weighting_scheme
  210. )
  211. }
  212. const selectedGradingPeriodId = GradeSummary.getSelectedGradingPeriodId()
  213. if (selectedGradingPeriodId) {
  214. return grades.gradingPeriods[selectedGradingPeriodId]
  215. }
  216. return grades
  217. }
  218. function canBeConvertedToGrade (score, possible) {
  219. return possible > 0 && !isNaN(score)
  220. }
  221. function calculatePercentGrade (score, possible) {
  222. return round((score / possible) * 100, round.DEFAULT)
  223. }
  224. function formatPercentGrade (percentGrade) {
  225. return I18n.n(percentGrade, {percentage: true})
  226. }
  227. function calculateGrade (score, possible) {
  228. if (canBeConvertedToGrade(score, possible)) {
  229. return formatPercentGrade(calculatePercentGrade(score, possible))
  230. }
  231. return I18n.t('N/A')
  232. }
  233. function subtotalByGradingPeriod () {
  234. const gpset = ENV.grading_period_set
  235. const gpselected = GradeSummary.getSelectedGradingPeriodId()
  236. return ((!gpselected || gpselected === 0) && gpset && gpset.weighted)
  237. }
  238. function calculateSubtotals (byGradingPeriod, calculatedGrades, currentOrFinal) {
  239. const subtotals = []
  240. let params
  241. if (byGradingPeriod) {
  242. params = {
  243. bins: ENV.grading_periods,
  244. grades: calculatedGrades.gradingPeriods,
  245. elementIdPrefix: '#submission_period'
  246. }
  247. } else {
  248. params = {
  249. bins: ENV.assignment_groups,
  250. grades: calculatedGrades.assignmentGroups,
  251. elementIdPrefix: '#submission_group'
  252. }
  253. }
  254. if (params.grades) {
  255. for (let i = 0; i < params.bins.length; i++) {
  256. const binId = params.bins[i].id
  257. let grade = params.grades[binId]
  258. if (grade) {
  259. grade = grade[currentOrFinal]
  260. } else {
  261. grade = {score: 0, possible: 0}
  262. }
  263. const scoreText = I18n.n(grade.score, {precision: round.DEFAULT})
  264. const possibleText = I18n.n(grade.possible, {precision: round.DEFAULT})
  265. const subtotal = {
  266. teaserText: `${scoreText} / ${possibleText}`,
  267. gradeText: calculateGrade(grade.score, grade.possible),
  268. rowElementId: `${params.elementIdPrefix}-${binId}`
  269. }
  270. subtotals.push(subtotal)
  271. }
  272. }
  273. return subtotals
  274. }
  275. function calculateTotals (calculatedGrades, currentOrFinal, groupWeightingScheme) {
  276. const showTotalGradeAsPoints = ENV.show_total_grade_as_points
  277. const subtotals = calculateSubtotals(subtotalByGradingPeriod(), calculatedGrades, currentOrFinal)
  278. for (let i = 0; i < subtotals.length; i++) {
  279. const $row = $(subtotals[i].rowElementId)
  280. $row.find('.grade').text(subtotals[i].gradeText)
  281. $row.find('.score_teaser').text(subtotals[i].teaserText)
  282. $row.find('.points_possible').text(subtotals[i].teaserText)
  283. }
  284. const finalScore = calculatedGrades[currentOrFinal].score
  285. const finalPossible = calculatedGrades[currentOrFinal].possible
  286. const scoreAsPoints = `${I18n.n(finalScore, {precision: round.DEFAULT})} / ${I18n.n(finalPossible, {precision: round.DEFAULT})}`
  287. const scoreAsPercent = calculateGrade(finalScore, finalPossible)
  288. let finalGrade
  289. let teaserText
  290. if (showTotalGradeAsPoints && groupWeightingScheme !== 'percent') {
  291. finalGrade = scoreAsPoints
  292. teaserText = scoreAsPercent
  293. } else {
  294. finalGrade = scoreAsPercent
  295. teaserText = scoreAsPoints
  296. }
  297. const $finalGradeRow = $('.student_assignment.final_grade')
  298. $finalGradeRow.find('.grade').text(finalGrade)
  299. $finalGradeRow.find('.score_teaser').text(teaserText)
  300. $finalGradeRow.find('.points_possible').text(scoreAsPoints)
  301. if (groupWeightingScheme === 'percent') {
  302. $finalGradeRow.find('.score_teaser').hide()
  303. }
  304. if ($('.grade.changed').length > 0) {
  305. // User changed their points for an assignment => let's let them know their updated points
  306. const msg = I18n.t('Based on What-If scores, the new total is now %{grade}', {grade: finalGrade})
  307. $.screenReaderFlashMessageExclusive(msg)
  308. }
  309. if (ENV.grading_scheme) {
  310. $('.final_letter_grade .grade').text(
  311. scoreToGrade(calculatePercentGrade(finalScore, finalPossible), ENV.grading_scheme)
  312. )
  313. }
  314. $('.revert_all_scores').showIf($('#grades_summary .revert_score_link').length > 0)
  315. }
  316. function updateStudentGrades () {
  317. const droppedMessage = I18n.t('This assignment is dropped and will not be considered in the total calculation')
  318. const ignoreUngradedSubmissions = $('#only_consider_graded_assignments').attr('checked')
  319. const currentOrFinal = ignoreUngradedSubmissions ? 'current' : 'final'
  320. const groupWeightingScheme = ENV.group_weighting_scheme
  321. const includeTotal = !ENV.exclude_total
  322. const calculatedGrades = calculateGrades()
  323. $('.dropped').attr('aria-label', '')
  324. $('.dropped').attr('title', '')
  325. // mark dropped assignments
  326. $('.student_assignment').find('.points_possible').attr('aria-label', '')
  327. _.forEach(calculatedGrades.assignmentGroups, (grades) => {
  328. _.forEach(grades[currentOrFinal].submissions, (submission) => {
  329. $(`#submission_${submission.submission.assignment_id}`).toggleClass('dropped', !!submission.drop)
  330. })
  331. })
  332. $('.dropped').attr('aria-label', droppedMessage)
  333. $('.dropped').attr('title', droppedMessage)
  334. if (includeTotal) {
  335. calculateTotals(calculatedGrades, currentOrFinal, groupWeightingScheme)
  336. }
  337. }
  338. function updateScoreForAssignment (assignmentId, score) {
  339. const submission = _.find(ENV.submissions, s => (`${s.assignment_id}`) === (`${assignmentId}`))
  340. if (submission) {
  341. submission.score = score
  342. } else {
  343. ENV.submissions.push({assignment_id: assignmentId, score})
  344. }
  345. }
  346. function bindShowAllDetailsButton ($ariaAnnouncer) {
  347. $('#show_all_details_button').click((event) => {
  348. event.preventDefault()
  349. const $button = $('#show_all_details_button')
  350. $button.toggleClass('showAll')
  351. if ($button.hasClass('showAll')) {
  352. $button.text(I18n.t('Hide All Details'))
  353. $('tr.student_assignment.editable').each(function () {
  354. const assignmentId = $(this).getTemplateValue('assignment_id')
  355. const muted = $(this).data('muted')
  356. if (!muted) {
  357. $(`#comments_thread_${assignmentId}`).show()
  358. $(`#rubric_${assignmentId}`).show()
  359. $(`#grade_info_${assignmentId}`).show()
  360. $(`#final_grade_info_${assignmentId}`).show()
  361. }
  362. })
  363. $ariaAnnouncer.text(I18n.t('assignment details expanded'))
  364. } else {
  365. $button.text(I18n.t('Show All Details'))
  366. $('tr.rubric_assessments').hide()
  367. $('tr.comments').hide()
  368. $ariaAnnouncer.text(I18n.t('assignment details collapsed'))
  369. }
  370. })
  371. }
  372. function setup () {
  373. $(document).ready(function () {
  374. GradeSummary.updateStudentGrades()
  375. const showAllWhatIfButton = $(this).find('#student-grades-whatif button')
  376. const revertButton = $(this).find('#revert-all-to-actual-score')
  377. const $ariaAnnouncer = $(this).find('#aria-announcer')
  378. $('.revert_all_scores_link').click((event) => {
  379. event.preventDefault()
  380. // we pass in refocus: false here so the focus won't go to the revert arrows within the grid
  381. $('#grades_summary .revert_score_link').each(function () {
  382. $(this).trigger('click', {skipEval: true, refocus: false})
  383. })
  384. $('#.show_guess_grades.exists').show()
  385. GradeSummary.updateStudentGrades()
  386. showAllWhatIfButton.focus()
  387. $.screenReaderFlashMessageExclusive(I18n.t('Grades are now reverted to original scores'))
  388. })
  389. // manages toggling and screenreader focus for comments, scoring, and rubric details
  390. $('.toggle_comments_link, .toggle_score_details_link, ' +
  391. '.toggle_rubric_assessments_link, .toggle_final_grade_info').click(function (event) {
  392. event.preventDefault()
  393. const $row = $(`#${$(this).attr('aria-controls')}`)
  394. const originEl = this
  395. $(originEl).attr('aria-expanded', $row.css('display') === 'none')
  396. $row.toggle()
  397. if ($row.css('display') !== 'none') {
  398. $row.find('.screenreader-toggle').focus()
  399. }
  400. })
  401. $('.screenreader-toggle').click(function (event) {
  402. event.preventDefault()
  403. const ariaControl = $(this).data('aria')
  404. const originEl = $(`a[aria-controls='${ariaControl}']`)
  405. $(originEl).attr('aria-expanded', false)
  406. $(originEl).focus()
  407. $(this).closest('.rubric_assessments, .comments').hide()
  408. })
  409. function editWhatIfScore (event) {
  410. if (event.type === 'click' || event.keyCode === 13) {
  411. if ($('#grades_summary.editable').length === 0 ||
  412. $(this).find('#grade_entry').length > 0 ||
  413. $(event.target).closest('.revert_score_link').length > 0) {
  414. return
  415. }
  416. GradeSummary.onEditWhatIfScore($(this), $ariaAnnouncer)
  417. }
  418. }
  419. $('.student_assignment.editable .assignment_score').on('click keypress', editWhatIfScore)
  420. $('#grade_entry').keydown(function (event) {
  421. if (event.keyCode === 13) {
  422. // Enter Key: Finish Changes
  423. $ariaAnnouncer.text('')
  424. $(this)[0].blur()
  425. } else if (event.keyCode === 27) {
  426. // Escape Key: Clear the Text Field
  427. $ariaAnnouncer.text('')
  428. const val = $(this).parents('.student_assignment')
  429. .addClass('dont_update')
  430. .find('.original_score')
  431. .text()
  432. $(this).val(val || '')[0].blur()
  433. }
  434. })
  435. $('#grades_summary .student_assignment').bind('score_change', function (_event, options) {
  436. GradeSummary.onScoreChange($(this), options)
  437. })
  438. $('#grade_entry').blur(function () {
  439. const $assignment = $(this).parents('.student_assignment')
  440. $assignment.triggerHandler('score_change', { update: true, refocus: true })
  441. })
  442. $('#grades_summary').delegate('.revert_score_link', 'click', function (event, options) {
  443. event.preventDefault()
  444. event.stopPropagation()
  445. GradeSummary.onScoreRevert($(this).parents('.student_assignment'), options)
  446. })
  447. $('#grades_summary:not(.editable) .assignment_score').css('cursor', 'default')
  448. $('#grades_summary tr').hover(function () {
  449. $(this).find('th.title .context').addClass('context_hover')
  450. }, function () {
  451. $(this).find('th.title .context').removeClass('context_hover')
  452. })
  453. $('.show_guess_grades_link').click(() => {
  454. $('#grades_summary .student_entered_score').each(function () {
  455. const score = GradeSummary.parseScoreText($(this).text())
  456. if (score.numericalValue != null) {
  457. const $assignment = $(this).parents('.student_assignment')
  458. $assignment.find('.what_if_score').text(score.formattedValue)
  459. $assignment.find('.score_value').hide()
  460. $assignment.triggerHandler('score_change', { update: false, refocus: false })
  461. }
  462. })
  463. $('.show_guess_grades').hide()
  464. revertButton.focus()
  465. $.screenReaderFlashMessageExclusive(I18n.t('Grades are now showing what-if scores'))
  466. })
  467. $('#grades_summary .student_entered_score').each(function () {
  468. const score = GradeSummary.parseScoreText($(this).text())
  469. if (score.numericalValue != null) {
  470. $('.show_guess_grades').show().addClass('exists')
  471. }
  472. })
  473. $('.comments .play_comment_link').mediaCommentThumbnail('normal')
  474. $('.play_comment_link').live('click', function (event) {
  475. event.preventDefault()
  476. const $parent = $(this).parents('.comment_media')
  477. const commentId = $parent.getTemplateData({textValues: ['media_comment_id']}).media_comment_id
  478. if (commentId) {
  479. let mediaType = 'any'
  480. if ($(this).hasClass('video_comment')) {
  481. mediaType = 'video'
  482. } else if ($(this).hasClass('audio_comment')) {
  483. mediaType = 'audio'
  484. }
  485. $parent.children(':not(.media_comment_content)').remove()
  486. $parent.find('.media_comment_content').mediaComment('show_inline', commentId, mediaType)
  487. }
  488. })
  489. $('#only_consider_graded_assignments').change(() => {
  490. GradeSummary.updateStudentGrades()
  491. }).triggerHandler('change')
  492. $('#observer_user_url').change(function () {
  493. if (location.href !== $(this).val()) {
  494. location.href = $(this).val()
  495. }
  496. })
  497. $('#assignment_order').change(function () {
  498. this.form.submit()
  499. })
  500. bindShowAllDetailsButton($ariaAnnouncer)
  501. StatusPill.renderPills()
  502. })
  503. $(document).on('change', '.grading_periods_selector', function () {
  504. const newGP = $(this).val()
  505. let matches = location.href.match(/grading_period_id=\d*/)
  506. if (matches) {
  507. location.href = location.href.replace(matches[0], `grading_period_id=${newGP}`)
  508. return
  509. }
  510. matches = location.href.match(/#tab-assignments/)
  511. if (matches) {
  512. location.href = `${location.href.replace(matches[0], '')}?grading_period_id=${newGP}${matches[0]}`
  513. } else {
  514. location.href += `?grading_period_id=${newGP}`
  515. }
  516. })
  517. }
  518. export default _.extend(GradeSummary, {
  519. setup,
  520. getGradingPeriodSet,
  521. canBeConvertedToGrade,
  522. calculateGrade,
  523. calculateGrades,
  524. calculateTotals,
  525. calculateSubtotals,
  526. calculatePercentGrade,
  527. formatPercentGrade,
  528. updateScoreForAssignment,
  529. updateStudentGrades
  530. })