quizzer
Version:
Quizzer is a webserver for collaborative writing lab support. Based on a _fail early, fail often? approach to written language, the tool is particularly suited to second-language learners. The workflow (essay - error - quiz - exam) treats mistakes as an o
519 lines (488 loc) • 17.8 kB
JavaScript
function showStragglers () {
buttonMode('non-submitters-display');
};
function restoreMain () {
buttonMode('main-display');
};
function buildQuizList (rows) {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
if (!rows) {
// if rows is nil, call the server.
var rows = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=readquizzes'
, {
classid:classID
}
);
if (false === rows) return;
}
rows.sort(function (a,b) {
a = parseInt(a.number,10);
b = parseInt(b.number,10);
if (a>b) {
return 1;
} else if (a<b) {
return -1;
} else {
return 0;
}
});
// Delete children from container
var container = document.getElementById('quiz-list');
for (var i=0,ilen=container.childNodes.length;i<ilen;i+=1) {
container.removeChild(container.childNodes[0]);
}
// Rebuild container content
if (rows.length === 0) {
rows = [{number:1,isnew:-1}];
}
for (var i=0,ilen=rows.length;i<ilen;i+=1) {
var row = rows[i];
var nameText;
if (row.name) {
nameText = document.createTextNode(row.name);
} else {
nameText = document.createTextNode("Quiz "+row.number);
}
var idText = document.createTextNode(row.number);
var tr = document.createElement('tr');
var nameAnchor = document.createElement('a');
var nameTD = document.createElement('td');
var idTD = document.createElement('td');
nameAnchor.appendChild(nameText);
// XXX Think about this one.
nameAnchor.setAttribute('href', fixPath('/?admin=' + adminID + '&page=quiz&classid=' + classID + '&quizno=' + rows[i].number));
nameTD.appendChild(nameAnchor);
tr.appendChild(nameTD);
idTD.appendChild(idText);
idTD.style.display = 'none';
tr.appendChild(idTD)
if (rows[i].isnew) {
var newmarkText;
if (rows[i].isnew === -1) {
newmarkText = document.createTextNode('[new]');
} else {
newmarkText = document.createTextNode('(' + rows[i].isnew + ')');
}
var newmarkTD = document.createElement('td');
newmarkTD.appendChild(newmarkText);
tr.appendChild(newmarkTD);
}
container.appendChild(tr);
}
}
function buildMemberLists(rowsets) {
if (!rowsets) {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
rowsets = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=readmembers'
, {
classid:classID
}
);
if (false === rowsets) return;
}
// Clear lists and rewrite
var memberContainer = document.getElementById("members");
var nonmemberContainer = document.getElementById("non-members");
var listContainers = [memberContainer, nonmemberContainer];
for (var i=0,ilen=listContainers.length;i<ilen;i+=1) {
rowsets[i].sort(function (a,b) {
return a.name.localeCompare(b.name);
});
for (var j=0,jlen=listContainers[i].childNodes.length;j<jlen;j+=1) {
listContainers[i].removeChild(listContainers[i].childNodes[0]);
}
for (var j=0,jlen=rowsets[i].length;j<jlen;j+=1) {
var entry = document.createElement('div');
if (i === 1) {
entry.setAttribute("class","non-member-entry");
entry.innerHTML = '<div class="non-member-del-button"><input type="button" value="Del" class="button-small i18n" name="value-delete" onclick="confirmDelete(this,\'executeNonMemberRemoval\');"/></div>'
}
var checkBox = document.createElement('input');
checkBox.setAttribute('type', 'checkbox');
checkBox.setAttribute('value', rowsets[i][j].studentid);
entry.appendChild(checkBox);
var entryText = document.createElement('span');
entryText.innerHTML = rowsets[i][j].name;
if (rowsets[i][j].privacy > 0) {
entryText.classList.add('external-member');
}
entry.appendChild(entryText);
listContainers[i].appendChild(entry);
}
}
}
function executeNonMemberRemoval (node) {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var studentID = node.parentNode.nextSibling.value;
var ignore = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=removenonmember'
, {
classid:classID,
studentid:studentID
}
);
buildMemberLists();
}
function addMembers () {
var ret = [];
var nonMembers = document.getElementById('non-members');
for (var i=0,ilen=nonMembers.childNodes.length;i<ilen;i+=1) {
if (nonMembers.childNodes[i].childNodes[1].checked) {
ret.push(nonMembers.childNodes[i].childNodes[1].value);
}
}
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var rowsets = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=addmembers'
, {
classid:classID,
addmembers:ret
}
);
if (false === rowsets) return;
buildMemberLists(rowsets);
buildQuizList();
}
function removeMembers () {
var ret = [];
var members = document.getElementById('members');
for (var i=0,ilen=members.childNodes.length;i<ilen;i+=1) {
if (members.childNodes[i].childNodes[0].checked) {
ret.push(members.childNodes[i].childNodes[0].value);
}
}
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var rowsets = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=removemembers'
, {
classid:classID,
removemembers:ret
}
);
if (false === rowsets) return;
buildMemberLists(rowsets);
buildQuizList();
}
function setupExam () {
buttonMode('create-exam');
};
function createExam () {
var examTitle = document.getElementById('exam-title').value;
var examDate = document.getElementById('exam-date').value;
var examNumberOfQuestions = document.getElementById('exam-number-of-questions').value;
if (!examTitle || !examDate || !examNumberOfQuestions) {
buttonMode('default');
return;
} else {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var result = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=createexam'
, {
classid:classID,
examtitle:examTitle,
examdate:examDate,
examnumberofquestions:examNumberOfQuestions
}
);
if (false === result) return;
buttonMode('default');
buildQuizList();
}
};
function showNonSubmitters () {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var result = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=getnonsubmitters'
, {
classid:classID
}
);
if (false === result) return;
var nonSubmittersList = document.getElementById('non-submitters-list');
for (var i=0,ilen=nonSubmittersList.childNodes.length;i<ilen;i+=1) {
nonSubmittersList.removeChild(nonSubmittersList.childNodes[0]);
}
var colspec = ['name','quizzes','email'];
for (var i=0,ilen=result.length;i<ilen;i+=1) {
var nonsubTR = document.createElement('tr');
if (i % 2) {
nonsubTR.setAttribute('class','even');
} else {
nonsubTR.setAttribute('class','odd');
}
var line = result[i];
for (var j=0,jlen=3;j<jlen;j+=1) {
var node = document.createElement('td');
if (j === 2) {
node.setAttribute('class', 'email');
}
node.innerHTML = line[colspec[j]];
nonsubTR.appendChild(node);
}
nonSubmittersList.appendChild(nonsubTR);
}
};
function showProfile () {
generateProfileChart();
buttonMode('profile-display');
}
function generateProfileChart() {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var graphData = apiRequest(
'/?admin='
+ adminID
+ '&page=class'
+ '&cmd=getprofiledata'
, {
classid:classID
}
);
if (false === graphData) return;
// Okay, let's get it straight this time.
//
// We will return values as percentages of the class in each of the five quintiles.
//
// Split it into two halves, early and late.
//var midPoint = ~~(graphData.length/2);
//var slicePos = [[0,midPoint],[midPoint,graphData.length]];
var responderTotal = [0,0];
var cohortNormalizationFactor = [1,1];
var quintileDataSets = [];
var respondingIDs = {};
var numbers = [];
for (var i=0,ilen=2;i<ilen;i++) {
//
// For each half ...
var rawdata = graphData[i];
// Get total questions answered and total correct for each student
var dataByStudent = {};
var maxAnswers = 0;
var studentID;
for (var j=0,jlen=rawdata.length;j<jlen;j+=1) {
studentID = rawdata[j].studentID;
var correct = rawdata[j].correct;
if (!dataByStudent[studentID]) {
dataByStudent[studentID] = {
answers:0,
correct:0
};
responderTotal[i] += 1;
respondingIDs[studentID] = true;
}
dataByStudent[studentID].answers += 1;
if (correct) {
dataByStudent[studentID].correct += 1;
}
// Watch for the max
if (dataByStudent[studentID].answers > maxAnswers) {
maxAnswers = dataByStudent[studentID].answers;
}
}
for (var studentID in dataByStudent) {
var studentData = dataByStudent[studentID];
// Get the overall percentage of correct answers for each student.
studentData.percentage = (studentData.correct*100/studentData.answers);
// Calculate a weight for each student by total questions answered (highest responder = 1.0)
studentData.weight = (studentData.answers/maxAnswers);
}
// Divide into quintile groups
var quintileData = {0:[],1:[],2:[],3:[],4:[]};
for (studentID in dataByStudent) {
var student = dataByStudent[studentID];
if (student.percentage === 100) {
// Otherwise 100% correct snaps to zero
quintileData[4].push(student);
} else {
var quintpos = parseInt((student.percentage/20) % 5,10);
quintileData[quintpos].push(student);
}
}
quintileDataSets.push(quintileData);
}
// Get total number of responders
studentCount = 0;
for (var id in respondingIDs) {
studentCount += 1;
}
// Set cohort normalization factor
var curvename;
if (responderTotal[0] > responderTotal[1]) {
cohortNormalizationFactor[1] = responderTotal[0]/responderTotal[1];
curvename = 'unshaded';
} else if (responderTotal[0] < responderTotal[1]) {
cohortNormalizationFactor[0] = responderTotal[1]/responderTotal[0];
curvename = 'shaded';
}
for (var i=0,ilen=2;i<ilen;i++) {
var quintileData = quintileDataSets[i];
// Take a *weighted* total of the students in each quintile, to avoid distortions from low-frequency responders.
numbers[i] = [{x:0,y:0}];
for (var j=0,jlen=5;j<jlen;j++) {
var quintData = quintileData[j];
var quint = {
x:((20*j)+10),
y:0
};
for (var k=0,klen=quintData.length;k<klen;k++) {
//quint.y += quintData[k].weight;
// Normalized, unweighted
quint.y += cohortNormalizationFactor[i];
// Normalized, weighted
//quint.y += (quintData[k].weight * cohortNormalizationFactor[i]);
}
numbers[i].push(quint);
}
numbers[i].push({x:100,y:0});
}
var data = {
xScale: 'linear',
yScale: 'linear',
type: 'line-dotted',
main: [
{
className: '.pizza',
data: numbers[0]
}
],
comp: [
{
className: '.pizza',
type: 'line-dotted',
data: numbers[1]
}
]
}
var opts = {};
var myChart = new xChart('line', data, '#profile-chart', opts);
}
function buttonMode (mode) {
var backButton = document.getElementById('back-button');
var setupButton = document.getElementById('exam-setup');
var createButton = document.getElementById('exam-create');
var examBoxes = document.getElementById('exam-boxes');
var mainDisplayButton = document.getElementById('main-display-button');
var nonSubmittersButton = document.getElementById('non-submitters-button');
var nonSubmittersDisplay = document.getElementsByClassName('non-submitters-display');
var profileButton = document.getElementById('class-profile-button');
if (mode === 'create-exam') {
setupButton.style.display = "none";
createButton.style.display = "inline";
examBoxes.style.display = "inline";
nonSubmittersButton.style.display = 'none';
profileButton.style.display = 'none';
} else if (mode === 'non-submitters-display') {
backButton.disabled = true;
setupButton.style.display = 'none';
setupButton.disabled = true;
createButton.style.display = 'none';
examBoxes.style.display = 'none';
mainDisplayButton.style.display = 'inline';
hideRevealMainDisplay('none');
for (var i=0,ilen=nonSubmittersDisplay.length;i<ilen;i+=1) {
nonSubmittersDisplay[i].style.display = 'block';
}
profileButton.style.display = 'none';
nonSubmittersButton.style.display = 'none';
showNonSubmitters();
} else if (mode === 'profile-display') {
backButton.disabled = true;
mainDisplayButton.style.display = 'inline';
nonSubmittersButton.style.display = 'none';
profileButton.style.display = 'none';
setupButton.style.display = "none";
hideRevealMainDisplay('none');
hideRevealProfileDisplay('block');
} else if (mode === 'main-display') {
backButton.disabled = false;
setupButton.style.display = 'inline';
setupButton.disabled = false;
createButton.style.display = 'none';
hideRevealProfileDisplay('none');
examBoxes.style.display = 'none';
mainDisplayButton.style.display = 'none';
hideRevealMainDisplay('block');
for (var i=0,ilen=nonSubmittersDisplay.length;i<ilen;i+=1) {
nonSubmittersDisplay[i].style.display = 'none';
}
nonSubmittersButton.style.display = 'inline';
profileButton.style.display = 'inline';
} else {
backButton.disabled = false;
setupButton.style.display = "inline";
createButton.style.display = "none";
examBoxes.style.display = "none";
document.getElementById('exam-title').value = '';
document.getElementById('exam-date').value = '';
document.getElementById('exam-number-of-questions').value = '';
nonSubmittersButton.style.display = 'inline';
profileButton.style.display = 'inline';
}
}
function hideRevealMainDisplay (arg) {
var mainDisplay = document.getElementsByClassName('main-display');
for (var i=0,ilen=mainDisplay.length;i<ilen;i+=1) {
mainDisplay[i].style.display = arg;
}
}
function hideRevealProfileDisplay (arg) {
var profileDisplay = document.getElementsByClassName('class-profile-display');
for (var i=0,ilen=profileDisplay.length;i<ilen;i+=1) {
profileDisplay[i].style.display = arg;
}
}
function setUploadURL () {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var uploadURL = fixPath(
'?admin='
+ adminID
+ '&page=class'
+ '&cmd=uploadstudentlist'
+ '&classid='
+ classID
);
var classRegistrationWidget = document.getElementById('class-registration-widget');
classRegistrationWidget.setAttribute('action',uploadURL);
};
function downloadClassList () {
var adminID = getParameterByName('admin');
var classID = getParameterByName('classid');
var downloadFrame = document.getElementById('download-frame');
downloadFrame.src = fixPath('?admin='
+ adminID
+ '&page=class'
+ '&cmd=downloadcsv'
+ '&classid='
+ classID);
};