Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions problem_builder/mcq.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,13 @@ def submit(self, submission):
log.debug(u'MCQ submission result: %s', 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
show them in the author edit view, for clarity.
"""
fragment = Fragment(u"<p>{}</p>".format(self.question))
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

def validate_field_data(self, validation, data):
Expand Down Expand Up @@ -194,7 +192,7 @@ def human_readable_choices(self):
{"display_name": dn, "value": val} for val, dn in zip(self.FIXED_VALUES, display_names)
] + 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
show them in the author edit view, for clarity.
Expand All @@ -207,6 +205,4 @@ def author_edit_view(self, context):
'accepted_statuses': [None] + [self.describe_choice_correctness(c) for c in "12345"],
}))
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
10 changes: 7 additions & 3 deletions problem_builder/mentoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,9 @@ def student_view(self, context):
'child_content': child_content,
'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/util.js'))
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, 'public/js/mentoring.js'))
Expand Down Expand Up @@ -731,7 +732,7 @@ def author_preview_view(self, context):
fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', {
"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)
return fragment

Expand All @@ -746,7 +747,10 @@ def author_edit_view(self, context):
fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', {
"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.initialize_js('MentoringEditComponents')
return fragment
Expand Down
15 changes: 15 additions & 0 deletions problem_builder/public/css/problem-builder-tinymce-content.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* 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: "]"
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,11 @@
float: right;
display: none;
}

.pb-clarification span.clarification i {
font-style: normal;
}

.pb-clarification span.clarification i:hover {
color: rgb(0, 159, 230);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be combined with mentoring.css ? Mentoring.css should really be renamed to problem_builder.css since it applies to the block as a whole. This would be a good opportunity to do so, and would reduce the number of separate CSS/JS files we are sending to the browser.

Also nit: missing newline at EOF

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradenmacdonald That's a good idea, I moved the CSS rules into mentoring.css and renamed it to problem_builder.css.

2 changes: 2 additions & 0 deletions problem_builder/public/js/mentoring.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ function MentoringBlock(runtime, element) {
}
}

ProblemBuilderUtil.transformClarifications(element);

if (data.mode === 'standard') {
MentoringStandardView(runtime, element, mentoring);
}
Expand Down
3 changes: 3 additions & 0 deletions problem_builder/public/js/mentoring_edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ function MentoringEditComponents(runtime, element) {
$(this).addClass('disabled');
}
});

ProblemBuilderUtil.transformClarifications(element);

runtime.listenTo('deleted-child', updateButtons);
}
4 changes: 4 additions & 0 deletions problem_builder/public/js/questionnaire_edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function QuestionnaireEdit(runtime, element) {
'use strict';
ProblemBuilderUtil.transformClarifications(element);
}
40 changes: 40 additions & 0 deletions problem_builder/public/js/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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});
}
}

};
10 changes: 9 additions & 1 deletion problem_builder/questionnaire.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,21 @@ def get_submission_display(self, submission):
return choice.content
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):
"""
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_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_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

def validate_field_data(self, validation, data):
Expand Down
4 changes: 2 additions & 2 deletions problem_builder/templates/html/ratingblock.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
</div>
<label class="choice-label" for="choice-{{ self.html_id }}-{{i}}">
{{i}}
{% if i == '1' %} - {{ self.low }}{% endif %}
{% if i == '5' %} - {{ self.high }}{% endif %}
{% if i == '1' %} - {{ self.low|safe }}{% endif %}
{% if i == '5' %} - {{ self.high|safe }}{% endif %}
</label>
<div class="choice-tips-container">
<div class="choice-tips"></div>
Expand Down
6 changes: 3 additions & 3 deletions problem_builder/templates/html/ratingblock_edit_preview.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{% load i18n %}
<p>{{ question }}</p>
<p>{{ question|safe }}</p>

<h2>{% trans "Built-in choices:" %}</h2>

<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 (3): <strong>3</strong> ({{accepted_statuses.3}})</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>

<h2>{% trans "Additional custom choices and tips:" %}</h2>
110 changes: 110 additions & 0 deletions problem_builder/tests/integration/test_clarifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# -*- 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>
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, you can put these templates into the xml_templates folder, and change the placeholders to {{django_template syntax}}, as some of the other tests do. But this way is fine too; personally I'd probably leave it as-is.


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)