491 lines
20 KiB
JavaScript
491 lines
20 KiB
JavaScript
/**
|
|
* Copyright (C) 2011 Instructure, Inc.
|
|
*
|
|
* This file is part of Canvas.
|
|
*
|
|
* Canvas is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, version 3 of the License.
|
|
*
|
|
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
define([
|
|
'i18n!quizzes.moderate',
|
|
'jquery' /* $ */,
|
|
'quiz_timing',
|
|
'jsx/quizzes/moderate/openModerateStudentDialog',
|
|
'jquery.ajaxJSON' /* ajaxJSON */,
|
|
'jquery.instructure_date_and_time' /* datetimeString */,
|
|
'jquery.instructure_forms' /* fillFormData, getFormData */,
|
|
'jqueryui/dialog',
|
|
'compiled/jquery/fixDialogButtons' /* fix dialog formatting */,
|
|
'jquery.instructure_misc_helpers' /* replaceTags */,
|
|
'jquery.instructure_misc_plugins' /* showIf */,
|
|
'compiled/jquery.rails_flash_notifications',
|
|
'jquery.templateData' /* fillTemplateData */,
|
|
'vendor/date' /* Date.parse */
|
|
], function(I18n, $, timing, openModerateStudentDialog) {
|
|
var DIALOG_WIDTH = 490;
|
|
/**
|
|
* Updates the digit(s) in the "gets X extra minutes" message in a student's
|
|
* block.
|
|
*
|
|
* @param {jQuery} $studentBlock
|
|
* Selector to the student block you're updating.
|
|
*
|
|
* @param {Number} extraTime
|
|
* The submission's extra allotted time.
|
|
*/
|
|
var updateExtraTime = function($studentBlock, extraTime) {
|
|
var $extraTime = $studentBlock.find('.extra_time_allowed');
|
|
|
|
if (extraTime > 0) {
|
|
$extraTime.text($extraTime.text().replace(/\s\d+\s/, ' ' + extraTime + ' '));
|
|
}
|
|
|
|
$extraTime.toggle(extraTime > 0);
|
|
};
|
|
|
|
window.moderation = {
|
|
updateTimes: function() {
|
|
var now = new Date();
|
|
moderation.studentsCurrentlyTakingQuiz = !!$("#students .student.in_progress");
|
|
$("#students .student.in_progress").each(function() {
|
|
var $row = $(this);
|
|
var row = $row.data('timing') || {};
|
|
var started_at = $row.attr('data-started-at');
|
|
var end_at = $row.attr('data-end-at');
|
|
if(!row.referenceDate) {
|
|
$.extend(row, timing.setReferenceDate(started_at, end_at, now));
|
|
}
|
|
if(!row.referenceDate) { return; }
|
|
$row.data('timing', row);
|
|
var diff = row.referenceDate.getTime() - now.getTime() - row.clientServerDiff;
|
|
if(row.isDeadline && diff < 0) {
|
|
$row.find(".time").text(I18n.t('time_up', "Time Up!"));
|
|
return;
|
|
}
|
|
$row.data('minutes_left', diff / 60000);
|
|
var date = new Date(Math.abs(diff));
|
|
var yr = date.getUTCFullYear() - 1970;
|
|
var mon = date.getUTCMonth();
|
|
mon = mon + (12 * yr);
|
|
var day = date.getUTCDate() - 1;
|
|
var hr = date.getUTCHours();
|
|
var min = date.getUTCMinutes();
|
|
var sec = date.getUTCSeconds();
|
|
var times = [];
|
|
if(mon) { times.push(mon < 10 ? '0' + mon : mon); }
|
|
if(day) { times.push(day < 10 ? '0' + day : day); }
|
|
if(hr) { times.push(hr < 10 ? '0' + hr : hr); }
|
|
if(true || min) { times.push(min < 10 ? '0' + min : min); }
|
|
if(true || sec) { times.push(sec < 10 ? '0' + sec : sec); }
|
|
$row.find(".time").text(times.join(":"));
|
|
});
|
|
},
|
|
updateSubmission: function(submission, updateLastUpdatedAt) {
|
|
var $student = $("#student_" + submission.user_id);
|
|
if(updateLastUpdatedAt) {
|
|
moderation.lastUpdatedAt = new Date(Math.max(Date.parse(submission.updated_at), moderation.lastUpdatedAt));
|
|
}
|
|
var state_text = "";
|
|
if(submission.workflow_state == 'complete' || submission.workflow_state == 'pending_review') {
|
|
state_text = I18n.t('finished_in_duration', "finished in %{duration}", {'duration': submission.finished_in_words});
|
|
}
|
|
var data = {
|
|
attempt: submission.attempt || '--',
|
|
extra_time: submission.extra_time,
|
|
extra_attempts: submission.extra_attempts,
|
|
score: submission.kept_score
|
|
};
|
|
if(submission.attempts_left == -1) {
|
|
data.attempts_left = '--';
|
|
} else if(submission.attempts_left) {
|
|
data.attempts_left = submission.attempts_left;
|
|
}
|
|
if(submission.workflow_state != 'untaken') {
|
|
data.time = state_text;
|
|
}
|
|
$student
|
|
.fillTemplateData({data: data})
|
|
.toggleClass('extendable', submission['extendable?'])
|
|
.toggleClass('in_progress', submission.workflow_state == 'untaken')
|
|
.toggleClass('manually_unlocked', !!submission.manually_unlocked)
|
|
.attr('data-started-at', submission.started_at || '')
|
|
.attr('data-end-at', submission.end_at || '')
|
|
.data('timing', null)
|
|
.find(".unlocked").showIf(submission.manually_unlocked);
|
|
|
|
updateExtraTime($student, submission.extra_time);
|
|
},
|
|
lastUpdatedAt: "",
|
|
studentsCurrentlyTakingQuiz: false
|
|
};
|
|
|
|
$(document).ready(function(event) {
|
|
timing.initTimes();
|
|
setInterval(moderation.updateTimes, 500)
|
|
var updateErrors = 0;
|
|
var moderate_url = $(".update_url").attr('href');
|
|
moderation.lastUpdatedAt = Date.parse($(".last_updated_at").text());
|
|
var currently_updating = false;
|
|
var $updating_img = $(".reload_link img");
|
|
function updating(bool) {
|
|
currently_updating = bool;
|
|
if(bool) {
|
|
$updating_img.attr('src', $updating_img.attr('src').replace("ajax-reload.gif", "ajax-reload-animated.gif"));
|
|
} else {
|
|
$updating_img.attr('src', $updating_img.attr('src').replace("ajax-reload-animated.gif", "ajax-reload.gif"));
|
|
}
|
|
}
|
|
function updateSubmissions(repeat) {
|
|
if(currently_updating) { return; }
|
|
updating(true);
|
|
var last_updated_at = moderation.lastUpdatedAt && moderation.lastUpdatedAt.toISOString();
|
|
|
|
$.ajaxJSON($.replaceTags(moderate_url, 'update', last_updated_at), 'GET', {}, function(data) {
|
|
updating(false);
|
|
if(repeat) {
|
|
if(data.length || moderation.studentsCurrentlyTakingQuiz) {
|
|
setTimeout(function() { updateSubmissions(true); }, 60000);
|
|
} else {
|
|
setTimeout(function() { updateSubmissions(true); }, 180000);
|
|
}
|
|
}
|
|
for(var idx in data) {
|
|
moderation.updateSubmission(data[idx], true);
|
|
}
|
|
}, function(data) {
|
|
updating(false);
|
|
updateErrors++;
|
|
if(updateErrors > 5) {
|
|
$.flashMessage(I18n.t('errors.server_communication_failed', "There was a problem communicating with the server. The system will try again in five minutes, or you can reload the page"));
|
|
updateErrors = 0;
|
|
if(repeat) {
|
|
setTimeout(function() { updateSubmissions(true); }, 300000);
|
|
}
|
|
} else if(repeat) {
|
|
setTimeout(function() { updateSubmissions(true); }, 120000);
|
|
}
|
|
});
|
|
};
|
|
setTimeout(function() { updateSubmissions(true); }, 1000);
|
|
function checkChange() {
|
|
var cnt = $(".student_check:checked").length;
|
|
$("#checked_count").text(cnt);
|
|
$(".moderate_multiple_link").showIf(cnt);
|
|
}
|
|
$("#check_all").change(function() {
|
|
var isChecked = $(this).is(":checked");
|
|
$(".student_check").each(function(index, elem) {
|
|
$(elem).prop("checked", isChecked);
|
|
});
|
|
checkChange();
|
|
});
|
|
$(".student_check").change(function() {
|
|
if(!$(this).attr('checked')) {
|
|
$("#check_all").attr('checked', false);
|
|
}
|
|
checkChange();
|
|
});
|
|
|
|
$(".moderate_multiple_link").live('click', function(event) {
|
|
event.preventDefault();
|
|
var student_ids = []
|
|
var data = {};
|
|
$(".student_check:checked").each(function() {
|
|
var $student = $(this).parents(".student");
|
|
student_ids.push($(this).attr('data-id'));
|
|
var student_data = {
|
|
manually_unlocked: $student.hasClass('manually_unlocked') ? '1' : '0',
|
|
extra_attempts: parseInt($student.find(".extra_attempts").text(), 10) || "",
|
|
extra_time: parseInt($student.find(".extra_time").text(), 10) || ""
|
|
};
|
|
$.each(['manually_unlocked', 'extra_attempts', 'extra_time'], function() {
|
|
if(data[this] == null) {
|
|
data[this] = student_data[this].toString();
|
|
} else if(data[this] != student_data[this].toString()) {
|
|
data[this] = '';
|
|
}
|
|
});
|
|
});
|
|
$("#moderate_student_form").data('ids', student_ids);
|
|
$("#moderate_student_dialog h2").text(I18n.t('extensions_for_students', {'one': "Extensions for 1 Student", 'other': "Extensions for %{count} Students"}, {'count': student_ids.length}));
|
|
$("#moderate_student_form").fillFormData(data);
|
|
$("#moderate_student_dialog").dialog({
|
|
title: I18n.t('titles.student_extensions', "Student Extensions"),
|
|
width: DIALOG_WIDTH
|
|
}).fixDialogButtons();
|
|
});
|
|
|
|
$(".moderate_student_link").live('click', function(event) {
|
|
event.preventDefault();
|
|
var $student = $(this).parents(".student");
|
|
var data = {
|
|
manually_unlocked: $student.hasClass('manually_unlocked') ? '1' : '0',
|
|
extra_attempts: parseInt($student.find(".extra_attempts").text(), 10) || "",
|
|
extra_time: parseInt($student.find(".extra_time_allowed").text().replace(/[^\d]/g, ''), 10) || ""
|
|
};
|
|
var name = $student.find(".student_name").text();
|
|
$("#moderate_student_form").fillFormData(data);
|
|
$("#moderate_student_form").data('ids', [$student.attr('data-user-id')]);
|
|
$("#moderate_student_form").find("button").attr('disabled', false);
|
|
$("#moderate_student_dialog h2").text(I18n.t('extensions_for_student', "Extensions for %{student}", {'student': name}));
|
|
|
|
openModerateStudentDialog($("#moderate_student_dialog"), DIALOG_WIDTH)
|
|
});
|
|
|
|
$(".reload_link").click(function(event) {
|
|
event.preventDefault();
|
|
updateSubmissions();
|
|
});
|
|
|
|
$('#extension_extra_time')
|
|
.on('invalid:not_a_number', function(e) {
|
|
$(this).errorBox(I18n.t('errors.quiz_submission_extra_time_not_a_number', 'Extra time must be a number.'));
|
|
})
|
|
.on('invalid:greater_than', function(e) {
|
|
$(this).errorBox(I18n.t('errors.quiz_submission_extra_time_too_short', 'Extra time must be greater than 0.'));
|
|
})
|
|
.on('invalid:less_than', function(e) {
|
|
$(this).errorBox(I18n.t('errors.quiz_submission_extra_time_too_long', 'Extra time must be less than than 10080.'));
|
|
});
|
|
|
|
$('#extension_extra_attempts')
|
|
.on('invalid:not_a_number', function(e) {
|
|
$(this).errorBox(I18n.t('errors.quiz_submission_extra_attempts_not_a_number', 'Extra attempts must be a number.'));
|
|
})
|
|
.on('invalid:greater_than', function(e) {
|
|
$(this).errorBox(I18n.t('errors.quiz_submission_extra_attempts_too_short', 'Extra attempts must be greater than 0.'));
|
|
})
|
|
.on('invalid:less_than', function(e) {
|
|
$(this).errorBox(I18n.t('errors.quiz_submission_extra_attempts_too_long', 'Extra attempts must be less than than 1000.'));
|
|
});
|
|
|
|
$("#moderate_student_form").submit(function(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
var ids = $(this).data('ids');
|
|
if(ids.length == 0) { return; }
|
|
var $form = $(this);
|
|
$form.find("button").attr('disabled', true).filter(".save_button").text(I18n.t('buttons.saving', "Saving..."));
|
|
var finished = 0, errors = 0;
|
|
var formData = $(this).getFormData();
|
|
|
|
function valid(data) {
|
|
var extraAttempts = parseInt(data.extra_attempts),
|
|
extraTime = parseInt(data.extra_time),
|
|
valid = true;
|
|
|
|
if (data.extra_attempts && isNaN(extraAttempts)) {
|
|
$("#extension_extra_attempts").trigger("invalid:not_a_number");
|
|
valid = false;
|
|
} else if (extraAttempts > 1000) {
|
|
$("#extension_extra_attempts").trigger("invalid:less_than");
|
|
valid = false;
|
|
} else if (extraAttempts < 0) {
|
|
$("#extension_extra_attempts").trigger("invalid:greater_than");
|
|
valid = false;
|
|
}
|
|
|
|
if (data.extra_time && isNaN(extraTime)) {
|
|
$("#extension_extra_time").trigger("invalid:not_a_number");
|
|
valid = false;
|
|
} else if (extraTime > 10080) { // 1 week
|
|
$("#extension_extra_time").trigger("invalid:less_than");
|
|
valid = false;
|
|
} else if (extraTime < 0) {
|
|
$("#extension_extra_time").trigger("invalid:greater_than");
|
|
valid = false;
|
|
}
|
|
return valid;
|
|
}
|
|
if (!valid(formData)) {
|
|
$form.find("button").attr('disabled', false).filter(".save_button").text(I18n.t('buttons.save', "Save"));
|
|
return;
|
|
}
|
|
|
|
function checkIfFinished() {
|
|
if(finished >= ids.length) {
|
|
if(errors > 0) {
|
|
if(ids.length == 1) {
|
|
$form.find("button").attr('disabled', false).filter(".save_button").text(I18n.t('buttons.save_failed', "Save Failed, please try again"));
|
|
} else {
|
|
$form.find("button").attr('disabled', false).filter(".save_button").text(I18n.t('buttons.save_failed_n_updates_lost', "Save Failed, %{n} Students were not updated", {'n': errors}));
|
|
}
|
|
} else {
|
|
$form.find("button").attr('disabled', false).filter(".save_button").text(I18n.t('buttons.save', "Save"));
|
|
$("#moderate_student_dialog").dialog('close');
|
|
}
|
|
}
|
|
};
|
|
for(var idx in ids) {
|
|
var id = ids[idx];
|
|
var url = $.replaceTags($(".extension_url").attr('href'), 'user_id', id);
|
|
$.ajaxJSON(url, 'POST', formData, function(data) {
|
|
finished++;
|
|
moderation.updateSubmission(data);
|
|
checkIfFinished();
|
|
}, function(data) {
|
|
finished++;
|
|
errors++;
|
|
checkIfFinished();
|
|
});
|
|
}
|
|
});
|
|
$("#moderate_student_dialog").find('.cancel_button').click(function() {
|
|
$("#moderate_student_dialog").dialog('close');
|
|
});
|
|
$(".extend_time_link").live('click', function(event) {
|
|
event.preventDefault();
|
|
var $row = $(event.target).parents(".student");
|
|
var end_at = $.datetimeString($row.attr('data-end-at'));
|
|
var started_at = $.datetimeString($row.attr('data-started-at'));
|
|
var $dialog = $("#extend_time_dialog");
|
|
$dialog.data('row', $row);
|
|
$dialog.fillTemplateData({
|
|
data: {
|
|
end_at: end_at,
|
|
started_at: started_at
|
|
}
|
|
});
|
|
$dialog.find("button").attr('disabled', false);
|
|
$dialog.dialog({
|
|
title: I18n.t('titles.extend_quiz_time', "Extend Quiz Time"),
|
|
width: DIALOG_WIDTH
|
|
}).fixDialogButtons();
|
|
});
|
|
$("#extend_time_dialog").find(".cancel_button").click(function() {
|
|
$("#extend_time_dialog").dialog('close');
|
|
}).end().find(".save_button").click(function() {
|
|
var $dialog = $("#extend_time_dialog");
|
|
var data = $dialog.getFormData();
|
|
var params = {};
|
|
data.time = parseInt(data.time, 10) || 0;
|
|
if(data.time <= 0) { return; }
|
|
if(data.time_type == 'extend_from_now' && data.time < $dialog.data('row').data('minutes_left')) {
|
|
var result = confirm(I18n.t('confirms.taking_time_away', "That would be less time than the student currently has. Continue anyway?"));
|
|
if(!result) { return; }
|
|
}
|
|
params[data.time_type] = data.time;
|
|
$dialog.find("button").attr('disabled', true).filter(".save_button").text(I18n.t('buttons.extending_time', "Extending Time..."));
|
|
var url = $.replaceTags($(".extension_url").attr('href'), 'user_id', $dialog.data('row').attr('data-user-id'));
|
|
$.ajaxJSON(url, 'POST', params, function(data) {
|
|
$dialog.find("button").attr('disabled', false).filter(".save_button").text(I18n.t('buttons.extend_time', "Extend Time"));
|
|
moderation.updateSubmission(data);
|
|
$dialog.dialog('close');
|
|
}, function(data) {
|
|
$dialog.find("button").attr('disabled', false).filter(".save_button").text(I18n.t('buttons.time_extension_failed', "Extend Time Failed, please try again"));
|
|
});
|
|
});
|
|
|
|
var outstanding = {
|
|
init: function(event) {
|
|
event.preventDefault();
|
|
this.$container = $(".child_container")
|
|
|
|
this.showDialog();
|
|
this.showResultsList(this.data);
|
|
},
|
|
fetchData: function () {
|
|
var indexUrl = $(".outstanding_index_url").attr('href');
|
|
$.ajaxJSON(indexUrl, "GET", null, this.storeDataAndShowAlert.bind(this));
|
|
},
|
|
storeDataAndShowAlert: function(data) {
|
|
if (data.quiz_submissions.length > 0) {
|
|
this.toggleAlertHeader();
|
|
this.data = data;
|
|
}
|
|
},
|
|
toggleAlertHeader: function() {
|
|
$(".alert").toggle();
|
|
},
|
|
showResultsList: function(data) {
|
|
this.adjustDialogHeight(data);
|
|
$("#autosubmit_content_description_things_to_do").show();
|
|
|
|
this.buildResultList(data);
|
|
$("#autosubmit_form_submit_btn").prop('disabled', false);
|
|
},
|
|
adjustDialogHeight: function(data) {
|
|
var height = 80;
|
|
height = height + (data['quiz_submissions'].length * 29);
|
|
if (height > 270) {
|
|
height = 270;
|
|
}
|
|
this.dialog.animate({height: height + 'px'});
|
|
},
|
|
buildResultList: function(data) {
|
|
$.each(data["quiz_submissions"], function (index, qs) {
|
|
clone = $(".example_autosubmit_row").clone()
|
|
.removeClass("example_autosubmit_row")
|
|
.appendTo(".outstanding_submissions_list").show();
|
|
clone.children("input").val(qs.id);
|
|
clone.find("input").prop("checked", true)
|
|
.attr('id', "id_" + index);
|
|
clone.children("label").attr("for", 'id_'+index)
|
|
.text(data["users"][index].sortable_name);
|
|
clone.show();
|
|
});
|
|
},
|
|
submitOutstandings: function () {
|
|
var gradeUrl = $(".outstanding_grade_url").attr('href');
|
|
var ids = this.$container.find("input:checked").map(function extractQSIds () {
|
|
return this.value;
|
|
}).get()
|
|
var json = {'quiz_submission_ids': ids };
|
|
$.ajaxJSON(gradeUrl, "POST", json, function successReporting(data,xhr) {
|
|
if (xhr.status === 204) {
|
|
if (ids.length == this.data["users"].length) {
|
|
this.toggleAlertHeader();
|
|
}
|
|
$.flashMessage("Successfully graded outstanding quizzes")
|
|
this.closeDialog();
|
|
updateSubmissions();
|
|
}
|
|
}.bind(this));
|
|
},
|
|
cleanUpEventListeners: function () {
|
|
$('#autosubmit_form_cancel_btn').off();
|
|
$('#autosubmit_form_submit_btn').off();
|
|
},
|
|
closeDialog: function() {
|
|
$("#autosubmit_content_description_things_to_do").hide();
|
|
$("#autosubmit_content_description_all_done").hide();
|
|
$(".autosubmit_data_row").not(".example_autosubmit_row").remove();
|
|
this.cleanUpEventListeners();
|
|
this.dialog.dialog('close');
|
|
},
|
|
setFocusOnLoad: function(event, ui) {
|
|
$("#autosubmit_form").focus();
|
|
},
|
|
showDialog: function() {
|
|
this.dialog = $("#autosubmit_form").dialog({
|
|
title: I18n.t('titles.autosubmit_dialog', "Outstanding Quiz Submissions"),
|
|
modal: true,
|
|
width: DIALOG_WIDTH,
|
|
height: 200,
|
|
close: this.closeDialog.bind(this)
|
|
}).dialog('open').fixDialogButtons();
|
|
|
|
// Set up button behaviors
|
|
$('#autosubmit_form_cancel_btn').on('click keyclick', function() {
|
|
this.closeDialog();
|
|
}.bind(this));
|
|
$("#autosubmit_form_submit_btn").on("click keyclick", this.submitOutstandings.bind(this));
|
|
|
|
this.setFocusOnLoad();
|
|
}
|
|
};
|
|
outstanding.fetchData();
|
|
$("#check_outstanding").click(outstanding.init.bind(outstanding));
|
|
});
|
|
});
|