Commit 30e9b3cd by Braden MacDonald

Merge branch 'clarifications' into fix-hgse-upgrade-merged

parents caccfd1e 2bde5538
...@@ -115,15 +115,13 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock): ...@@ -115,15 +115,13 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock):
log.debug(u'MCQ submission result: %s', result) log.debug(u'MCQ submission result: %s', result)
return result return result
def author_edit_view(self, context): def get_author_edit_view_fragment(self, context):
""" """
The options for the 1-5 values of the Likert scale aren't child blocks but we want to The options for the 1-5 values of the Likert scale aren't child blocks but we want to
show them in the author edit view, for clarity. show them in the author edit view, for clarity.
""" """
fragment = Fragment(u"<p>{}</p>".format(self.question)) fragment = Fragment(u"<p>{}</p>".format(self.question))
self.render_children(context, fragment, can_reorder=True, can_add=False) self.render_children(context, fragment, can_reorder=True, can_add=False)
fragment.add_content(loader.render_template('templates/html/questionnaire_add_buttons.html', {}))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/questionnaire-edit.css'))
return fragment return fragment
def validate_field_data(self, validation, data): def validate_field_data(self, validation, data):
...@@ -194,7 +192,7 @@ class RatingBlock(MCQBlock): ...@@ -194,7 +192,7 @@ class RatingBlock(MCQBlock):
{"display_name": dn, "value": val} for val, dn in zip(self.FIXED_VALUES, display_names) {"display_name": dn, "value": val} for val, dn in zip(self.FIXED_VALUES, display_names)
] + super(RatingBlock, self).human_readable_choices ] + super(RatingBlock, self).human_readable_choices
def author_edit_view(self, context): def get_author_edit_view_fragment(self, context):
""" """
The options for the 1-5 values of the Likert scale aren't child blocks but we want to The options for the 1-5 values of the Likert scale aren't child blocks but we want to
show them in the author edit view, for clarity. show them in the author edit view, for clarity.
...@@ -207,6 +205,4 @@ class RatingBlock(MCQBlock): ...@@ -207,6 +205,4 @@ class RatingBlock(MCQBlock):
'accepted_statuses': [None] + [self.describe_choice_correctness(c) for c in "12345"], 'accepted_statuses': [None] + [self.describe_choice_correctness(c) for c in "12345"],
})) }))
self.render_children(context, fragment, can_reorder=True, can_add=False) self.render_children(context, fragment, can_reorder=True, can_add=False)
fragment.add_content(loader.render_template('templates/html/questionnaire_add_buttons.html', {}))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/questionnaire-edit.css'))
return fragment return fragment
...@@ -314,8 +314,9 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -314,8 +314,9 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
'child_content': child_content, 'child_content': child_content,
'missing_dependency_url': self.has_missing_dependency and self.next_step_url, 'missing_dependency_url': self.has_missing_dependency and self.next_step_url,
})) }))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/mentoring.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/vendor/underscore-min.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/vendor/underscore-min.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js'))
js_file = 'public/js/mentoring_{}_view.js'.format('assessment' if self.is_assessment else 'standard') js_file = 'public/js/mentoring_{}_view.js'.format('assessment' if self.is_assessment else 'standard')
fragment.add_javascript_url(self.runtime.local_resource_url(self, js_file)) fragment.add_javascript_url(self.runtime.local_resource_url(self, js_file))
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'))
...@@ -731,7 +732,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -731,7 +732,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', { fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', {
"url_name": self.url_name "url_name": self.url_name
})) }))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/mentoring_edit.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css'))
self.include_theme_files(fragment) self.include_theme_files(fragment)
return fragment return fragment
...@@ -746,7 +747,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -746,7 +747,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', { fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', {
"url_name": self.url_name "url_name": self.url_name
})) }))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/mentoring_edit.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder.css'))
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_edit.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_edit.js'))
fragment.initialize_js('MentoringEditComponents') fragment.initialize_js('MentoringEditComponents')
return fragment return fragment
......
/* Some styling to make clarifications stand out a bit in
studio HTML edit view. */
.mce-content-body .pb-clarification {
color: #999;
font-size: 0.75em;
}
.mce-content-body .pb-clarification::before {
content: "(?)["
}
.mce-content-body .pb-clarification::after {
content: "]"
}
...@@ -173,3 +173,11 @@ ...@@ -173,3 +173,11 @@
float: right; float: right;
display: none; display: none;
} }
.pb-clarification span.clarification i {
font-style: normal;
}
.pb-clarification span.clarification i:hover {
color: rgb(0, 159, 230);
}
...@@ -113,6 +113,8 @@ function MentoringBlock(runtime, element) { ...@@ -113,6 +113,8 @@ function MentoringBlock(runtime, element) {
} }
} }
ProblemBuilderUtil.transformClarifications(element);
if (data.mode === 'standard') { if (data.mode === 'standard') {
MentoringStandardView(runtime, element, mentoring); MentoringStandardView(runtime, element, mentoring);
} }
......
...@@ -17,5 +17,8 @@ function MentoringEditComponents(runtime, element) { ...@@ -17,5 +17,8 @@ function MentoringEditComponents(runtime, element) {
$(this).addClass('disabled'); $(this).addClass('disabled');
} }
}); });
ProblemBuilderUtil.transformClarifications(element);
runtime.listenTo('deleted-child', updateButtons); runtime.listenTo('deleted-child', updateButtons);
} }
function QuestionnaireEdit(runtime, element) {
'use strict';
ProblemBuilderUtil.transformClarifications(element);
}
window.ProblemBuilderUtil = {
transformClarifications: function(element) {
var $element = $(element);
var transformExisting = function(node) {
$('.pb-clarification', node).each(function() {
var item = $(this);
var content = item.html();
var clarification = $(
'<span class="clarification" tabindex="0" role="note" aria-label="Clarification">' +
'<i data-tooltip-show-on-click="true" class="fa fa-info-circle" aria-hidden="true"></i>' +
'<span class="sr"></span>' +
'</span>'
);
clarification.find('i').attr('data-tooltip', content);
clarification.find('span.sr').html(content);
item.empty().append(clarification);
});
};
// Transform all span.pb-clarifications already existing inside the element.
transformExisting($element);
// Transform all future span.pb-clarifications using mutation observer.
// It's only needed in the Studio when editing xblock children because the
// block's JS init function isn't called after edits in the Studio.
if (window.MutationObserver) {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
Array.prototype.forEach.call(mutation.addedNodes, function(node) {
transformExisting(node);
});
})
});
observer.observe($element[0], {childList: true, subtree: true});
}
}
};
...@@ -167,13 +167,21 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -167,13 +167,21 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
return choice.content return choice.content
return submission return submission
def get_author_edit_view_fragment(self, context):
fragment = super(QuestionnaireAbstractBlock, self).author_edit_view(context)
return fragment
def author_edit_view(self, context): def author_edit_view(self, context):
""" """
Add some HTML to the author view that allows authors to add choices and tips. Add some HTML to the author view that allows authors to add choices and tips.
""" """
fragment = super(QuestionnaireAbstractBlock, self).author_edit_view(context) fragment = self.get_author_edit_view_fragment(context)
fragment.add_content(loader.render_template('templates/html/questionnaire_add_buttons.html', {})) fragment.add_content(loader.render_template('templates/html/questionnaire_add_buttons.html', {}))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder.css'))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/questionnaire-edit.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/questionnaire-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/questionnaire_edit.js'))
fragment.initialize_js('QuestionnaireEdit')
return fragment return fragment
def validate_field_data(self, validation, data): def validate_field_data(self, validation, data):
......
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
</div> </div>
<label class="choice-label" for="choice-{{ self.html_id }}-{{i}}"> <label class="choice-label" for="choice-{{ self.html_id }}-{{i}}">
{{i}} {{i}}
{% if i == '1' %} - {{ self.low }}{% endif %} {% if i == '1' %} - {{ self.low|safe }}{% endif %}
{% if i == '5' %} - {{ self.high }}{% endif %} {% if i == '5' %} - {{ self.high|safe }}{% endif %}
</label> </label>
<div class="choice-tips-container"> <div class="choice-tips-container">
<div class="choice-tips"></div> <div class="choice-tips"></div>
......
{% load i18n %} {% load i18n %}
<p>{{ question }}</p> <p>{{ question|safe }}</p>
<h2>{% trans "Built-in choices:" %}</h2> <h2>{% trans "Built-in choices:" %}</h2>
<ul> <ul>
<li>Choice (1): <strong>1 - {{ low }}</strong> ({{accepted_statuses.1}})</li> <li>Choice (1): <strong>1 - {{ low|safe }}</strong> ({{accepted_statuses.1}})</li>
<li>Choice (2): <strong>2</strong> ({{accepted_statuses.2}})</li> <li>Choice (2): <strong>2</strong> ({{accepted_statuses.2}})</li>
<li>Choice (3): <strong>3</strong> ({{accepted_statuses.3}})</li> <li>Choice (3): <strong>3</strong> ({{accepted_statuses.3}})</li>
<li>Choice (4): <strong>4</strong> ({{accepted_statuses.4}})</li> <li>Choice (4): <strong>4</strong> ({{accepted_statuses.4}})</li>
<li>Choice (5): <strong>5 - {{ high }}</strong> ({{accepted_statuses.5}})</li> <li>Choice (5): <strong>5 - {{ high|safe }}</strong> ({{accepted_statuses.5}})</li>
</ul> </ul>
<h2>{% trans "Additional custom choices and tips:" %}</h2> <h2>{% trans "Additional custom choices and tips:" %}</h2>
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2015 Harvard, edX & OpenCraft
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
"""
Test that <span class="pb-clarification"> elements are transformed into LMS-like tooltips.
"""
# Imports ###########################################################
import ddt
from cgi import escape
from xblockutils.base_test import SeleniumXBlockTest
# Classes ###########################################################
@ddt.ddt
class ClarificationTest(SeleniumXBlockTest):
"""
Test that the content of span.pb-clarification elements is transformed into
tooltip elements.
"""
clarification_text = 'Let me clarify...'
mcq_template = """
<problem-builder>
<pb-mcq question="Who was your favorite character? {clarify_escaped}">
<pb-choice value="gaius">Gaius Baltar</pb-choice>
<pb-choice value="adama">Admiral William Adama {clarify}</pb-choice>
<pb-choice value="starbuck">Starbuck</pb-choice>
</pb-mcq>
</problem-builder>
"""
mrq_template = """
<problem-builder>
<pb-mrq question="What makes a great {clarify_escaped} MRQ {clarify_escaped}?">
<pb-choice value="1">Lots of choices</pb-choice>
<pb-choice value="2">Funny{clarify} choices</pb-choice>
<pb-choice value="3">Not sure {clarify}</pb-choice>
</pb-mrq>
</problem-builder>
"""
rating_template = """
<problem-builder>
<pb-rating name="rating_1_1" question="How do you rate {clarify_escaped} Battlestar Galactica?">
<pb-choice value="6">More than 5 stars {clarify}</pb-choice>
</pb-rating>
</problem-builder>
"""
long_answer_template = """
<problem-builder>
<pb-answer question="What did you think {clarify_escaped} of the ending?" />
</problem-builder>
"""
html_block_template = """
<problem-builder>
<html_demo><p>This is some raw {clarify} HTML code.</p></html_demo>
</problem-builder>
"""
def prepare_xml_scenario(self, xml_template):
span = '<span class="pb-clarification">{}</span>'.format(self.clarification_text)
escaped_span = escape(span, quote=True)
return xml_template.format(
clarify=span,
clarify_escaped=escaped_span
)
@ddt.data(
(mcq_template, 2),
(mrq_template, 4),
(rating_template, 2),
(long_answer_template, 1),
(html_block_template, 1),
)
@ddt.unpack
def test_title(self, xml_template, tooltip_count):
self.set_scenario_xml(self.prepare_xml_scenario(xml_template))
pb_element = self.go_to_view()
clarifications = pb_element.find_elements_by_css_selector('span.pb-clarification')
self.assertEqual(len(clarifications), tooltip_count)
for clarification in clarifications:
tooltip = clarification.find_element_by_css_selector('i[data-tooltip]')
self.assertEqual(tooltip.get_attribute('data-tooltip'), self.clarification_text)
sr = clarification.find_element_by_css_selector('span.sr')
self.assertEqual(sr.text, self.clarification_text)
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