Commit 3f1fae5f by Alan Boudreault

First implementation of the mrqblock max attempts feature

parent 7d794f08
...@@ -246,6 +246,26 @@ class String(LightChildField): ...@@ -246,6 +246,26 @@ class String(LightChildField):
return self.value.split(*args, **kwargs) 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): class Boolean(LightChildField):
pass pass
......
...@@ -84,6 +84,7 @@ class MentoringBlock(XBlockWithLightChildren): ...@@ -84,6 +84,7 @@ class MentoringBlock(XBlockWithLightChildren):
self.runtime.local_resource_url(self, 'public/js/vendor/underscore-min.js')) 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_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/mentoring_progress.html'), "text/html")
fragment.add_resource(load_resource('templates/html/mrqblock_attempts.html'), "text/html")
fragment.initialize_js('MentoringBlock') fragment.initialize_js('MentoringBlock')
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
import logging import logging
from .light_children import List, Scope from .light_children import Integer, List, Scope
from .questionnaire import QuestionnaireAbstractBlock from .questionnaire import QuestionnaireAbstractBlock
from .utils import render_template from .utils import render_template
...@@ -43,11 +43,14 @@ class MRQBlock(QuestionnaireAbstractBlock): ...@@ -43,11 +43,14 @@ class MRQBlock(QuestionnaireAbstractBlock):
An XBlock used to ask multiple-response questions An XBlock used to ask multiple-response questions
""" """
student_choices = List(help="Last submissions by the student", default=[], scope=Scope.user_state) 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): def submit(self, submissions):
log.debug(u'Received MRQ submissions: "%s"', submissions) log.debug(u'Received MRQ submissions: "%s"', submissions)
completed = True completed = True
results = [] results = []
for choice in self.custom_choices: for choice in self.custom_choices:
choice_completed = True choice_completed = True
...@@ -73,12 +76,32 @@ class MRQBlock(QuestionnaireAbstractBlock): ...@@ -73,12 +76,32 @@ class MRQBlock(QuestionnaireAbstractBlock):
}), }),
}) })
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 self.student_choices = submissions
result = { result = {
'submissions': submissions, 'submissions': submissions,
'completed': completed, 'completed': completed,
'choices': results, 'choices': results,
'message': self.message, '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) log.debug(u'MRQ submissions result: %s', result)
return result return result
...@@ -78,3 +78,16 @@ ...@@ -78,3 +78,16 @@
.mentoring .choices-list .choice-selector { .mentoring .choices-list .choice-selector {
margin-right: 5px; 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) { ...@@ -74,7 +74,7 @@ function MentoringBlock(runtime, element) {
} }
function initXBlock() { function initXBlock() {
var submit_dom = $(element).find('.submit'); var submit_dom = $(element).find('.submit input');
submit_dom.bind('click', function() { submit_dom.bind('click', function() {
var data = {}; var data = {};
...@@ -89,6 +89,12 @@ function MentoringBlock(runtime, element) { ...@@ -89,6 +89,12 @@ function MentoringBlock(runtime, element) {
$.post(handlerUrl, JSON.stringify(data)).success(handleSubmitResults); $.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) { if (submit_dom.length) {
renderProgress(); renderProgress();
} }
......
// TODO: Split in two files // TODO: Split in two files
var mrqAttemptsTemplate = _.template($('#xblock-mrq-attempts').html());
function MCQBlock(runtime, element) { function MCQBlock(runtime, element) {
return { return {
...@@ -25,6 +26,23 @@ function MCQBlock(runtime, element) { ...@@ -25,6 +26,23 @@ function MCQBlock(runtime, element) {
function MRQBlock(runtime, element) { function MRQBlock(runtime, element) {
return { 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() { submit: function() {
var checkedCheckboxes = $('input[type=checkbox]:checked', element), var checkedCheckboxes = $('input[type=checkbox]:checked', element),
checkedValues = []; checkedValues = [];
...@@ -58,6 +76,7 @@ function MRQBlock(runtime, element) { ...@@ -58,6 +76,7 @@ function MRQBlock(runtime, element) {
showPopup(messageDOM); showPopup(messageDOM);
} }
var answers = []; // used in displayAnswers
$.each(result.choices, function(index, choice) { $.each(result.choices, function(index, choice) {
var choiceInputDOM = $('.choice input[value='+choice.value+']', element), var choiceInputDOM = $('.choice input[value='+choice.value+']', element),
choiceDOM = choiceInputDOM.closest('.choice'), choiceDOM = choiceInputDOM.closest('.choice'),
...@@ -65,10 +84,19 @@ function MRQBlock(runtime, element) { ...@@ -65,10 +84,19 @@ function MRQBlock(runtime, element) {
choiceTipsDOM = $('.choice-tips', choiceDOM), choiceTipsDOM = $('.choice-tips', choiceDOM),
choiceTipsCloseDOM; choiceTipsCloseDOM;
/* 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) { if (choice.completed) {
choiceResultDOM.removeClass('incorrect icon-exclamation').addClass('correct icon-ok'); choiceResultDOM.addClass('correct icon-ok');
} else { } else if (!choice.completed) {
choiceResultDOM.removeClass('correct icon-ok').addClass('incorrect icon-exclamation'); choiceResultDOM.addClass('incorrect icon-exclamation');
}
} }
choiceTipsDOM.html(choice.tips); choiceTipsDOM.html(choice.tips);
...@@ -78,6 +106,32 @@ function MRQBlock(runtime, element) { ...@@ -78,6 +106,32 @@ function MRQBlock(runtime, element) {
showPopup(choiceTipsDOM); 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 @@ ...@@ -4,13 +4,14 @@
<div class="choices-list"> <div class="choices-list">
{% for choice in custom_choices %} {% for choice in custom_choices %}
<div class="choice"> <div class="choice">
<span class="choice-result icon-2x"></span>
<label class="choice-label"> <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 }} <input class="choice-selector" type="checkbox" name="{{ self.name }}" value="{{ choice.value }}"{% if choice.value in self.student_choices %} checked{% endif %}> {{ choice.content }}
</label> </label>
<span class="choice-result icon-2x"></span>
<div class="choice-tips"></div> <div class="choice-tips"></div>
</div> </div>
{% endfor %} {% endfor %}
<div class="choice-message"></div> <div class="choice-message"></div>
</div> </div>
</fieldset> </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