Commit c87d5cc2 by Sven Marnach

Merge pull request #2 from open-craft/upstream_review

Review notes
parents ca0e6eb4 0b4a5d33
# Created by https://www.gitignore.io
tests.integration.*.png
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
......
......@@ -6,7 +6,8 @@ before_install:
- "sh -e /etc/init.d/xvfb start"
install:
- "sh install_test_deps.sh"
- "python setup.py develop"
script: pep8 poll --max-line-length=120 && pylint poll && python run_tests.py --with-coverage --cover-package=poll
notifications:
email: false
addons:
firefox: "36.0"
......@@ -22,6 +22,8 @@ and images. Formatting for images is handled by the XBlock's formatters to keep
The feedback section of a poll or survey is shown after a user has completed the block. It, along with a poll block's
question field, are intended to make full use of Markdown.
These blocks currently do not support grading.
## Installation and configuration
This XBlock relies on [Xblock-utils](https://github.com/edx-solutions/xblock-utils), which should be installed first.
......@@ -59,7 +61,7 @@ is presented to them.
![Poll example results](doc_img/poll_result.png)
The top choice's percentage is shown in *orange* while the user's selection is marked by a selected (but disabled)
The top choice's percentage is highlighted while the user's selection is marked by a selected (but disabled)
radio button on the side.
### Poll variations
......@@ -73,13 +75,14 @@ of the answers:
![Image-only poll](doc_img/img_poll.png)
This poll also contains a feedback section, which is enhanced with Markdown:
Please note that using only images is not accessible as Poll XBlock does not provide means for specifying alternate
text for images. Instead use images *and* texts:
![Image-only poll results](doc_img/img_poll_result.png)
![Image and Label label poll](doc_img/img_and_label_poll.png)
Polls may also have images and with text.
This poll also contains a feedback section, which is enhanced with Markdown:
![Image and Label label poll](doc_img/img_and_label_poll.png)
![Image-only poll results](doc_img/img_poll_result.png)
Or they may have a mix of both.
......@@ -183,8 +186,9 @@ for 'Private Results':
![Private Results](doc_img/private_results.png)
**Notes on Private Results**: Users will be able to change their vote on polls and surveys with this option enabled.
An analytics event will not be fired upon the student viewing the results, as the results are never visible. A user
will see a thank you message and any feedback provided upon submission:
An analytics event will not be fired upon the student viewing the results, as the results are never visible. A user
will see a thank you message, and optionally, any instructor-provided Feedback in an additional "Feedback" section,
when they click submit:
![Private Results Submission](doc_img/private_results_submission.png)
......@@ -211,10 +215,6 @@ or their score.
Things that could make a poll's previous answers ambiguous include adding or removing a question, or adding or
removing an answer.
## Grading
Each block has a score value of 1, credited to the student upon completion of the block.
## Analytics
Two events are fired by the XBlocks-- one for viewing the results of a poll, and one for submitting the user's choice.
......
# Installs xblock-sdk and dependencies needed to run the tests suite.
# Run this script inside a fresh virtual environment.
pip install -e git://github.com/edx/xblock-sdk.git#egg=xblock-sdk
pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements.txt
cd $VIRTUAL_ENV/src/xblock-sdk/ && { pip install -r requirements.txt; cd -; }
pip install -r $VIRTUAL_ENV/src/xblock-sdk/test-requirements.txt
python setup.py develop
pip install -r requirements.txt
......@@ -13,7 +13,7 @@
}
.poll-result-input-container {
display: table-cell;
display: inline-block;
vertical-align: middle;
}
......@@ -21,9 +21,15 @@
margin: 0 .5em 0 0;
}
.percentage-gauge-background {
position: absolute;
display: inline-block;
background-color: #fafbfc;
}
.percentage-gauge-container {
display: table-cell;
width: 100%;
display: inline-block;
width: 65%;
vertical-align: middle;
background-color: #fafbfc;
}
......@@ -45,14 +51,10 @@ li.poll-spacer {
height: .25em;
}
ul.poll-answers-results {
display: table;
}
li.poll-result {
div.poll-block ul.poll-results li.poll-result {
/* The selector above needs all three parts to trump some margin setting from the LMS. */
width: 100%;
display: table-row;
padding-bottom: .2em;
margin-bottom: 4px;
}
.poll-answer-label {
......@@ -68,14 +70,12 @@ li.poll-result {
width: 25%;
display: inline-block;
vertical-align: middle;
}
.poll-image {
margin-left: .5em;
}
li.poll-result .poll-image {
display: table-cell;
width: 22%;
display: inline-block;
margin-left: 0;
}
......@@ -92,7 +92,7 @@ li.poll-result .poll-image {
}
.poll-percent-container {
display: table-cell;
display: inline-block;
text-align: left;
padding-left: .2em;
vertical-align: middle;
......@@ -152,7 +152,7 @@ li.poll-result .poll-image {
background: none !important;
}
.survey-table .survey-option {
.survey-table .survey-option label {
text-align: center;
vertical-align: middle;
}
......
/* CSS for PollBlock Studio Menu View */
.poll-delete-answer {
clear: right;
float: right;
margin-top: 1em;
opacity: 0.5;
}
.poll-delete-answer:hover {
opacity: 1;
}
#poll-question-editor-container, #poll-feedback-editor-container{
width: 100%;
......@@ -22,10 +27,12 @@
}
.poll-move-up {
display: block;
opacity: .5;
}
.poll-move-down {
display: block;
opacity: .5;
}
......
.themed-xblock.poll-block .poll-voting-thanks span {
/* default LMS colors - contrast 11.0 */
background-color: #ffffff;
color: #3c3c3c;
}
.themed-xblock.poll-block .poll-top-choice {
/* close to LMS color for links and menu items - contrast 5.5 with white and 4.6 with e5ebee*/
color: #0070a0;
}
.themed-xblock.poll-block .poll-answer-label {
vertical-align: middle;
}
.themed-xblock.poll-block .survey-table thead tr th {
font-size: 1em;
}
.themed-xblock.poll-block .survey-row td {
padding-top: 5px;
padding-bottom: 5px;
}
/* LMS have very specific css selector that sets ~1.5em bottom margin */
.themed-xblock.poll-block table.survey-table .survey-row td p,
.themed-xblock.poll-block ul.poll-answers li.poll-answer .poll-answer p,
.themed-xblock.poll-block ul.poll-results li.poll-result .poll-answer-label p {
margin-bottom: 0;
}
.themed-xblock.poll-block ul.poll-results li.poll-spacer {
margin-bottom: 0;
}
\ No newline at end of file
......@@ -5,17 +5,17 @@
{{#each tally}}
<li class="poll-result">
<div class="poll-result-input-container">
<input id="answer-{{key}}" type="radio" disabled {{#if choice}}checked="True"{{/if}} />
<input id="answer-{{key}}" type="radio" disabled {{#if choice}}checked{{/if}} />
</div>
{{#if any_img}}
{{~#if any_img~}}
<div class="poll-image result-image">
<label for="answer-{{key}}" class="poll-image-label">
{{#if img}}
<img src="{{img}}" />
<img src="{{img}}" alt="{{img_alt}}"/>
{{/if}}
</label>
</div>
{{/if}}
</div><div class="percentage-gauge-background"></div>
{{~/if~}}
<div class="percentage-gauge-container">
<div class="percentage-gauge" style="width:{{percent}}%;">
<label class="poll-answer-label" for="answer-{{key}}">{{{answer}}}</label>
......@@ -25,10 +25,6 @@
<span class="poll-percent-display{{#if first}} poll-top-choice{{/if}}">{{percent}}%</span>
</div>
</li>
{{^last}}
<li class="poll-spacer">
</li>
{{/last}}
{{/each}}
</ul>
<input class="input-main" type="button" name="poll-submit" value="Submit" disabled>
......
......@@ -2,21 +2,29 @@
{{#each items}}
<li class="field comp-setting-entry is-set poll-{{noun}}-studio-item">
<div class="wrapper-comp-setting">
<div class="poll-move">
<button class="poll-move-up">&#9650;<span class="sr">&nbsp;move poll up</span></button>
<button class="poll-move-down">&#9660;<span class="sr">&nbsp;move poll down</span></button>
</div>
<button class="button action-button poll-delete-answer">Delete</button>
<label class="label setting-label poll-setting-label" for="{{noun}}-label-{{key}}">{{noun}}</label>
<input class="input setting-input" name="{{noun}}-label-{{key}}" id="{{noun}}-label-{{key}}" value="{{text}}" type="text" /><br />
{{#if image}}
<label class="label setting-label" for="{{noun}}-img-{{key}}">Image URL</label>
<input class="input setting-input" name="{{noun}}-img-{{key}}" id="{{noun}}-img-{{key}}" value="{{img}}" type="text" />
<input class="input setting-input" name="{{noun}}-img-{{key}}" id="{{noun}}-img-{{key}}" value="{{img}}" type="text" /><br />
<label class="label setting-label" for="{{noun}}-img_alt-{{key}}">Image alternative text</label>
<input class="input setting-input" name="{{noun}}-img_alt-{{key}}" id="{{noun}}-img_alt-{{key}}" value="{{img_alt}}" type="text" /><br />
{{/if}}
<div class="poll-move">
<div class="poll-move-up">&#9650;</div>
<div class="poll-move-down">&#9660;</div>
</div>
</div>
<span class="tip setting-help">
{{#if image}}This must have an image URL or text, and can have both.{{/if}}
You can make limited use of Markdown in answer texts, preferably only bold and italics.
</span>
<span class="tip setting-help">
{{#if image}}
This must have an image URL or text, and can have both. If you add an image, you must also provide an alternative text
that describes the image in a way that would allow someone to answer the poll if the image did not load.
{{/if}}
</span>
<a href="#" class="button action-button poll-delete-answer" onclick="return false;">Delete</a>
</li>
{{/each}}
</script>
......@@ -14,7 +14,7 @@
<td class="survey-question">
{{#if img}}
<div class="poll-image-td">
<img src="{{img}}" />
<img src="{{img}}" alt="img_alt"/>
</div>
{{/if}}
{{{label}}}
......
{{ js_template|safe }}
<div class="poll-block" data-private="{% if private_results %}1{% endif %}" data-can-vote="{% if can_vote %}1{% endif %}">
<div class="poll-block themed-xblock" data-private="{% if private_results %}1{% endif %}" data-can-vote="{% if can_vote %}1{% endif %}">
{# If no form is present, the Javascript will load the results instead. #}
{% if private_results or not choice %}
<h3 class="poll-header">{{display_name}}</h3>
......@@ -14,9 +14,7 @@
{% if value.img %}
<div class="poll-image">
<label for="{{url_name}}-answer-{{key}}" class="poll-image-label">
{% if value.img %}
<img src="{{value.img}}" />
{% endif %}
<img src="{{value.img}}" alt="{{value.img_alt|default_if_none:''}}"/>
</label>
</div>
{% endif %}
......@@ -42,7 +40,7 @@
{% endif %}
{% if can_view_private_results %}
<div class="view-results-button-wrapper"><a class="view-results-button">View results</a></div>
<div class="view-results-button-wrapper"><button class="view-results-button">View results</button></div>
{% endif %}
{% endif %}
</div>
......@@ -14,18 +14,20 @@
<h2><label for="poll-question-editor">Question/Prompt</label></h2>
<a href="//daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Syntax</a> is supported.
<div id="poll-question-editor-container">
<textarea class="input setting-input" name="question" id="poll-question-editor">{{question}}</textarea>
<textarea class="input setting-input" name="question" id="poll-question-editor"
aria-describedby="poll-question-editor-help">{{question}}</textarea>
</div>
<span class="tip setting-help">Enter the prompt for the user.</span>
<span class="tip setting-help" id="poll-question-editor-help">Enter the prompt for the user.</span>
</li>
{% endif %}
<li class="field comp-setting-entry is-set">
<h2><label for="poll-feedback-editor">Feedback</label></h2>
<a href="//daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Syntax</a> is supported.
<div id="poll-feedback-editor-container">
<textarea class="input setting-input" name="feedback" id="poll-feedback-editor">{{feedback}}</textarea>
<textarea class="input setting-input" name="feedback" id="poll-feedback-editor"
aria-describedby="poll-feedback-editor-help">{{feedback}}</textarea>
</div>
<span class="tip setting-help">
<span class="tip setting-help" id="poll-feedback-editor-help">
This text will be displayed for the user as some extra feedback after they have
submitted their response to the poll.
</span>
......@@ -33,22 +35,24 @@
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label poll-setting-label" for="poll-private-results">Private Results</label>
<select id="poll-private-results" class="input setting-input" name="private_results">
<select id="poll-private-results" class="input setting-input" name="private_results"
aria-describedby="poll-private-results-help">
<!-- So far as I can see, there's not a proper style for checkboxes. LTI module does it this way. -->
<option value="true" {% if private_results %} selected{% endif %}>True</option>
<option value="false" {% if not private_results %} selected{% endif %}>False</option>
</select>
</div>
<span class="tip setting-help">
If this is set True, don't display results of the poll to the user.
<span class="tip setting-help" id="poll-private-results-help">
If this is set to True, don't display results of the poll to the user.
</span>
</li>
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label poll-setting-label" for="poll-max-submissions">Maximum Submissions</label>
<input id="poll-max-submissions" type="number" min="0" step="1" value="{{ max_submissions }}"/>
<input id="poll-max-submissions" type="number" min="0" step="1" value="{{ max_submissions }}"
aria-describedby="poll-max-submissions-help"/>
</div>
<span class="tip setting-help">
<span class="tip setting-help" id="poll-max-submissions-help">
Maximum number of times a user may submit a poll. <strong>Setting this to a value other than 1 will imply that
'Private Results' should be true.</strong> Setting it to 0 will allow infinite resubmissions.
</span>
......
{{ js_template|safe }}
<div class="poll-block" data-private="{% if private_results %}1{% endif %}" data-can-vote="{% if can_vote %}1{% endif %}">
<div class="poll-block themed-xblock" data-private="{% if private_results %}1{% endif %}" data-can-vote="{% if can_vote %}1{% endif %}">
{# If no form is present, the Javascript will load the results instead. #}
{% if not choices or private_results %}
<h3 class="poll-header">{{block_name}}</h3>
......@@ -18,14 +18,17 @@
<td class="survey-question">
{% if question.img %}
<div class="poll-image-td">
<img src="{{question.img}}" />
<img src="{{question.img}}" alt="{{question.img_alt|default_if_none:''}}"/>
</div>
{% endif %}
{{question.label|safe}}
</td>
{% for answer, answer_details in answers %}
{% for answer, label in answers %}
<td class="survey-option">
<label>
<input type="radio" name="{{key}}" value="{{answer}}"{% if question.choice == answer %} checked{% endif %}/>
<span class="sr">{{label}}</span>
</label>
</td>
{% endfor %}
</tr>
......@@ -49,7 +52,7 @@
{% endif %}
{% if can_view_private_results %}
<div class="view-results-button-wrapper"><a class="view-results-button">View results</a></div>
<div class="view-results-button-wrapper"><button class="view-results-button">View results</button></div>
{% endif %}
{% endif %}
</div>
......@@ -142,6 +142,31 @@ function PollUtil (runtime, element, pollType) {
this.getResults = function () {
// Used if results are not private, to show the user how other students voted.
function adjustGaugeBackground() {
// Adjust the height of the grey background of the the percentage gauges. This
// couldn't be achieved with CSS.
$('ul.poll-results > li', element).each(function() {
var height = 0, width;
$(this).children().each(function() {
height = Math.max(height, $(this).height());
});
width = $('.percentage-gauge-container', this).width();
$('.percentage-gauge-background', this).height(height).width(width);
});
}
function whenImagesLoaded(callback) {
// Wait for all images to be loaded, then call callback.
var missingImages = 1;
$('img', element).each(function() {
if ($(this).height() == 0) {
missingImages++;
$(this).load(function() {
if (--missingImages == 0) callback();
});
}
});
if (--missingImages == 0) callback();
}
$.ajax({
// Semantically, this would be better as GET, but we can use helper
// functions with POST.
......@@ -150,8 +175,9 @@ function PollUtil (runtime, element, pollType) {
data: JSON.stringify({}),
success: function (data) {
$('div.poll-block', element).html(self.resultsTemplate(data));
whenImagesLoaded(adjustGaugeBackground);
}
})
});
};
this.enableSubmit = function () {
......
......@@ -93,7 +93,7 @@ function PollEditUtil(runtime, element, pollType) {
// A 'key' element will have to be added after the fact, since it needs to be
// generated with the current time.
return self.extend({'text': '', 'img': ''}, extra)
return self.extend({'text': '', 'img': '', 'img_alt': ''}, extra)
};
this.empowerDeletes = function (scope) {
......@@ -175,16 +175,19 @@ function PollEditUtil(runtime, element, pollType) {
this.gather = function (scope, tracker, data, prefix, field) {
var key = 'label';
var name = scope.name.replace(prefix + '-', '');
if (name.indexOf('img-') == 0){
if (name.indexOf('img_alt-') == 0) {
name = name.replace('img_alt-', '');
key = 'img_alt'
} else if (name.indexOf('img-') == 0) {
name = name.replace('img-', '');
key = 'img'
} else if (name.indexOf('label-') == 0){
} else if (name.indexOf('label-') == 0) {
name = name.replace('label-', '');
}
if (! (scope.name.indexOf(prefix + '-') >= 0)) {
return
}
if (tracker.indexOf(name) == -1){
if (tracker.indexOf(name) == -1) {
tracker.push(name);
data[field].push({'key': name})
}
......
-e .
markdown
-e git+https://github.com/edx-solutions/xblock-utils.git#egg=xblock-utils
-e git://github.com/edx/xblock-utils.git@213a97a50276d6a2504d8133650b2930ead357a0#egg=xblock-utils
-e git://github.com/nosedjango/nosedjango.git@ed7d7f9aa969252ff799ec159f828eaa8c1cbc5a#egg=nosedjango-dev
ddt
mock
-e .
File mode changed from 100644 to 100755
......@@ -50,13 +50,11 @@ setup(
'poll',
],
install_requires=[
'XBlock',
'markdown',
'xblock-utils',
'ddt',
'mock',
],
dependency_links=['http://github.com/edx-solutions/xblock-utils/tarball/master#egg=xblock-utils'],
dependency_links=['http://github.com/edx/xblock-utils/tarball/master#egg=xblock-utils'],
entry_points={
'xblock.v1': [
'poll = poll:PollBlock',
......
......@@ -121,7 +121,7 @@ class TestPrivateResults(PollBaseTest):
@stub_view_permission(True)
def test_results_button(self, page_name, names):
self.go_to_page(page_name)
button = self.browser.find_element_by_css_selector('a.view-results-button')
button = self.browser.find_element_by_css_selector('.view-results-button')
button.click()
self.wait_until_exists('.poll-results')
self.wait_until_exists('.poll-footnote')
<poll tally='{"red": 20, "fennec": 29, "kit": 15, "arctic" : 35}'
question="## What is your favorite kind of fox?"
answers='[["red", {"label": "Red Fox", "img": "../img/red_fox.png"}], ["fennec", {"label": "Fennec Fox", "img": "../img/fennec_fox.png"}], ["kit", {"label": "Kit Fox", "img": "../img/kit_fox.png"}], ["arctic", {"label": "Arctic fox", "img": "../img/arctic_fox.png"}]]' />
answers='[["red", {"label": "Red Fox", "img": "../img/red_fox.png", "img_alt": "Red Fox"}], ["fennec", {"label": "Fennec Fox", "img": "../img/fennec_fox.png", "img_alt": "Fennec Fox"}], ["kit", {"label": "Kit Fox", "img": "../img/kit_fox.png", "img_alt": "Kit Fox"}], ["arctic", {"label": "Arctic Fox", "img": "../img/arctic_fox.png", "img_alt": "Arctic Fox"}]]' />
<poll tally="{'red': 20, 'fennec': 29, 'kit': 15, 'arctic' : 35}"
question="## What is your favorite kind of fox?"
answers='[["red", {"label": "Red Fox", "img": null}], ["fennec", {"label": "Fennec Fox", "img": "../img/fennec_fox.png"}], ["kit", {"label": "Kit Fox", "img": null}], ["arctic", {"label": "Arctic fox", "img": null}]]' />
answers='[["red", {"label": "Red Fox", "img": null}], ["fennec", {"label": "Fennec Fox", "img": "../img/fennec_fox.png", "img_alt": "Fennec Fox"}], ["kit", {"label": "Kit Fox", "img": null}], ["arctic", {"label": "Arctic fox", "img": null}]]' />
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