UNPKG

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

426 lines (402 loc) 17.6 kB
function clearContainer (container) { for (var i=0,ilen=container.childNodes.length;i<ilen;i+=1) { container.removeChild(container.childNodes[0]); } } var commenterInfo = {}; var quizMistakes; function showMistakes () { var commenterKey = getParameterByName('commenter'); var classID = getParameterByName('classid'); var quizNumber = getParameterByName('quizno'); quizMistakes = apiRequest( '/?commenter=' + commenterKey +'&page=quiz&cmd=quizmistakes' + '&classid=' + classID + '&quizno=' + quizNumber); if (false === quizMistakes) return; // Empty the container var container = document.getElementById('quiz-mistakes'); clearContainer(container); // Get name of commenter from return commenterInfo.commenter = quizMistakes.commenter; commenterInfo.commenterID = quizMistakes.commenterID; // For each mistake ... for (var i=0,ilen=quizMistakes.mistakes.length;i<ilen;i+=1) { var mistake = quizMistakes.mistakes[i]; var mistakeDiv = document.createElement('div'); mistakeDiv.setAttribute('id', 'mistake-' + mistake.questionNumber + '-' + mistake.wrongChoice); var rubricText = markdown(mistake.rubric); var correctText = markdown(mistake.correct); var langBubbles = ''; if (mistake.langs) { var langs = mistake.langs.split(','); langBubbles = ' <div class="language-bubble">' + langs.join('</div> <div class="language-bubble">') + '</div>'; } var wrongText = markdown(langBubbles + mistake.wrong); var buttonMode = {edit:'none',comment:'inline',save:'none'}; if (mistake.hasCommenterComment) { buttonMode = {edit:'inline',comment:'none',save:'none'}; } mistakeDiv.innerHTML = '<div class="answer-pair"><div class="rubric">' + rubricText + '</div>' + '<div class="right-answer">' + correctText + '</div>' + '<div class="wrong-answer">' + wrongText + '</div></div>' + '<div class="button-bold">' + '<input type="button" class="button i18n" ' + 'id="comment-button-' + mistake.questionNumber + '-' + mistake.wrongChoice + '" ' + 'style="display:' + buttonMode.comment + '" name="value-comment" value="Comment" ' + 'onclick="newComment(this,\'comment-' + commenterInfo.commenterID + '-' + mistake.questionNumber + '-' + mistake.wrongChoice + '\')"' +'/>' + '<input type="button" class="button i18n" ' + 'id="edit-button-' + mistake.questionNumber + '-' + mistake.wrongChoice + '" ' + 'style="display:' + buttonMode.edit + '" name="value-edit" value="Edit" ' + 'onclick="openComment(\'comment-' + commenterInfo.commenterID + '-' + mistake.questionNumber + '-' + mistake.wrongChoice + '\')"' +'/>' + '<input type="button" class="button i18n" ' + 'id="save-button-' + mistake.questionNumber + '-' + mistake.wrongChoice + '" ' + 'style="display:' + buttonMode.save + '" name="value-save" value="Save" ' + 'onclick="saveComment(\'comment-' + commenterInfo.commenterID + '-' + mistake.questionNumber + '-' + mistake.wrongChoice + '\')"' +'/>' + '<input type="button" class="button i18n" ' + 'id="eg-button-' + mistake.questionNumber + '-' + mistake.wrongChoice + '" ' + 'style="display:' + buttonMode.save + '" name="value-eg" value="e.g." ' + 'onclick="copyDown(this,\'comment-' + commenterInfo.commenterID + '-' + mistake.questionNumber + '-' + mistake.wrongChoice + '\')"' +'/>' + '<select class="rule-dropdown" onclick="" id="rule-' + mistake.questionNumber + '-' + mistake.wrongChoice + '" onfocus="buildRuleSelect(this);" onblur="shrinkOnBlur(this);" onchange="addRuleToMistake(this)"><option class="i18n" name="content-tag-with-a-rule" value="none">Tag with a rule</option></select> <a class="i18n" name="content-help" target="_blank" href="commenter-manual.html">[help]</a>' + '<div style="display:none;">' + mistake.wrong + '</div>' + '</div>'; var questionNumber = mistake.questionNumber; var wrongChoice = mistake.wrongChoice; for (var j=0,jlen=mistake.comments.length;j<jlen;j+=1) { var commenter = mistake.comments[j].commenter; var commenterID = mistake.comments[j].commenterID; var comment = mistake.comments[j].comment; var commentContainer = buildComment(questionNumber,wrongChoice,commenter,commenterID,comment); mistakeDiv.appendChild(commentContainer); } for (var j=0,jlen=mistake.rules.length;j<jlen;j+=1) { var rule = mistake.rules[j]; var ruleNode = buildRule(questionNumber,wrongChoice,rule.ruleid,rule.ruletext); mistakeDiv.childNodes[0].appendChild(ruleNode); } container.appendChild(mistakeDiv); } MathJax.Hub.Queue(["Typeset",MathJax.Hub]); } function addRuleToMistake (node) { var commenterKey = getParameterByName('commenter'); var classID = getParameterByName('classid'); var quizNumber = getParameterByName('quizno'); // Figure out which choice we're looking at var m = node.id.split('-'); var questionNumber = m[1]; var wrongChoice = m[2]; var ruleid = node.value; // Call the server to attach the rule if necessary, // and to find out whether it needs to be added // in the UI. var ruleData = apiRequest( '/?commenter=' + commenterKey +'&page=quiz&cmd=attachrule' + '&classid=' + classID + '&quizno=' + quizNumber , { questionno:questionNumber, wrongchoice: wrongChoice, ruleid:ruleid } ); if (false === ruleData) return; // If it needs to be added, add it. if (ruleData.ruleID) { var pairNode = node.parentNode.previousSibling; var ruleNode = buildRule(questionNumber,wrongChoice,ruleData.ruleID,ruleData.ruleText); pairNode.appendChild(ruleNode); } // Then reset the value on the select node.selectedIndex = 0; node.blur(); }; function shrinkOnBlur (node) { // This is kind of crappy, but there is no cross-browser way // of detecting // Remove the select nodes, for consistent UI layout without // pre-populating selection lists everywhere. // This only fires when a selection is made, or when the // node is blurred. Not ideal, but the best we can do. for (var i=node.childNodes.length-1;i>0;i--) { var child = node.childNodes[i]; node.removeChild(child); } }; function buildRuleSelect (node) { for (var i=1,ilen=node.childNodes.length;i<ilen;i+=1) { node.removeChild(node.childNodes[1]); } var selections = []; for (var i=0,ilen=quizMistakes.selections.length;i<ilen;i+=1) { var rule = quizMistakes.selections[i]; var option = document.createElement('option'); option.setAttribute('value',rule.ruleid); option.innerHTML = markdown(rule.ruletext); option.innerHTML = option.textContent; selections.push({node:option,str:option.textContent}); } selections.sort(function(a,b){if(a.str>b.str){return 1}else if(a.str>b.str){return -1}else{return 0}}); for (var i=0,ilen=selections.length;i<ilen;i+=1) { var selection = selections[i]; node.appendChild(selection.node); } }; function copyDown(node,id) { var targetNode = document.getElementById(id); var currentText = targetNode.childNodes[0].value; if (currentText && currentText.replace(/\s+/,'')) { return; } var sourceNode = node.parentNode.previousSibling.childNodes[2]; sourceNode = sourceNode.cloneNode(true); var bubbles = sourceNode.getElementsByClassName('language-bubble'); if (bubbles && bubbles.length) { for (var i=bubbles.length-1;i>-1;i+=-1) { sourceNode.removeChild(bubbles[i]); } } var wrongText = sourceNode.textContent.replace(/^\s+/,'').replace(/\s+$/,''); targetNode.childNodes[0].value = '> ' + wrongText; } function buildComment (questionNumber,wrongChoice,commenter,commenterID,comment) { var commentContainer = document.createElement('div'); commentContainer.setAttribute('class', 'comment-container'); commentContainer.setAttribute('id','comment-' + commenterID + '-' + questionNumber + '-' + wrongChoice); commentContainer.innerHTML = '<div class="commenter-name">' + commenter + '</div>' + '<div>' + markdown(comment) + '</div>'; return commentContainer; } function openComment (id) { var commenterKey = getParameterByName('commenter'); var classID = getParameterByName('classid'); var quizNumber = getParameterByName('quizno'); var m = id.split('-'); var questionNumber = m[2]; var wrongChoice = m[3]; var commentText = apiRequest( '/?commenter=' + commenterKey +'&page=quiz&cmd=getonecomment' + '&classid=' + classID + '&quizno=' + quizNumber , { questionno:questionNumber, wrongchoice: wrongChoice } ); if (false === commentText) return; var node = document.getElementById(id); buildOpenComment(node,questionNumber,wrongChoice,commentText); } function buildOpenComment(node,questionNumber,wrongChoice,commentText) { node.innerHTML = '<textarea style="width:100%" cols="60" rows="3" class="selection-text" placeholder="Saving a comment without content will remove it">' + commentText + '</textarea>' setButtonMode('save',questionNumber,wrongChoice); } function saveComment (id) { var adminID = getParameterByName('admin'); var commenterKey = getParameterByName('commenter'); var classID = getParameterByName('classid'); var quizNumber = getParameterByName('quizno'); //var commenterID = getParameterByName('commenter'); var m = id.split('-'); var questionNumber = m[2]; var wrongChoice = m[3]; var commenterID = m[1]; var node = document.getElementById(id); var comment = node.firstChild.value; // Check whether the comment text contains one or more rules var lst = comment.split('\n\n'); var rules = {top:null,other:[]}; for (var i=lst.length-1;i>-1;i+=-1) { var m = lst[i].match(/^>>>(.*)/); if (m) { var rule = m[1].replace(/^\s+/,'').replace(/\s+$/,''); if (i === 0) { rules.top = rule; lst = lst.slice(1); } else { rules.other.push(rule); } } } //console.log("XX "+JSON.stringify(rules)); // Reconstruct comment comment = lst.join('\n\n').replace(/^\s+/,''); // Rules if (rules.top || rules.other.length) { // Send rules to server for saving var ruleData = apiRequest( '/?commenter=' + commenterKey +'&page=quiz&cmd=getrule' + '&classid=' + classID + '&quizno=' + quizNumber , { questionno:questionNumber, wrongchoice: wrongChoice, rules:rules } ); if (false === ruleData) return; // Refresh dropdown list quizMistakes.selections = ruleData.selections; // Add top rule to UI if (ruleData.ruleID && rules.top) { var editButton = document.getElementById('edit-button-' + questionNumber + '-' +wrongChoice); var ruleBlock = buildRule(questionNumber,wrongChoice,ruleData.ruleID,rules.top); var answerPairNode = editButton.parentNode.previousSibling; answerPairNode.appendChild(ruleBlock); } } // Handle comment if (comment) { var commentBlock = buildComment(questionNumber,wrongChoice,commenterInfo.commenter,commenterInfo.commenterID,comment); node.parentNode.insertBefore(commentBlock,node); node.parentNode.removeChild(node); setButtonMode('edit',questionNumber,wrongChoice); } else { // Delete from server, remove the node, restore to comment mode node.parentNode.removeChild(node); setButtonMode('comment',questionNumber,wrongChoice) } writeComment(questionNumber,wrongChoice,comment,id); } function removeRule (node) { // XXX // Simple API call var m = node.id.split('-'); var questionNumber = m[1]; var wrongChoice = m[2]; var ruleID = m[3]; var commenterKey = getParameterByName('commenter'); var classID = getParameterByName('classid'); var quizNumber = getParameterByName('quizno'); apiRequest( '/?commenter=' + commenterKey +'&page=quiz&cmd=removerule' + '&classid=' + classID + '&quizno=' + quizNumber , { questionno:questionNumber, wrongchoice: wrongChoice, ruleid:ruleID } ); node.parentNode.parentNode.parentNode.removeChild(node.parentNode.parentNode); }; function buildRule (questionNumber,wrongChoice,ruleID,ruleText) { // Build the object var ruleContainer = document.createElement('div'); ruleContainer.setAttribute('class', 'rule-container'); ruleContainer.setAttribute('id', 'rule-' + ruleID + '-' + questionNumber + '-' +wrongChoice); // XXXX ruleContainer.innerHTML = '<div class="rule-button"><input type="button" id="removerule-' + questionNumber + '-' + wrongChoice + '-' + ruleID + '" class="button-small i18n" name="value-delete" onclick="confirmDelete(this,\'removeRule\')" value="Del"/></div><div>' + markdown(ruleText) + '</div>'; // Return return ruleContainer; }; function refreshDropdownList (ruleData,questionNumber,wrongChoice) { var dropdownList = document.getElementById('rule-' + questionNumber + '-' + wrongChoice); for (var i=1,ilen=dropdownList.childNodes.length;i<ilen;i+=1) { dropdownList.removeChild(dropdownList.childNodes[1]); } for (var i=0,ilen=ruleData.selections.length;i<ilen;i+=1) { var selection = ruleData.selections[i]; var option = document.createElement('option'); option.setAttribute('value',selection.ruleID); option.innerHTML = markdown(selection.ruleText); option.innerHTML = option.textContent; dropdownList.appendChild(option); } } function newComment(button,id) { var parent = button.parentNode; var uncle = parent.nextSibling; var adminID = getParameterByName('admin'); var classID = getParameterByName('classid'); var quizNumber = getParameterByName('quizno'); //var commenterID = getParameterByName('commenter'); var m = id.split('-'); var questionNumber = m[2]; var wrongChoice = m[3]; //var commenterID = m[1]; var commentContainer = document.createElement('div'); commentContainer.setAttribute('id',id); commentContainer.setAttribute('class','comment-container'); parent.parentNode.insertBefore(commentContainer,uncle); buildOpenComment(commentContainer,questionNumber,wrongChoice,''); } function setButtonMode (mode,questionNumber,wrongChoice) { var commentButton = document.getElementById('comment-button-' + questionNumber + '-' + wrongChoice); var editButton = document.getElementById('edit-button-' + questionNumber + '-' + wrongChoice); var saveButton = document.getElementById('save-button-' + questionNumber + '-' + wrongChoice); var egButton = document.getElementById('eg-button-' + questionNumber + '-' + wrongChoice); if (mode === 'edit') { commentButton.setAttribute('style', 'display:none'); editButton.setAttribute('style', 'display:inline'); saveButton.setAttribute('style', 'display:none'); egButton.setAttribute('style', 'display:none'); } else if (mode === 'save') { commentButton.setAttribute('style', 'display:none'); editButton.setAttribute('style', 'display:none'); saveButton.setAttribute('style', 'display:inline'); egButton.setAttribute('style', 'display:inline'); } else { commentButton.setAttribute('style', 'display:inline'); editButton.setAttribute('style', 'display:none'); saveButton.setAttribute('style', 'display:none'); egButton.setAttribute('style', 'display:none'); } }; function writeComment (questionNumber,wrongChoice,comment,id) { var commenterKey = getParameterByName('commenter'); var classID = getParameterByName('classid'); var quizNumber = getParameterByName('quizno'); if (comment) { comment = comment.replace(/^\s+/,'').replace(/\s+$/,''); } var ignoreStr = apiRequest( '/?commenter=' + commenterKey +'&page=quiz&cmd=writeonecomment' + '&classid=' + classID + '&quizno=' + quizNumber , { questionno:questionNumber, wrongchoice: wrongChoice, comment: comment } ); MathJax.Hub.Queue(["Typeset",MathJax.Hub,id]); }