Commit 3f1fae5f by Alan Boudreault

First implementation of the mrqblock max attempts feature

parent 7d794f08
......@@ -246,6 +246,26 @@ class String(LightChildField):
return self.value.split(*args, **kwargs)
class Integer(LightChildField):
def __init__(self, *args, **kwargs):
self.value = kwargs.get('default', 0)
print self.value
def __str__(self):
return str(self.value)
def __int__(self):
return self.value
def __nonzero__(self):
try:
int(self.value)
except ValueError: # not an integer
return False
return self.value is not None
class Boolean(LightChildField):
pass
......
......@@ -84,6 +84,7 @@ class MentoringBlock(XBlockWithLightChildren):
self.runtime.local_resource_url(self, 'public/js/vendor/underscore-min.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring.js'))
fragment.add_resource(load_resource('templates/html/mentoring_progress.html'), "text/html")
fragment.add_resource(load_resource('templates/html/mrqblock_attempts.html'), "text/html")
fragment.initialize_js('MentoringBlock')
......
......@@ -26,7 +26,7 @@
import logging
from .light_children import List, Scope
from .light_children import Integer, List, Scope
from .questionnaire import QuestionnaireAbstractBlock
from .utils import render_template
......@@ -43,11 +43,14 @@ class MRQBlock(QuestionnaireAbstractBlock):
An XBlock used to ask multiple-response questions
"""
student_choices = List(help="Last submissions by the student", default=[], scope=Scope.user_state)
max_attempts = Integer(help="Number of max attempts for this questions", default=None, scope=Scope.content)
num_attempts = Integer(help="Number of attempts a user has answered for this questions", scope=Scope.user_state)
def submit(self, submissions):
log.debug(u'Received MRQ submissions: "%s"', submissions)
completed = True
results = []
for choice in self.custom_choices:
choice_completed = True
......@@ -73,12 +76,32 @@ class MRQBlock(QuestionnaireAbstractBlock):
}),
})
self.student_choices = submissions
self.message = u'Your answer is correct!' if completed else u'Your answer is incorrect.'
# What's the proper way to get my value saved? it doesn't work without '.value'
# this is incorrect and the num_attempts is resetted if we restart the server.
self.num_attempts.value = int(self.num_attempts) + 1
max_attempts_reached = False
if self.max_attempts:
max_attempts = int(self.max_attempts)
num_attempts = int(self.num_attempts)
max_attempts_reached = num_attempts >= max_attempts
if max_attempts_reached and (not completed or num_attempts > max_attempts):
log.debug(u'MRQ max attempts reached');
completed = True
self.message += u' You have reached the maximum number of attempts for this question. Your next answers won''t be saved. You can check the answer(s) using the "Show Answer(s)" button.'
else: # only save the student_choices if there was a attempt left, might be incorrect or unuseful
self.student_choices = submissions
result = {
'submissions': submissions,
'completed': completed,
'choices': results,
'message': self.message,
'max_attempts': int(self.max_attempts) if self.max_attempts else None,
'num_attempts': int(self.num_attempts)
}
log.debug(u'MRQ submissions result: %s', result)
return result
......@@ -78,3 +78,16 @@
.mentoring .choices-list .choice-selector {
margin-right: 5px;
}
.mentoring .mrq-attempts {
display: inline-block;
vertical-align: baseline;
margin: 8px 0 0 10px;
color: #777;
font-style: italic;
webkit-font-smoothing: antialiased;
}
.mentoring .mrq-attempts div {
display: inline-block;
}
......@@ -74,7 +74,7 @@ function MentoringBlock(runtime, element) {
}
function initXBlock() {
var submit_dom = $(element).find('.submit');
var submit_dom = $(element).find('.submit input');
submit_dom.bind('click', function() {
var data = {};
......@@ -89,6 +89,12 @@ function MentoringBlock(runtime, element) {
$.post(handlerUrl, JSON.stringify(data)).success(handleSubmitResults);
});
// init children (especially mrq blocks)
var children = getChildren(element);
_.each(children, function(child) {
callIfExists(child, 'init');
});
if (submit_dom.length) {
renderProgress();
}
......
// TODO: Split in two files
var mrqAttemptsTemplate = _.template($('#xblock-mrq-attempts').html());
function MCQBlock(runtime, element) {
return {
......@@ -25,6 +26,23 @@ function MCQBlock(runtime, element) {
function MRQBlock(runtime, element) {
return {
renderAttempts: function() {
var data = $('.mrq-attempts', element).data();
$('.mrq-attempts', element).html(mrqAttemptsTemplate(data));
// bind show answer button
var showAnswerButton = $('button', element);
if (showAnswerButton.length != 0) {
if (_.isUndefined(this.answers))
showAnswerButton.hide();
else
showAnswerButton.on('click', _.bind(this.displayAnswers, this));
}
},
init: function() {
this.renderAttempts();
},
submit: function() {
var checkedCheckboxes = $('input[type=checkbox]:checked', element),
checkedValues = [];
......@@ -58,6 +76,7 @@ function MRQBlock(runtime, element) {
showPopup(messageDOM);
}
var answers = []; // used in displayAnswers
$.each(result.choices, function(index, choice) {
var choiceInputDOM = $('.choice input[value='+choice.value+']', element),
choiceDOM = choiceInputDOM.closest('.choice'),
......@@ -65,10 +84,19 @@ function MRQBlock(runtime, element) {
choiceTipsDOM = $('.choice-tips', choiceDOM),
choiceTipsCloseDOM;
if (choice.completed) {
choiceResultDOM.removeClass('incorrect icon-exclamation').addClass('correct icon-ok');
} else {
choiceResultDOM.removeClass('correct icon-ok').addClass('incorrect icon-exclamation');
/* update our answers dict */
answers.push({
input: choiceInputDOM,
answer: choice.completed ? choiceInputDOM.attr('checked') : !choiceInputDOM.attr('checked')
});
choiceResultDOM.removeClass('incorrect icon-exclamation correct icon-ok');
if (choiceInputDOM.prop('checked')) { /* show hint if checked */
if (choice.completed) {
choiceResultDOM.addClass('correct icon-ok');
} else if (!choice.completed) {
choiceResultDOM.addClass('incorrect icon-exclamation');
}
}
choiceTipsDOM.html(choice.tips);
......@@ -78,6 +106,32 @@ function MRQBlock(runtime, element) {
showPopup(choiceTipsDOM);
});
});
}
this.answers = answers;
$('.mrq-attempts', element).data('num_attempts', result.num_attempts);
this.renderAttempts();
},
displayAnswers: function() {
var showAnswerButton = $('button span', element);
var answers_displayed = this.answers_displayed = !this.answers_displayed;
_.each(this.answers, function(answer) {
var choiceResultDOM = $('.choice-result', answer.input.closest('.choice'));
choiceResultDOM.removeClass('incorrect icon-exclamation correct icon-ok');
if (answers_displayed) {
answer.input.attr('checked', answer.answer);
if (answer.answer)
choiceResultDOM.addClass('correct icon-ok');
showAnswerButton.text('Hide Answer(s)');
}
else {
answer.input.attr('checked', false);
showAnswerButton.text('Show Answer(s)');
}
});
}
};
}
<script type="text/template" id="xblock-mrq-attempts">
<% if (_.isNumber(max_attempts)) {{ %>
<% if (num_attempts >= max_attempts) {{ %>
<button class="show">
<span class="show-label">Show Answer(s)</span>
</button>
<% }} %>
<div> You have used <%= _.min([num_attempts, max_attempts]) %> of <%= max_attempts %> attempts for this question.</div>
<% }} %>
</script>
......@@ -4,13 +4,14 @@
<div class="choices-list">
{% for choice in custom_choices %}
<div class="choice">
<span class="choice-result icon-2x"></span>
<label class="choice-label">
<input class="choice-selector" type="checkbox" name="{{ self.name }}" value="{{ choice.value }}"{% if choice.value in self.student_choices %} checked{% endif %}> {{ choice.content }}
</label>
<span class="choice-result icon-2x"></span>
<div class="choice-tips"></div>
</div>
{% endfor %}
<div class="choice-message"></div>
</div>
</fieldset>
<div class="mrq-attempts" data-max_attempts="{{ self.max_attempts }}" data-num_attempts="{{ self.num_attempts }}"></div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment