Calculator in JavaScript
Calculator in JavaScript
http://www.apache.org/licenses/LICENSE-2.0
QUESTION_TYPES = {
MC_QUESTION: 'McQuestion',
SA_QUESTION: 'SaQuestion',
QUESTION_GROUP: 'QuestionGroup'
}
_CORRECT_ANSWER_CUTOFF = 0.99;
_PARTIALLY_CORRECT_ANSWER_CUTOFF = 0.01;
function roundToTwoDecimalPlaces(x) {
return Math.round(x * 100) / 100;
}
/**
* Base class for rendering questions.
*/
function BaseQuestion(el, questionData, messages, componentAudit,
scored) {
this.el = el;
this.id = this.el.attr('id');
this.data = questionData[this.id];
this.scored = scored;
this.messages = messages;
this.componentAudit = componentAudit;
}
BaseQuestion.bindSubclass = function(subclass) {
var tmp = function() {};
tmp.prototype = BaseQuestion.prototype;
subclass.prototype = new tmp();
subclass.prototype.constructor = subclass;
}
BaseQuestion.prototype.getMessageAboutScore = function(score) {
var p = $('<p></p>');
if (score > _CORRECT_ANSWER_CUTOFF) {
p.addClass('correct').text(this.messages.correctAnswer);
} else if (score < _PARTIALLY_CORRECT_ANSWER_CUTOFF) {
p.addClass('incorrect').text(this.messages.incorrectAnswer);
} else {
p.addClass('partially-
correct').text(this.messages.partiallyCorrectAnswer);
}
return p;
};
BaseQuestion.prototype.onCheckAnswer = function() {
var grade = this.grade();
this.displayFeedback(
$('<div/>')
.append(this.getMessageAboutScore(grade.score))
.append(grade.feedback));
if (this.componentAudit) {
var auditDict = {
'instanceid': this.id,
'quid': this.data.quid,
'answer': grade.answer,
'score': roundToTwoDecimalPlaces(grade.score),
'type': this.type
}
if (this instanceof QuestionGroup) {
auditDict['individualScores'] = grade.individualScores;
auditDict['containedTypes'] = this.containedTypes;
}
this.componentAudit(auditDict);
}
};
BaseQuestion.prototype.displayFeedback = function(feedback) {
this.el.find('div.qt-feedback')
.empty()
.append(feedback)
.removeClass('qt-hidden');
try {
window.convertGcbTag();
} catch (error) {
console.log(error);
}
};
BaseQuestion.prototype.getWeight = function() {
var weight = Number(this.data.weight);
return (this.data.weight == null || isNaN(weight)) ? 1.0 :
weight;
};
function getIdentityPermutation(size) {
var identity = [];
for (var i = 0; i < size; i++) {
identity[i] = i;
}
return identity;
}
/**
* A class to handle multiple choice questions.
*/
function McQuestion(el, questionData, messages, componentAudit,
scored,
random) {
BaseQuestion.call(this, el, questionData, messages,
componentAudit, scored);
this.type = QUESTION_TYPES.MC_QUESTION;
this.random = random || Math.random;
this.choicesDivs = this.el.find('div.qt-choices > div');
if (this.data.permuteChoices) {
this.permutation = this._randomPermutation();
this._applyPermutation(this.permutation);
}
}
}
BaseQuestion.bindSubclass(McQuestion);
McQuestion.prototype.bind = function() {
var that = this;
if (this.scored) {
this.el.find('> div.qt-check-answer').addClass('qt-hidden');
return this;
}
this.el.find('div.qt-check-answer > button.qt-check-answer-
button')
.click(function() {
that.onCheckAnswer();
});
return this;
};
McQuestion.prototype._applyPermutation = function(perm) {
var newChoices = [];
for (var i = 0; i < perm.length; i++) {
newChoices[perm[i]] = this.choicesDivs.eq(i);
}
this.el.find('div.qt-choices').empty().append(newChoices);
};
McQuestion.prototype._unapplyPermutation = function(perm) {
var newChoices = [];
for (var i = 0; i < perm.length; i++) {
newChoices[i] = this.choicesDivs.eq(perm[i]);
}
this.el.find('div.qt-choices').empty().append(newChoices);
};
McQuestion.prototype._identityPermutation = function() {
return getIdentityPermutation(this.choicesDivs.length);
};
McQuestion.prototype._randomPermutation = function() {
return getRandomPermutation(this.choicesDivs.length,
this.random);
};
McQuestion.prototype.grade = function() {
var that = this;
var answer = [];
var score = 0.0;
var feedbackDiv = $('<div>');
var feedback = [];
this.choicesDivs.find('> input').each(function() {
var input = this;
var index = $(input).data('index');
if (input.checked) {
answer.push(index);
score += parseFloat(that.data.choices[index].score);
if (that.data.choices[index].feedback) {
feedback.push($('<li/>').html(that.data.choices[index].feedback))
;
}
}
});
if (this.data.allOrNothingGrading) {
score = score > _CORRECT_ANSWER_CUTOFF ? 1.0 : 0.0;
}
header.append($
('<span>').addClass(headerClass).text(headerText));
header.append($('<br/>'));
header.append($('<span>').addClass(headerClass).text("Score:
"+score*this.getWeight()));
feedbackDiv.append(header);
if (feedback.length) {
feedbackDiv
.append($('<h3 class="feedback-header">')
.text(this.messages.targetedFeedbackHeading))
.append($('<ul>').append(feedback));
}
if (this.data.defaultFeedback) {
feedbackDiv
.append($('<h3 class="feedback-header">')
.text(this.messages.feedbackHeading))
.append($('<div>').append(this.data.defaultFeedback));
}
return {
answer: answer,
score: score,
feedback: feedbackDiv,
type: this.type
};
};
McQuestion.prototype.getStudentAnswer = function() {
var responses = [];
this.choicesDivs.find('> input').each(function() {
var index = $(this).data('index');
responses[index] = this.checked;
});
if (this.data.permuteChoices) {
return {
responses: responses,
permutation: this.permutation
};
} else {
return responses;
}
};
McQuestion.prototype.setStudentAnswer = function(answer) {
if (! answer) {
return;
}
this.choicesDivs.find('> input').each(function() {
var index = $(this).data('index');
if (typeof responses[index] == 'boolean') {
this.checked = responses[index];
}
});
if (this.data.permuteChoices) {
this._unapplyPermutation(this.permutation);
this._applyPermutation(permutation);
this.permutation = permutation;
}
};
McQuestion.prototype.makeReadOnly = function() {
this.choicesDivs.find('> input').prop('disabled', true);
};
/**
* A class to handle short answer questions.
*/
function SaQuestion(el, questionData, messages, componentAudit,
scored) {
BaseQuestion.call(this, el, questionData, messages,
componentAudit, scored);
this.type = QUESTION_TYPES.SA_QUESTION;
}
BaseQuestion.bindSubclass(SaQuestion);
SaQuestion.MATCHERS = {
case_insensitive: {
matches: function(answer, response) {
return answer.toLowerCase().trim() ==
response.toLowerCase().trim();
}
},
regex: {
matches: function(answer, response) {
return SaQuestion.parseRegExp(answer).test(response);
}
},
numeric: {
matches: function(answer, response) {
if (response.trim()=="") {
return false;
}
return parseFloat(answer) == parseFloat(response);
}
},
range_match: {
matches: function(answer, response) {
var range;
if (answer.includes(",")) {
range = answer.split(",");
}
else {
// Backward Compatibility
range = answer.split("-");
}
if(response.trim()==""){
return false;
}
else if (response <= parseFloat(range[1]) && response >=
parseFloat(range[0])) {
return true;
}
}
}
};
SaQuestion.parseRegExp = function(regexpString) {
var matches = regexpString.match(/\/(.*)\/([gim]*)/);
if (matches) {
return new RegExp(matches[1], matches[2]);
} else {
return new RegExp(regexpString);
}
};
SaQuestion.prototype.bindHintButton = function() {
var that = this;
this.el.find('div.qt-hint > button.qt-hint-button')
.click(function() {
that.onShowHint();
});
return this;
};
SaQuestion.prototype.bind = function() {
var that = this;
if (this.scored) {
this.el.find('> div.qt-check-answer').addClass('qt-hidden');
} else {
this.el.find('div.qt-check-answer > button.qt-check-answer-
button')
.click(function() {
that.onCheckAnswer();
});
}
this.bindHintButton();
return this;
};
SaQuestion.prototype.onShowHint = function() {
this.el.find('div.qt-feedback')
.empty()
.append($('<div/>').html(this.data.hint))
.removeClass('qt-hidden');
};
SaQuestion.prototype.grade = function() {
var feedbackDiv = $('<div>');
var facultyResponse = $('<div>');
facultyResponse.append($('<h3 class="feedback-header faculty-
answer">')
.text("Accepted Answers:"));
if (SaQuestion.MATCHERS[grader.matcher].matches(
grader.response, response)) {
var score = Math.min(Math.max(parseFloat(grader.score), 0),
1);
//Add result + score
var header = $('<h3 class="feedback-header">');
headerClass = 'correct';
headerText = this.messages.correctAnswer;
header.append($
('<span>').addClass(headerClass).text(headerText));
header.append($('<br/>'));
header.append($
('<span>').addClass(headerClass).text("Score:
"+score*this.getWeight()));
feedbackDiv.append(header);
if(grader.feedback){
feedbackDiv
.append($('<h3 class="feedback-header">')
.text(this.messages.feedbackHeading))
.append($('<ul>').append(grader.feedback));
}
return {
answer: response,
score: score,
feedback: feedbackDiv.append(facultyResponse),
type: this.type
};
}
}
/**
* A class to handle groups of questions.
*
* @param el JQuery root node of the question group
* @param questionData the global question data object
*/
function QuestionGroup(el, questionData, messages,
componentAudit, scored) {
BaseQuestion.call(this, el, questionData, messages,
componentAudit, scored);
this.type = QUESTION_TYPES.QUESTION_GROUP;
this.questionData = questionData;
this.questions = [];
this.init();
}
BaseQuestion.bindSubclass(QuestionGroup);
QuestionGroup.prototype.init = function() {
var that = this;
this.el.find('div.qt-mc-question.qt-embedded, div.qt-sa-
question.qt-embedded')
.each(function(index, element) {
var elt = $(element);
if (elt.hasClass('qt-mc-question')) {
that.questions.push(new McQuestion(elt,
that.questionData,
that.messages, null, this.scored));
} else {
that.questions.push(new SaQuestion(elt,
that.questionData,
that.messages, null,
this.scored).bindHintButton());
}
});
};
QuestionGroup.prototype.getWeight = function() {
// The following ensures that the weight is always strictly
positive, thus
// preventing division-by-zero errors.
return this.getTotalPoints() + 1e-12;
};
QuestionGroup.prototype.bind = function() {
var that = this;
if (this.scored) {
this.el.find('> div.qt-check-answer').addClass('qt-hidden');
return this;
}
this.el.find('div.qt-check-answer > button.qt-check-answer-
button')
.click(function() {
that.onCheckAnswer();
});
return this;
};
QuestionGroup.prototype.displayFeedback = function(feedback) {
var that = this;
$.each(feedback, function(index, feedback) {
that.questions[index].displayFeedback(feedback);
});
};
QuestionGroup.prototype.getTotalPoints = function() {
var that = this;
var total = 0.0;
$.each(this.questions, function(index, question) {
total += parseFloat(that.data[question.id].weight);
});
return total;
};
QuestionGroup.prototype.onCheckAnswer = function() {
var grade = this.grade();
this.el.find('> div.qt-feedback')
.empty()
.append(this.getMessageAboutScore(grade.score))
.removeClass('qt-hidden');
this.displayFeedback(grade.feedback);
this.componentAudit({
'instanceid': this.id,
'answer': grade.answer,
'score': roundToTwoDecimalPlaces(grade.score),
'individualScores': grade.individualScores,
'containedTypes': grade.containedTypes,
'quids': grade.quids,
'type': this.type
});
};
QuestionGroup.prototype.grade = function() {
// This returns a score that is normalized to a total weight of
1.
var that = this;
var answer = [];
var score = 0.0;
var feedback = [];
var individualScores = [];
var containedTypes = [];
var quids = [];
$.each(this.questions, function(index, question) {
var grade = question.grade();
answer.push(grade.answer);
containedTypes.push(question.type);
individualScores.push(grade.score);
quids.push(question.data.quid);
score += that.data[question.id].weight * grade.score;
feedback.push(grade.feedback);
});
QuestionGroup.prototype.getStudentAnswer = function() {
var state = {};
$.each(this.questions, function(index, question) {
state[question.id] = question.getStudentAnswer();
});
return state;
};
QuestionGroup.prototype.setStudentAnswer = function(state) {
if (state) {
$.each(this.questions, function(index, question) {
question.setStudentAnswer(state[question.id]);
});
}
};
QuestionGroup.prototype.makeReadOnly = function(state) {
$.each(this.questions, function(index, question) {
question.makeReadOnly();
});
};
function startSubmitLoad(){
// Change the text in submit button and disable it
$('.qt-check-answer-button').attr("disabled", true)
$('.qt-check-answer-button').css("background", "gray")
$('.qt-check-answer-button').css("cursor", "progress")
}
function endSubmitLoad(msg, showAlerts=true){
// Change the text in submit button and enable it
$('.qt-check-answer-button').attr("disabled", false)
$('.qt-check-answer-button').css("background", '')
$('.qt-check-answer-button').css("cursor", '')
if (showAlerts) {
alert(msg);
}
// window.location.reload();
}
window.startSubmitLoad = startSubmitLoad;
window.endSubmitLoad = endSubmitLoad;
function submitForm(action, reFetchData=true, showAlerts=true,
hiddenData) {
// First confirm whether the logged in user is correct
idTokenFromLocalStorage =
JSON.parse(localStorage.getItem('id_token'));
if (!idTokenFromLocalStorage) {
return;
}
var userEmail = idTokenFromLocalStorage.userEmail;
var confirmEmail = "You are submitting this assignment as <b>"
+ userEmail+"</b>";
// if (showAlerts) {
// if (!confirm("You are submitting this assignment as " +
userEmail)) {
// return;
// }
// }
// Now continue
startSubmitLoad();
pathname = window.location.pathname;
while (pathname.startsWith('/')) {
pathname = pathname.slice(1);
}
if (!pathname.startsWith('courses/')) {
alert("Something went wrong");
return;
}
pathname = pathname.slice(8);
if (pathname.includes("modules/assignment")) {
namespace = pathname.slice(0,pathname.indexOf('/'));
}
else if (pathname.startsWith('ns_') && !pathname.includes('/'))
{
namespace = pathname;
}
else {
alert("Something went wrong");
return;
}
slug = namespace.slice(3);
/**
* This will move the submit answers button to a div with
class
* 'qt-assessment-button-bar-location' if the lesson author
has included
* exactly one.
*/
function maybeMoveGradingButton() {
var buttonBarDiv = $('div.qt-assessment-button-bar');
var buttonBarPreferredLocation = $('div.qt-assessment-button-
bar-location');
if (buttonBarDiv.length == 1 &&
buttonBarPreferredLocation.length == 1) {
buttonBarDiv.appendTo(buttonBarPreferredLocation);
}
}
function getQuestionBatchId(element) {
var parents = $(element).parents('[data-question-batch-id]')
if (parents.length > 0) {
return $(parents[0]).data('question-batch-id');
} else {
return 'unowned';
}
}
function findGcbQuestions() {
function gcbAssessmentTagAudit(data_dict) {
gcbTagEventAudit(data_dict, 'assessment');
}
var messages = window.assessmentTagMessages;
var gcbQuestions = {};
if (window.questionData.savedAnswers) {
for (var group in gcbQuestions) {
$.each(gcbQuestions[group], function(index, question) {
// Display feedback
if ($(question.el).parents('div.show-feedback').length >
0) {
var grade = question.grade();
question.displayFeedback(grade.feedback);
}
});
}
}
$( document ).ready(function() {
var questionBatchId = $('div.qt-grade-scored-lesson-calc-
score').parents('[data-question-batch-
id]').data('questionBatchId');
if(gcbQuestions[questionBatchId]){
gradeScoredLesson(gcbQuestions[questionBatchId],messages,
questionBatchId);
}
});
$('div.qt-grade-assessment')
.css('display', '')
.children('button').click(function(event) {
var dataDiv = $(
$(event.target).parents('[data-question-batch-id]')
[0]);
var questionBatchId = dataDiv.data('question-batch-
id');
var unitId = dataDiv.data('unit-id');
var xsrfToken = dataDiv.data('xsrf-token');
var graderUri = dataDiv.data('grader-uri');
gradeAssessment(gcbQuestions[questionBatchId], unitId,
xsrfToken,
graderUri);
});
$('button.qt-save-draft')
.click(function(event) {
var dataDiv = $(
$(event.target).parents('[data-question-batch-id]')
[0]);
var questionBatchId = dataDiv.data('question-batch-
id');
var unitId = dataDiv.data('unit-id');
var xsrfToken = dataDiv.data('xsrf-token');
var reviewKey = dataDiv.data('review-key');
var score = document.getElementById('peer-review-
score').value;
submitReview(true, gcbQuestions[questionBatchId],
unitId,
xsrfToken, reviewKey, score);
});
$('button.qt-submit-review')
.click(function(event) {
var dataDiv = $(
$(event.target).parents('[data-question-batch-id]')
[0]);
var questionBatchId = dataDiv.data('question-batch-
id');
var unitId = dataDiv.data('unit-id');
var xsrfToken = dataDiv.data('xsrf-token');
var reviewKey = dataDiv.data('review-key');
var score = document.getElementById('peer-review-
score').value;
submitReview(false, gcbQuestions[questionBatchId],
unitId,
xsrfToken, reviewKey, score);
});
}
maybeMoveGradingButton();
return gcbQuestions;
}