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
413 lines (390 loc) • 20 kB
JavaScript
(function () {
var utilClass = function (sys) {
this.sys = sys;
};
utilClass.prototype.getUtils = function () {
var sys = this.sys;
return {
getCommenterLanguages: function(commenterKey,callback) {
var sql = "SELECT adminID,lang FROM admin LEFT JOIN adminLanguages USING (adminID) WHERE adminKey=?;"
sys.db.all(sql,[commenterKey],function(err,rows){
if (err||!rows) {return oops(response,err,'class/readquizzes(1)')};
var commenterLangs = [];
for (var i=0,ilen=rows.length;i<ilen;i++) {
var commenter = rows[i];
commenterID = commenter.adminID;
if (commenter.lang) {
commenterLangs.push(commenter.lang);
}
}
callback(commenterLangs);
});
},
getMistakenChoices: function (locale,commenterLangs,classID,quizNumber) {
if (commenterLangs) {
if (commenterLangs.length) {
if (commenterLangs.indexOf(locale) > -1) {
commenterLangs = false;
}
} else {
commenterLangs = false;
}
}
function getSQL() {
var sql = "SELECT classID,"
+ "quizNumber,"
+ "questions.questionNumber,"
+ "choices.choice AS wrong,"
+ "choices.stringID,"
+ "choices.choiceID,"
+ "quizzes.examName AS mistakeExamName,"
+ returnColumns(commenterLangs) + " "
+ "FROM quizzes "
+ "JOIN questions USING(quizID) "
+ "JOIN choices USING(questionID) "
+ "JOIN answers USING(questionID,choice) "
+ "JOIN students USING(studentID) "
+ "LEFT JOIN ("
+ "SELECT choiceID "
+ "FROM quizzes "
+ "JOIN questions USING(quizID) "
+ "JOIN choices USING(questionID) "
+ "JOIN answers USING(questionID,choice) "
+ "JOIN comments USING(choiceID)"
+ "JOIN admin USING(adminID) "
+ "JOIN adminLanguages USING(adminID) "
+ "WHERE " + classCondition() + quizCondition() + "NOT answers.choice=questions.correct AND lang=? " // instance lang
+ ") INSTANCE USING(choiceID) "
+ otherLanguageJoin(commenterLangs)
+ "LEFT JOIN rulesToChoices USING(choiceID) "
+ "WHERE " + classCondition() + quizCondition() + "NOT answers.choice=questions.correct " + innerSelectConditions(commenterLangs,quizNumber) + " "
+ "GROUP BY quizzes.quizNumber, questions.questionNumber, choices.choice ";
function classCondition() {
if (classID) {
return "classID=? AND ";
} else {
return "";
}
}
function quizCondition() {
if (quizNumber) {
return "quizNumber=? AND ";
} else {
return "";
}
}
function returnColumns(commenterLangs) {
var sub = {
locale: "CASE WHEN INSTANCE.choiceID IS NULL THEN 1 ELSE NULL END AS commentNeeded",
langs: "CASE WHEN INSTANCE.choiceID IS NOT NULL AND COUNT(OTHER.choiceID) < " + commenterLangs.length + " THEN 1 ELSE NULL END AS commentNeeded"
}
return commenterLangs ? sub.langs : sub.locale;
}
function otherLanguageJoin(commenterLangs) {
var sub = {
locale: '',
langs: "LEFT JOIN ("
+ "SELECT choiceID "
+ "FROM quizzes "
+ "JOIN questions USING(quizID) "
+ "JOIN choices USING(questionID) "
+ "JOIN answers USING(questionID,choice) "
+ "JOIN comments USING(choiceID) "
+ "JOIN admin USING(adminID) "
+ "JOIN adminLanguages USING(adminID) "
+ "WHERE " + classCondition() + quizCondition() + "NOT answers.choice=questions.correct AND lang IN (" + langSQL() + ") "
+ ") OTHER USING(choiceID) "
}
return commenterLangs ? sub.langs : sub.locale;
}
function innerSelectConditions(commenterLangs,quizNumber) {
sub = {
locale: "AND INSTANCE.choiceID IS NULL AND rulesToChoices.choiceID IS NULL ",
langs: "AND students.lang IN (" + langSQL() + ") AND INSTANCE.choiceID IS NOT NULL AND OTHER.choiceID IS NULL "
}
var ret;
if (quizNumber) {
if (commenterLangs) {
ret = "AND students.lang IN (" + langSQL() + ") "
} else {
ret = "";
}
} else {
if (commenterLangs) {
ret = sub.langs;
} else {
ret = sub.locale;
}
}
return ret;
}
function langSQL () {
var ret = [];
for (var i=0,ilen=commenterLangs.length;i<ilen;i++) {
ret.push('?');
}
return ret.join(',');
};
return sql;
};
function getSqlVars () {
var sqlVars;
if (!commenterLangs) {
// Instance lang mode
sqlVars = [];
classID ? sqlVars.push(classID) : null;
quizNumber ? sqlVars.push(quizNumber) : null;
sqlVars.push(locale);
classID ? sqlVars.push(classID) : null;
quizNumber ? sqlVars.push(quizNumber) : null;
} else {
// Commenter lang mode
sqlVars = [];
classID ? sqlVars.push(classID) : null;
quizNumber ? sqlVars.push(quizNumber) : null;
sqlVars.push(locale);
classID ? sqlVars.push(classID) : null;
quizNumber ? sqlVars.push(quizNumber) : null;
for (var i=0,ilen=commenterLangs.length;i<ilen;i++) {
sqlVars.push(commenterLangs[i]);
}
classID ? sqlVars.push(classID) : null;
quizNumber ? sqlVars.push(quizNumber) : null;
for (var i=0,ilen=commenterLangs.length;i<ilen;i++) {
sqlVars.push(commenterLangs[i]);
}
}
return sqlVars;
}
return { sql: getSQL(), vars: getSqlVars() }
},
writeQuestion: function (response,classID,quizNumber,questionNumber,data) {
var oops = this.apiError;
var sys = this.sys;
var qObj = {
haveStringId:{},
needStringId:{},
needStringIdList:[]
};
beginTransaction();
function beginTransaction () {
sys.db.run('BEGIN TRANSACTION',function(err){
checkQuiz();
})
};
// Check for existence of quiz, create if necessary, get ID
function checkQuiz () {
var sql = 'SELECT quizID FROM quizzes WHERE classID=? AND quizNumber=?;';
sys.db.get(sql,[classID,quizNumber],function(err,row){
if (err) {return oops(response,err,'quiz/writeonequestion(1)')}
if (row && row.quizID) {
qObj.quizID = row.quizID;
checkStrings(0,5);
} else {
createQuiz();
}
});
}
function createQuiz () {
var sql = 'INSERT INTO quizzes VALUES(NULL,?,?,0,NULL,NULL);'
sys.db.run(sql,[classID,quizNumber],function(err){
if (err) {return oops(response,err,'quiz/writeonequestion(2)')};
qObj.quizID = this.lastID;
checkStrings(0,5);
});
};
// Check for existence of strings, create if necessary, save IDs
function checkStrings (pos,limit) {
if (pos === limit) {
createStrings(0,qObj.needStringIdList.length);
return;
}
var sql = 'SELECT stringID FROM strings WHERE string=?;'
var string = strings[pos];
sys.db.get(sql,[string],function(err,row){
if (err) {return oops(response,err,'quiz/writeonequestion(3)')};
if (row && row.stringID) {
qObj.haveStringId[pos] = row.stringID;
} else {
qObj.needStringId[pos] = strings[pos];
qObj.needStringIdList.push(pos);
}
checkStrings(pos+1,limit);
});
};
function createStrings (pos,limit) {
if (pos === limit) {
checkQuestionNumber();
return;
}
var sql = 'INSERT INTO strings VALUES (NULL,?)';
var key = qObj.needStringIdList[pos];
var string = qObj.needStringId[key];
sys.db.run(sql,[string],function(err){
if (err) {return oops(response,err,'quiz/writeonequestion(4)')};
qObj.haveStringId[key] = this.lastID;
createStrings(pos+1,limit);
});
};
// Check for existence of question, create if necessary, save ID
function checkQuestionNumber () {
if (questionNumber == 0) {
makeQuestionNumber();
} else {
qObj.questionNumber = questionNumber;
checkQuestion();
}
};
function makeQuestionNumber () {
var sql = 'SELECT MAX(questionNumber) AS questionMax FROM quizzes JOIN questions USING(quizID) WHERE quizID=?'
sys.db.get(sql,[qObj.quizID],function(err,row){
if (err) {return oops(response,err,'quiz/writeonequestion(5)')};
if (row && row.questionMax) {
qObj.questionNumber = (row.questionMax + 1);
} else {
qObj.questionNumber = 1;
}
createQuestion();
});
};
function createQuestion () {
// Jumps to checkChoices
var sql = 'INSERT INTO questions VALUES (NULL,?,?,?,?);'
var quizID = qObj.quizID;
var questionNumber = qObj.questionNumber;
var correct = data.correct;
var rubricID = qObj.haveStringId[4];
sys.db.run(sql,[quizID,questionNumber,correct,rubricID],function (err) {
if (err) {return oops(response,err,'quiz/writeonequestion(6)')};
qObj.questionID = this.lastID;
writeChoices(0,4);
});
};
function checkQuestion () {
// To updateQuestion
var sql = 'SELECT questionID FROM questions WHERE quizID=? AND questionNumber=?;';
var quizID = qObj.quizID;
var questionNumber = qObj.questionNumber;
sys.db.get(sql,[quizID,questionNumber],function(err,row){
if (err||!row) {return oops(response,err,'quiz/writeonequestion(7)')};
qObj.questionID = row.questionID;
updateQuestion();
});
};
function updateQuestion () {
// To checkChoices
var sql = 'UPDATE questions SET stringID=?,correct=? WHERE questionID=?;';
var stringID = qObj.haveStringId[4];
var correct = data.correct;
var questionID = qObj.questionID;
sys.db.run(sql,[stringID,correct,questionID],function(err){
if (err) {return oops(response,err,'quiz/writeonequestion(8)')};
writeChoices(0,4);
});
};
// Insert or replace is good enough for choices
function writeChoices(pos,limit) {
if (pos === limit) {
endTransaction();
return;
}
var sql = 'INSERT OR REPLACE INTO choices VALUES (NULL,?,?,?)';
var questionID = qObj.questionID;
var choice = pos;
var stringID = qObj.haveStringId[pos];
sys.db.run(sql,[questionID,choice,stringID],function(err){
if (err) {return oops(response,err,'quiz/writeonequestion(9)')};
writeChoices(pos+1,limit);
});
};
function endTransaction () {
sys.db.run('END TRANSACTION',function(){
response.writeHead(200, {'Content-Type': 'application/json'});
response.end(JSON.stringify(qObj.questionNumber));
})
};
},
getClassMemberships: function (params,request,response) {
var oops = this.apiError;
var classID = params.classid;
var sql = "SELECT s.name,s.studentID AS studentID,m.studentID AS enroled,sh.studentID AS showing,privacy "
+ "FROM students AS s "
+ "LEFT JOIN ("
+ "SELECT classID,studentID "
+ "FROM memberships "
+ "WHERE classID=?"
+ ") AS m ON m.studentID=s.studentID "
+ 'LEFT JOIN ('
+ 'SELECT studentID FROM showing AS mysh '
+ 'WHERE mysh.classID=?'
+ ') AS sh ON sh.studentID=s.studentID'
sys.db.all(sql,[classID,classID],function(err,rows) {
if (err||!rows) {return oops(response,err,'getClassMemberships()')};
var rowsets = [[],[]];
for (var i=0,ilen=rows.length;i<ilen;i+=1) {
var row = rows[i];
var dataitem = {name:row.name,studentid:row.studentID,privacy:row.privacy};
if (row.enroled) {
rowsets[0].push(dataitem);
} else if (row.showing) {
rowsets[1].push(dataitem);
}
}
response.writeHead(200, {'Content-Type': 'application/json'});
response.end(JSON.stringify(rowsets));
});
},
apiError: function (response,err,str,transaction) {
if (str) {
str = " in " + str;
} else {
str = "";
}
if (err) {
console.log("Error" + str + ": " + err)
} else {
console.log("Warning" + str + ": no results returned");
}
sys.db.run('ROLLBACK TRANSACTION',function(err){
response.writeHead(500, {'Content-Type': 'text/plain'});
response.end('server-side error or warning');
});
},
getChoices: function (response,obj,questionID,callback) {
var oops = this.apiError;
var sql = 'SELECT questionNumber,one.string AS one,two.string AS two,three.string AS three,four.string AS four '
+ 'FROM questions '
+ 'JOIN ('
+ 'SELECT questionID,string '
+ 'FROM choices '
+ 'NATURAL JOIN strings '
+ 'WHERE questionID=? AND choice=0'
+ ') AS one ON one.questionID=questions.questionID '
+ 'JOIN ('
+ 'SELECT questionID,string '
+ 'FROM choices '
+ 'NATURAL JOIN strings '
+ 'WHERE questionID=? AND choice=1'
+ ') AS two ON two.questionID=questions.questionID '
+ 'JOIN ('
+ 'SELECT questionID,string '
+ 'FROM choices '
+ 'NATURAL JOIN strings '
+ 'WHERE questionID=? AND choice=2'
+ ') AS three ON three.questionID=questions.questionID '
+ 'JOIN ('
+ 'SELECT questionID,string '
+ 'FROM choices '
+ 'NATURAL JOIN strings '
+ 'WHERE questionID=? AND choice=3'
+ ') AS four ON four.questionID=questions.questionID '
+ 'WHERE questions.questionID=?'
sys.db.get(sql,[questionID,questionID,questionID,questionID,questionID],function(err,row){
if (err||!row) {return oops(response,err,'getChoices()')};
callback(obj,row);
});
}
}
}
exports.utilClass = utilClass;
})();