Commit cc9498cd by Braden MacDonald

Refactored Step Builder Messages

parent 5b751841
......@@ -35,15 +35,15 @@ from xblock.fragment import Fragment
from xblock.validation import ValidationMessage
from .message import MentoringMessageBlock
from .mixins import (
_normalize_id, QuestionMixin, MessageParentMixin, StepParentMixin, XBlockWithTranslationServiceMixin
)
from .step_review import ReviewStepBlock
from xblockutils.helpers import child_isinstance
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import (
StudioEditableXBlockMixin, StudioContainerXBlockMixin, StudioContainerWithNestedXBlocksMixin
NestedXBlockSpec, StudioEditableXBlockMixin, StudioContainerXBlockMixin, StudioContainerWithNestedXBlocksMixin,
)
......@@ -925,10 +925,16 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
@property
def has_review_step(self):
from .step import ReviewStepBlock
return any(child_isinstance(self, child_id, ReviewStepBlock) for child_id in self.children)
@property
def review_step(self):
""" Get the Review Step XBlock child, if any. Otherwise returns None """
for step_id in self.children:
if child_isinstance(self, step_id, ReviewStepBlock):
return self.runtime.get_block(step_id)
@property
def score(self):
questions = self.questions
total_child_weight = sum(float(question.weight) for question in questions)
......@@ -981,11 +987,12 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
context = context or {}
context['hide_prev_answer'] = True # For Step Builder, we don't show the users' old answers when they try again
context['score_summary'] = self.get_score_summary()
for child_id in self.children:
child = self.runtime.get_block(child_id)
if child is None: # child should not be None but it can happen due to bugs or permission issues
child_content = u"<p>[{}]</p>".format(self._(u"Error: Unable to load child component."))
elif not isinstance(child, MentoringMessageBlock):
else:
child_fragment = self._render_child_fragment(child, context, view='mentoring_view')
fragment.add_frag_resources(child_fragment)
child_content = child_fragment.content
......@@ -1002,11 +1009,12 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_with_steps.js'))
fragment.add_resource(loader.load_unicode('templates/html/mentoring_attempts.html'), "text/html")
fragment.add_resource(loader.load_unicode('templates/html/mentoring_review_templates.html'), "text/html")
self.include_theme_files(fragment)
fragment.initialize_js('MentoringWithStepsBlock')
fragment.initialize_js('MentoringWithStepsBlock', {
'show_extended_feedback': self.show_extended_feedback(),
})
return fragment
......@@ -1021,10 +1029,11 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
NestedXBlockSpec allows explicitly setting disabled/enabled state, disabled reason (if any) and single/multiple
instances
"""
from .step import MentoringStepBlock, ReviewStepBlock # Import here to avoid circular dependency
# Import here to avoid circular dependency
from .step import MentoringStepBlock
return [
MentoringStepBlock,
ReviewStepBlock,
NestedXBlockSpec(ReviewStepBlock, single_instance=True),
]
@XBlock.json_handler
......@@ -1048,11 +1057,15 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
self.active_step = new_value
elif new_value == len(self.step_ids):
# The user just completed the final step.
if self.has_review_step:
self.active_step = -1
# Update the number of attempts, if necessary:
if self.num_attempts < self.max_attempts:
self.num_attempts += 1
# Do we need to render a review (summary of the user's score):
if self.has_review_step:
self.active_step = -1
response_data['review_html'] = self.runtime.render(self.review_step, "mentoring_view", {
'score_summary': self.get_score_summary(),
}).content
response_data['num_attempts'] = self.num_attempts
# And publish the score:
score = self.score
......@@ -1061,12 +1074,13 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
'max_value': self.max_score(),
}
self.runtime.publish(self, 'grade', grade_data)
response_data['grade_data'] = self.get_grade()
response_data['active_step'] = self.active_step
return response_data
def get_grade(self, data=None, suffix=None):
def get_score_summary(self):
if self.num_attempts == 0:
return {}
score = self.score
return {
'score': score.percentage,
......@@ -1078,7 +1092,7 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
'partial': self.partial_json(stringify=False),
'complete': self.complete,
'max_attempts_reached': self.max_attempts_reached,
'assessment_review_tips': self.review_tips,
'review_tips': self.review_tips,
}
@XBlock.json_handler
......@@ -1101,9 +1115,7 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
}
def author_preview_view(self, context):
context = context.copy() if context else {}
context['author_preview_view'] = True
return super(MentoringWithExplicitStepsBlock, self).author_preview_view(context)
return self.student_view(context)
def author_edit_view(self, context):
"""
......@@ -1121,6 +1133,6 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css'))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-tinymce-content.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_with_steps_edit.js'))
fragment.initialize_js('MentoringWithStepsEdit')
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js'))
fragment.initialize_js('ProblemBuilderContainerEdit')
return fragment
......@@ -100,18 +100,6 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin, XBlockWithTransla
"used up all of their allowed attempts."
),
},
"on-review": {
"display_name": _(u"Message shown when no attempts left"),
"long_display_name": _(u"Message shown during review when no attempts remain"),
"default": _(
u"Note: you have used all attempts. Continue to the next unit."
),
"description": _(
u"This message will be shown when the student is reviewing their answers to the assessment, "
"if the student has used up all of their allowed attempts. "
"It is not shown if the student is allowed to try again."
),
},
}
content = String(
......@@ -203,8 +191,3 @@ class CompletedMentoringMessageShim(object):
class IncompleteMentoringMessageShim(object):
CATEGORY = 'pb-message'
STUDIO_LABEL = _("Message (Incomplete)")
class OnReviewMentoringMessageShim(object):
CATEGORY = 'pb-message'
STUDIO_LABEL = _("Message (Review)")
......@@ -357,8 +357,8 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
fragment = super(PlotBlock, self).author_edit_view(context)
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/plot_edit.js'))
fragment.initialize_js('PlotEdit')
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js'))
fragment.initialize_js('ProblemBuilderContainerEdit')
return fragment
......
......@@ -6,6 +6,13 @@
font-style: italic;
}
.xblock[data-block-type=sb-step] .author-preview-view,
.xblock[data-block-type=step-builder] .author-preview-view,
.xblock[data-block-type=problem-builder] .author-preview-view,
.xblock[data-block-type=mentoring] .author-preview-view {
margin: 10px;
}
.xblock[data-block-type=sb-step] .url-name-footer .url-name,
.xblock[data-block-type=step-builder] .url-name-footer .url-name,
.xblock[data-block-type=problem-builder] .url-name-footer .url-name,
......
......@@ -234,6 +234,11 @@
position: relative;
}
.assessment-question-block div[data-block-type=sb-step],
.assessment-question-block div[data-block-type=sb-review-step] {
display: none; /* Hidden until revealed by JS */
}
.mentoring .sb-step .sb-step-message {
position: absolute;
top: 50%;
......@@ -242,4 +247,4 @@
padding: 1.5em;
background-color: white;
box-shadow: 0 10px 20px #5C5C5C;
}
\ No newline at end of file
}
function ProblemBuilderContainerEdit(runtime, element) {
"use strict";
// Standard initialization for any Problem Builder / Step Builder container XBlocks
// that are instances of StudioContainerXBlockWithNestedXBlocksMixin
StudioContainerXBlockWithNestedXBlocksMixin(runtime, element);
if (window.ProblemBuilderUtil) {
ProblemBuilderUtil.transformClarifications(element);
}
}
function MentoringWithStepsBlock(runtime, element) {
function MentoringWithStepsBlock(runtime, element, params) {
// Set up gettext in case it isn't available in the client runtime:
if (typeof gettext == "undefined") {
......@@ -8,23 +8,22 @@ function MentoringWithStepsBlock(runtime, element) {
var children = runtime.children(element);
var steps = [];
var reviewStep;
for (var i = 0; i < children.length; i++) {
var child = children[i];
var blockType = $(child.element).data('block-type');
if (blockType === 'sb-step') {
steps.push(child);
} else if (blockType === 'sb-review-step') {
reviewStep = child;
}
}
var activeStep = $('.mentoring', element).data('active-step');
var reviewTipsTemplate = _.template($('#xblock-review-tips-template').html()); // Tips about specific questions the user got wrong
var attemptsTemplate = _.template($('#xblock-attempts-template').html());
var message = $('.sb-step-message', element);
var checkmark, submitDOM, nextDOM, reviewDOM, tryAgainDOM,
gradeDOM, attemptsDOM, reviewTipsDOM, reviewLinkDOM, submitXHR;
var checkmark, submitDOM, nextDOM, reviewButtonDOM, tryAgainDOM,
gradeDOM, attemptsDOM, reviewLinkDOM, submitXHR;
var reviewStepDOM = $("[data-block-type=sb-review-step]", element);
var hasAReviewStep = reviewStepDOM.length == 1;
function isLastStep() {
return (activeStep === steps.length-1);
......@@ -43,8 +42,7 @@ function MentoringWithStepsBlock(runtime, element) {
}
function extendedFeedbackEnabled() {
var data = gradeDOM.data();
return data.extended_feedback === "True";
return !!(params.extended_feedback); // Show extended feedback when all attempts are used up?
}
function showFeedback(response) {
......@@ -61,22 +59,6 @@ function MentoringWithStepsBlock(runtime, element) {
}
}
function updateGrade(grade_data) {
gradeDOM.data('score', grade_data.score);
gradeDOM.data('correct_answer', grade_data.correct_answers);
gradeDOM.data('incorrect_answer', grade_data.incorrect_answers);
gradeDOM.data('partially_correct_answer', grade_data.partially_correct_answers);
gradeDOM.data('correct', grade_data.correct);
gradeDOM.data('incorrect', grade_data.incorrect);
gradeDOM.data('partial', grade_data.partial);
gradeDOM.data('assessment_review_tips', grade_data.assessment_review_tips);
updateReviewStep(grade_data);
}
function updateReviewStep(response) {
reviewStep.updateAssessmentMessage(response, updateControls);
}
function updateControls() {
submitDOM.attr('disabled', 'disabled');
......@@ -84,8 +66,8 @@ function MentoringWithStepsBlock(runtime, element) {
if (nextDOM.is(':visible')) { nextDOM.focus(); }
if (atReviewStep()) {
if (reviewStep) {
reviewDOM.removeAttr('disabled');
if (hasAReviewStep) {
reviewButtonDOM.removeAttr('disabled');
} else {
if (someAttemptsLeft()) {
tryAgainDOM.removeAttr('disabled');
......@@ -111,7 +93,8 @@ function MentoringWithStepsBlock(runtime, element) {
// We are now showing the review step / end
// Update the number of attempts.
attemptsDOM.data('num_attempts', response.num_attempts);
updateGrade(response.grade_data);
reviewStepDOM.html($(response.review_html).html());
updateControls();
} else if (!hasQuestion) {
// This was a step with no questions, so proceed to the next step / review:
updateDisplay();
......@@ -156,7 +139,6 @@ function MentoringWithStepsBlock(runtime, element) {
hideAllSteps();
hideReviewStep();
attemptsDOM.html('');
reviewTipsDOM.empty().hide();
message.hide();
}
......@@ -186,54 +168,32 @@ function MentoringWithStepsBlock(runtime, element) {
} else {
nextDOM.removeAttr('disabled');
}
if (isLastStep() && reviewStep) {
if (isLastStep() && hasAReviewStep) {
if (step.hasQuestion()) {
reviewDOM.attr('disabled', 'disabled');
reviewButtonDOM.attr('disabled', 'disabled');
} else {
reviewDOM.removeAttr('disabled')
reviewButtonDOM.removeAttr('disabled')
}
reviewDOM.show();
reviewButtonDOM.show();
}
}
}
function showReviewStep() {
// Forward to review step to show assessment message
reviewStep.showAssessmentMessage();
// Forward to review step to render grade data
var showExtendedFeedback = (!someAttemptsLeft() && extendedFeedbackEnabled());
reviewStep.renderGrade(gradeDOM, showExtendedFeedback);
// Add click handler that takes care of showing associated step to step links
$('a.step-link', element).on('click', getStepToReview);
if (someAttemptsLeft()) {
tryAgainDOM.removeAttr('disabled');
// Review tips
var data = gradeDOM.data();
if (data.assessment_review_tips.length > 0) {
// on-assessment-review-question messages specific to questions the student got wrong:
reviewTipsDOM.html(reviewTipsTemplate({
tips: data.assessment_review_tips
}));
reviewTipsDOM.show();
}
}
submitDOM.hide();
nextDOM.hide();
reviewDOM.hide();
reviewButtonDOM.hide();
tryAgainDOM.show();
reviewStepDOM.show();
}
function hideReviewStep() {
if (reviewStep) {
reviewStep.hideAssessmentMessage();
reviewStep.clearGrade(gradeDOM);
}
reviewStepDOM.hide()
}
function getStepToReview(event) {
......@@ -249,8 +209,8 @@ function MentoringWithStepsBlock(runtime, element) {
updateNextLabel();
if (isLastStep()) {
reviewDOM.show();
reviewDOM.removeAttr('disabled');
reviewButtonDOM.show();
reviewButtonDOM.removeAttr('disabled');
nextDOM.hide();
nextDOM.attr('disabled', 'disabled');
} else {
......@@ -307,8 +267,8 @@ function MentoringWithStepsBlock(runtime, element) {
if (isLastStep() && step.hasQuestion()) {
nextDOM.hide();
} else if (isLastStep()) {
reviewDOM.one('click', submit);
reviewDOM.removeAttr('disabled');
reviewButtonDOM.one('click', submit);
reviewButtonDOM.removeAttr('disabled');
nextDOM.hide()
} else if (!step.hasQuestion()) {
nextDOM.one('click', submit);
......@@ -388,7 +348,7 @@ function MentoringWithStepsBlock(runtime, element) {
nextDOM.off();
nextDOM.on('click', updateDisplay);
nextDOM.show();
reviewDOM.hide();
reviewButtonDOM.hide();
}
}
......@@ -434,7 +394,7 @@ function MentoringWithStepsBlock(runtime, element) {
hideAllSteps();
// Initialize references to relevant DOM elements and set up event handlers
checkmark = $('.assessment-checkmark', element);
checkmark = $('.step-overall-checkmark', element);
submitDOM = $(element).find('.submit .input-main');
submitDOM.on('click', submit);
......@@ -446,19 +406,21 @@ function MentoringWithStepsBlock(runtime, element) {
nextDOM.on('click', updateDisplay);
}
reviewDOM = $(element).find('.submit .input-review');
reviewDOM.on('click', showGrade);
reviewButtonDOM = $(element).find('.submit .input-review');
reviewButtonDOM.on('click', showGrade);
tryAgainDOM = $(element).find('.submit .input-try-again');
tryAgainDOM.on('click', tryAgain);
gradeDOM = $('.grade', element);
attemptsDOM = $('.attempts', element);
reviewTipsDOM = $('.assessment-review-tips', element);
reviewLinkDOM = $(element).find('.review-link');
reviewLinkDOM.on('click', showGrade);
// Add click handler that takes care of links to steps on the extended review:
$('a.step-link', element).on('click', getStepToReview);
// Initialize individual steps
// (sets up click handlers for questions and makes sure answer data is up-to-date)
var options = {
......
function MentoringWithStepsEdit(runtime, element) {
"use strict";
var $buttons = $('.add-xblock-component-button[data-category=sb-review-step]', element);
var blockIsPresent = function(klass) {
return $('.xblock ' + klass).length > 0;
};
var updateButton = function(button, condition) {
button.toggleClass('disabled', condition);
};
var disableButton = function(ev) {
if ($(this).is('.disabled')) {
ev.preventDefault();
ev.stopPropagation();
} else {
$(this).addClass('disabled');
}
};
var updateButtons = function(buttons) {
buttons.each(function() {
var button = $(this);
updateButton(button, blockIsPresent('.xblock-header-sb-review-step'));
});
};
var initButtons = function() {
updateButtons($buttons);
$buttons.on('click', disableButton);
};
var resetButtons = function() {
var $disabledButtons = $buttons.filter('.disabled');
updateButtons($disabledButtons);
};
ProblemBuilderUtil.transformClarifications(element);
initButtons();
runtime.listenTo('deleted-child', resetButtons);
}
function PlotEdit(runtime, element) {
'use strict';
StudioContainerXBlockWithNestedXBlocksMixin(runtime, element);
ProblemBuilderUtil.transformClarifications(element);
}
function ReviewStepBlock(runtime, element) {
var gradeTemplate = _.template($('#xblock-feedback-template').html());
var reviewStepsTemplate = _.template($('#xblock-step-links-template').html());
var assessmentMessageDOM = $('.assessment-message', element);
return {
'showAssessmentMessage': function() {
var assessmentMessage = assessmentMessageDOM.data('assessment_message');
assessmentMessageDOM.html(assessmentMessage);
assessmentMessageDOM.show();
},
'hideAssessmentMessage': function() {
assessmentMessageDOM.html('');
assessmentMessageDOM.hide();
},
'updateAssessmentMessage': function(grade, callback) {
var handlerUrl = runtime.handlerUrl(element, 'get_assessment_message');
$.post(handlerUrl, JSON.stringify(grade)).success(function(response) {
assessmentMessageDOM.data('assessment_message', response.assessment_message);
callback();
});
},
'renderGrade': function(gradeDOM, showExtendedFeedback) {
var data = gradeDOM.data();
_.extend(data, {
'runDetails': function(correctness) {
if (!showExtendedFeedback) {
return '';
}
var self = this;
return reviewStepsTemplate({'questions': self[correctness], 'correctness': correctness});
}
});
gradeDOM.html(gradeTemplate(data));
},
'clearGrade': function(gradeDOM) {
gradeDOM.html('');
}
};
}
function ReviewStepEdit(runtime, element) {
"use strict";
var $buttons = $('.add-xblock-component-button[data-category=pb-message]', element);
var blockIsPresent = function(klass) {
return $('.xblock ' + klass).length > 0;
};
var updateButton = function(button, condition) {
button.toggleClass('disabled', condition);
};
var disableButton = function(ev) {
if ($(this).is('.disabled')) {
ev.preventDefault();
ev.stopPropagation();
} else {
$(this).addClass('disabled');
}
};
var updateButtons = function(buttons) {
buttons.each(function() {
var button = $(this);
var msgType = button.data('boilerplate');
updateButton(button, blockIsPresent('.submission-message.'+msgType));
});
};
var initButtons = function() {
updateButtons($buttons);
$buttons.on('click', disableButton);
};
var resetButtons = function() {
var $disabledButtons = $buttons.filter('.disabled');
updateButtons($disabledButtons);
};
ProblemBuilderUtil.transformClarifications(element);
initButtons();
runtime.listenTo('deleted-child', resetButtons);
}
function StepEdit(runtime, element) {
'use strict';
StudioContainerXBlockWithNestedXBlocksMixin(runtime, element);
ProblemBuilderUtil.transformClarifications(element);
}
......@@ -32,10 +32,7 @@ from xblockutils.studio_editable import (
from problem_builder.answer import AnswerBlock, AnswerRecapBlock
from problem_builder.mcq import MCQBlock, RatingBlock
from .message import (
CompletedMentoringMessageShim, IncompleteMentoringMessageShim, OnReviewMentoringMessageShim
)
from problem_builder.mixins import EnumerableChildMixin, MessageParentMixin, StepParentMixin
from problem_builder.mixins import EnumerableChildMixin, StepParentMixin
from problem_builder.mrq import MRQBlock
from problem_builder.plot import PlotBlock
from problem_builder.slider import SliderBlock
......@@ -69,11 +66,6 @@ class Correctness(object):
INCORRECT = 'incorrect'
class HtmlBlockShim(object):
CATEGORY = 'html'
STUDIO_LABEL = _(u"HTML")
@XBlock.needs('i18n')
class MentoringStepBlock(
StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin, XBlockWithPreviewMixin,
......@@ -152,7 +144,8 @@ class MentoringStepBlock(
return [
NestedXBlockSpec(AnswerBlock, boilerplate='studio_default'),
MCQBlock, RatingBlock, MRQBlock, HtmlBlockShim,
MCQBlock, RatingBlock, MRQBlock,
NestedXBlockSpec(None, category="html", label=self._("HTML")),
AnswerRecapBlock, MentoringTableBlock, PlotBlock, SliderBlock
] + additional_blocks
......@@ -229,8 +222,8 @@ class MentoringStepBlock(
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css'))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-tinymce-content.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/step_edit.js'))
fragment.initialize_js('StepEdit')
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js'))
fragment.initialize_js('ProblemBuilderContainerEdit')
return fragment
def mentoring_view(self, context=None):
......@@ -275,96 +268,3 @@ class MentoringStepBlock(
fragment.initialize_js('MentoringStepBlock')
return fragment
class ReviewStepBlock(MessageParentMixin, StudioContainerWithNestedXBlocksMixin, XBlockWithPreviewMixin, XBlock):
""" A dedicated step for reviewing results for a mentoring block """
CATEGORY = 'sb-review-step'
STUDIO_LABEL = _("Review Step")
display_name = String(
default="Review Step"
)
@property
def allowed_nested_blocks(self):
"""
Returns a list of allowed nested XBlocks. Each item can be either
* An XBlock class
* A NestedXBlockSpec
If XBlock class is used it is assumed that this XBlock is enabled and allows multiple instances.
NestedXBlockSpec allows explicitly setting disabled/enabled state,
disabled reason (if any) and single/multiple instances.
"""
return [
NestedXBlockSpec(CompletedMentoringMessageShim, boilerplate='completed'),
NestedXBlockSpec(IncompleteMentoringMessageShim, boilerplate='incomplete'),
NestedXBlockSpec(OnReviewMentoringMessageShim, boilerplate='on-review'),
]
@XBlock.json_handler
def get_assessment_message(self, grade, suffix):
# Data passed as "grade" comes from "get_grade" handler of Step Builder (MentoringWithExplicitStepsBlock)
complete = grade.get('complete')
max_attempts_reached = grade.get('max_attempts_reached')
return {
'assessment_message': self.assessment_message(complete, max_attempts_reached)
}
def assessment_message(self, complete=None, max_attempts_reached=None):
if complete is None and max_attempts_reached is None:
parent = self.get_parent()
complete = parent.complete
max_attempts_reached = parent.max_attempts_reached
if max_attempts_reached:
assessment_message = self.get_message_content('on-review', or_default=True)
else:
if complete: # All answers correct
assessment_message = self.get_message_content('completed', or_default=True)
else:
assessment_message = self.get_message_content('incomplete', or_default=True)
return assessment_message
def mentoring_view(self, context=None):
""" Mentoring View """
return self._render_view(context)
def student_view(self, context=None):
""" Student View """
return self._render_view(context)
def studio_view(self, context=None):
""" Studio View """
return Fragment(u'<p>This is a preconfigured block. It is not editable.</p>')
def _render_view(self, context):
fragment = Fragment()
fragment.add_content(loader.render_template('templates/html/review_step.html', {
'self': self,
}))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/review_step.js'))
fragment.initialize_js('ReviewStepBlock')
return fragment
def author_preview_view(self, context):
return Fragment(
u"<p>{}</p>".format(
_(u"This block summarizes a student's performance on the parent Step Builder block.")
)
)
def author_edit_view(self, context):
"""
Add some HTML to the author view that allows authors to add child blocks.
"""
context['wrap_children'] = {
'head': u'<div class="mentoring">',
'tail': u'</div>'
}
fragment = super(ReviewStepBlock, self).author_edit_view(context)
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/review_step_edit.js'))
fragment.initialize_js('ReviewStepEdit')
return fragment
<!-- Tips about specific questions the student got wrong. From pb-message[type=on-assessment-review-question] blocks -->
<script type="text/template" id="xblock-review-tips-template">
<p class="review-tips-intro"><%= gettext("You might consider reviewing the following items before your next assessment attempt:") %></p>
<ul class="review-tips-list">
<% for (var tip_idx in tips) {{ %>
<li><%= tips[tip_idx] %></li>
<% }} %>
</ul>
</script>
......@@ -13,20 +13,8 @@
{{ child_content|safe }}
{% endfor %}
<div class="grade"
data-score="{{ self.score.percentage }}"
data-correct_answer="{{ self.score.correct|length }}"
data-incorrect_answer="{{ self.score.incorrect|length }}"
data-partially_correct_answer="{{ self.score.partially_correct|length }}"
data-assessment_review_tips="{{ self.review_tips_json }}"
data-extended_feedback="{{ self.extended_feedback }}"
data-correct="{{ self.correct_json }}"
data-incorrect="{{ self.incorrect_json }}"
data-partial="{{ self.partial_json }}">
</div>
<div class="submit">
<span class="assessment-checkmark fa icon-2x"></span>
<span class="step-overall-checkmark fa icon-2x"></span>
<input type="button" class="input-main" value="Submit" disabled="disabled" />
<input type="button" class="input-next" value="Next Step" disabled="disabled" />
<input type="button" class="input-review" value="Review grade" disabled="disabled" />
......@@ -35,11 +23,7 @@
<div class="attempts"
data-max_attempts="{{ self.max_attempts }}" data-num_attempts="{{ self.num_attempts }}">
</div>
</div>
<div class="assessment-review-tips"></div>
</div>
<div class="review-link"><a href="#">Review final grade</a></div>
......
<div class="sb-review-step">
<div class="assessment-message" data-assessment_message="{{ self.assessment_message }}"></div>
<script type="text/template" id="xblock-feedback-template">
<div class="grade-result">
<h2>
<%= _.template(gettext("You scored {percent}% on this assessment."), {percent: score}, {interpolate: /\{(.+?)\}/g}) %>
</h2>
<hr/>
<span class="assessment-checkmark icon-2x checkmark-correct icon-ok fa fa-check"></span>
<div class="results-section">
<p>
<%= _.template(
ngettext(
"You answered 1 question correctly.",
"You answered {number_correct} questions correctly.",
correct_answer
), {number_correct: correct_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<%= runDetails('correct') %>
</div>
<div class="clear"></div>
<span class="assessment-checkmark icon-2x checkmark-partially-correct icon-ok fa fa-check"></span>
<div class="results-section">
<p>
<%= _.template(
ngettext(
"You answered 1 question partially correctly.",
"You answered {number_partially_correct} questions partially correctly.",
partially_correct_answer
), {number_partially_correct: partially_correct_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<%= runDetails('partial') %>
</div>
<div class="clear"></div>
<span class="assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa fa-exclamation"></span>
<div class="results-section">
<p>
<%= _.template(
ngettext(
"You answered 1 question incorrectly.",
"You answered {number_incorrect} questions incorrectly.",
incorrect_answer
), {number_incorrect: incorrect_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<%= runDetails('incorrect') %>
</div>
<div class="clear"></div>
<hr/>
</div>
</script>
<!-- Template for extended feedback: Show extended feedback details when all attempts are used up. -->
<script type="text/template" id="xblock-step-links-template">
<ul class="review-list <%= correctness %>-list">
<% for (var question in questions) { %>
<%
var q = questions[question];
var last_question = question == questions.length - 1;
var second_last_question = question == questions.length - 2;
%>
<li>
<a href="#" class="step-link" data-step="<%= q.step %>"><%=
_.template(gettext("Question {number}"), {number: q.number}, {interpolate: /\{(.+?)\}/g})
%></a><% if (!last_question) { %><%= (questions.length > 2 ? ", " : "") %><%= (second_last_question ? " " + gettext("and"): "") %><% } %>
</li>
<% } %>
</ul>
</script>
</div>
{% load i18n %}
<!-- Tips about specific questions the student got wrong. From pb-message[type=on-assessment-review-question] blocks -->
<p class="review-tips-intro">{% trans "You might consider reviewing the following items before your next assessment attempt:" %}</p>
<ul class="review-tips-list">
{% for tip in tips %}
<li>{{tip}}</li>
{% endfor %}
</ul>
{% load i18n %}
<div class="sb-review-score">
<div class="grade-result">
<h2>{% blocktrans %}You scored {{score}}% on this assessment. {% endblocktrans %}</h2>
{% if is_example %}
<p><em>{% trans "Note: This is an example score, to show how the review step will look." %}</em></p>
{% endif %}
<hr/>
<span class="assessment-checkmark icon-2x checkmark-correct icon-ok fa fa-check"></span>
<div class="results-section">
<p>
{% blocktrans count correct_answers=correct_answers %}
You answered 1 question correctly.
{% plural %}
You answered {{correct_answers}} questions correctly.
{% endblocktrans %}
</p>
{% if show_extended_review %}
<ul class="review-list correct-list">
{% for question in correct %}
<li>
{% if forloop.last and not forloop.first %} {% trans "and" %} {% endif %}
<a href="#" class="step-link" data-step="{{ question.step }}">{% blocktrans %}Question {{question.number}}{% endblocktrans %}</a>{% if forloop.revcounter > 1 and correct|length > 2 %},{%endif%}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="clear"></div>
<span class="assessment-checkmark icon-2x checkmark-partially-correct icon-ok fa fa-check"></span>
<div class="results-section">
<p>
{% blocktrans count partially_correct_answers=partially_correct_answers %}
You answered 1 question partially correctly.
{% plural %}
You answered {{partially_correct_answers}} questions partially correctly.
{% endblocktrans %}
</p>
{% if show_extended_review %}
<ul class="review-list partial-list">
{% for question in partial %}
<li>
{% if forloop.last and not forloop.first %} {% trans "and" %} {% endif %}
<a href="#" class="step-link" data-step="{{ question.step }}">{% blocktrans %}Question {{question.number}}{% endblocktrans %}</a>{% if forloop.revcounter > 1 and partial|length > 2 %},{%endif%}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="clear"></div>
<span class="assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa fa-exclamation"></span>
<div class="results-section">
<p>
{% blocktrans count incorrect_answers=incorrect_answers %}
You answered 1 question incorrectly.
{% plural %}
You answered {{incorrect_answers}} questions incorrectly.
{% endblocktrans %}
</p>
{% if show_extended_review %}
<ul class="review-list incorrect-list">
{% for question in incorrect %}
<li>
{% if forloop.last and not forloop.first %} {% trans "and" %} {% endif %}
<a href="#" class="step-link" data-step="{{ question.step }}">{% blocktrans %}Question {{question.number}}{% endblocktrans %}</a>{% if forloop.revcounter > 1 and incorrect|length > 2 %},{%endif%}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="clear"></div>
<hr/>
</div>
</div>
......@@ -43,7 +43,10 @@ BLOCKS = [
'problem-builder = problem_builder.mentoring:MentoringBlock',
'step-builder = problem_builder.mentoring:MentoringWithExplicitStepsBlock',
'sb-step = problem_builder.step:MentoringStepBlock',
'sb-review-step = problem_builder.step:ReviewStepBlock',
'sb-review-step = problem_builder.step_review:ReviewStepBlock',
'sb-conditional-message = problem_builder.step_review:ConditionalMessageBlock',
'sb-review-score = problem_builder.step_review:ScoreSummaryBlock',
'sb-review-per-question-feedback = problem_builder.step_review:PerQuestionFeedbackBlock',
'sb-plot = problem_builder.plot:PlotBlock',
'sb-plot-overlay = problem_builder.plot:PlotOverlayBlock',
......
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