Commit 2255b5c8 by Chris Rodriguez

AC-552 word cloud accessibility updates

parent a81b3b36
...@@ -188,6 +188,39 @@ ...@@ -188,6 +188,39 @@
} }
} }
// LMS-style CAPA button for consistency with LMS buttons
%btn-lms-style {
border: 1px solid $btn-lms-border;
border-radius: 3px;
box-shadow: inset 0 1px 0 0 $white;
color: $gray-d3;
display: inline-block;
font-size: inherit;
font-weight: bold;
background-color: $btn-lms-background;
background-image: -webkit-linear-gradient($btn-lms-background,$btn-lms-gradient);
background-image: linear-gradient($btn-lms-background,$btn-lms-gradient);
padding: 7px 18px;
text-decoration: none;
text-shadow: 0 1px 0 $btn-lms-shadow;
background-clip: padding-box;
font-size: 0.8125em;
&:focus,
&:hover {
box-shadow: inset 0 1px 0 0 $btn-lms-shadow-hover;
cursor: pointer;
background-color: $btn-lms-background-hover;
background-image: -webkit-linear-gradient($btn-lms-background-hover,$btn-lms-gradient-hover);
background-image: linear-gradient($btn-lms-background-hover,$btn-lms-gradient-hover);
}
&:active {
border: 1px solid $btn-lms-border;
box-shadow: inset 0 0 8px 4px $btn-lms-shadow-active,inset 0 0 8px 4px $btn-lms-shadow-active;
}
}
// +Button Element // +Button Element
// ==================== // ====================
.button { .button {
......
...@@ -248,6 +248,15 @@ ...@@ -248,6 +248,15 @@
color: $color-visibility-set; color: $color-visibility-set;
} }
} }
.action {
.save {
// taking styles from LMS for these Save buttons to maintain consistency
// there is no studio-specific style for these LMS-styled buttons
@extend %btn-lms-style;
}
}
} }
// +Messaging - Xblocks // +Messaging - Xblocks
......
...@@ -82,6 +82,17 @@ $gray-d2: shade($gray,40%); ...@@ -82,6 +82,17 @@ $gray-d2: shade($gray,40%);
$gray-d3: shade($gray,60%); $gray-d3: shade($gray,60%);
$gray-d4: shade($gray,80%); $gray-d4: shade($gray,80%);
// These define button styles similar to LMS
// The goal here is consistency (until we can overhaul all of this...)
$btn-lms-border: #d2c9c9;
$btn-lms-background: #f1f1f1;
$btn-lms-gradient: #d9d1d1;
$btn-lms-shadow: #fcfbfb;
$btn-lms-shadow-hover: #fefefe;
$btn-lms-background-hover: #e4e4e4;
$btn-lms-gradient-hover: #d1c9c9;
$btn-lms-shadow-active: #cac2c2;
$blue: rgb(0, 159, 230); $blue: rgb(0, 159, 230);
$blue-l1: tint($blue,20%); $blue-l1: tint($blue,20%);
$blue-l2: tint($blue,40%); $blue-l2: tint($blue,40%);
......
...@@ -10,12 +10,15 @@ ...@@ -10,12 +10,15 @@
.result_cloud_section.active { .result_cloud_section.active {
display: block; display: block;
width: 635px; width: 100%;
height: auto; height: auto;
margin-left: auto; margin-top: 1em;
margin-right: auto;
h3 {
font-size: 100%;
}
} }
.your_words{ .your_words{
font-size: 0.85em; font-size: 0.85em;
display: block; display: block;
} }
\ No newline at end of file
...@@ -12,106 +12,110 @@ ...@@ -12,106 +12,110 @@
*/ */
(function(requirejs, require, define) { (function(requirejs, require, define) {
define('WordCloudMain', [], function() { 'use strict';
/** define('WordCloudMain', [
* @function WordCloudMain 'gettext',
* 'edx-ui-toolkit/js/utils/html-utils'
* This function will process all the attributes from the DOM element passed, taking all of ], function(gettext, HtmlUtils) {
* the configuration attributes. It will either then attach a callback handler for the click function generateUniqueId(wordCloudId, counter) {
* event on the button in the case when the user needs to enter words, or it will call the return '_wc_' + wordCloudId + '_' + counter;
* appropriate mehtod to generate and render a word cloud from user's enetered words along with }
* all of the other words.
* /**
* @constructor * @function WordCloudMain
* *
* @param {jQuery} el DOM element where the word cloud will be processed and created. * This function will process all the attributes from the DOM element passed, taking all of
*/ * the configuration attributes. It will either then attach a callback handler for the click
* event on the button in the case when the user needs to enter words, or it will call the
* appropriate mehtod to generate and render a word cloud from user's enetered words along with
* all of the other words.
*
* @constructor
*
* @param {jQuery} el DOM element where the word cloud will be processed and created.
*/
var WordCloudMain = function(el) { var WordCloudMain = function(el) {
var _this = this; var _this = this;
this.wordCloudEl = $(el).find('.word_cloud'); this.wordCloudEl = $(el).find('.word_cloud');
// Get the URL to which we will post the users words. // Get the URL to which we will post the users words.
this.ajax_url = this.wordCloudEl.data('ajax-url'); this.ajax_url = this.wordCloudEl.data('ajax-url');
// Dimensions of the box where the word cloud will be drawn. // Dimensions of the box where the word cloud will be drawn.
this.width = 635; this.width = 635;
this.height = 635; this.height = 635;
// Hide WordCloud container before Ajax request done // Hide WordCloud container before Ajax request done
this.wordCloudEl.hide(); this.wordCloudEl.hide();
// Retriveing response from the server as an AJAX request. Attach a callback that will // Retriveing response from the server as an AJAX request. Attach a callback that will
// be fired on server's response. // be fired on server's response.
$.postWithPrefix( $.postWithPrefix(
_this.ajax_url + '/' + 'get_state', null, _this.ajax_url + '/get_state', null,
function(response) { function(response) {
if (response.status !== 'success') { if (response.status !== 'success') {
console.log('ERROR: ' + response.error); return;
}
return; _this.configJson = response;
} }
)
.done(function() {
// Show WordCloud container after Ajax request done
_this.wordCloudEl.show();
_this.configJson = response; if (_this.configJson && _this.configJson.submitted) {
} _this.showWordCloud(_this.configJson);
)
.done(function() {
// Show WordCloud container after Ajax request done
_this.wordCloudEl.show();
if (_this.configJson && _this.configJson.submitted) {
_this.showWordCloud(_this.configJson);
return; return;
} }
}); });
$(el).find('input.save').on('click', function() { $(el).find('.save').on('click', function() {
_this.submitAnswer(); _this.submitAnswer();
}); });
}; // End-of: var WordCloudMain = function (el) { }; // End-of: var WordCloudMain = function(el) {
/** /**
* @function submitAnswer * @function submitAnswer
* *
* Callback to be executed when the user eneter his words. It will send user entries to the * Callback to be executed when the user eneter his words. It will send user entries to the
* server, and upon receiving correct response, will call the function to generate the * server, and upon receiving correct response, will call the function to generate the
* word cloud. * word cloud.
*/ */
WordCloudMain.prototype.submitAnswer = function() { WordCloudMain.prototype.submitAnswer = function() {
var _this = this, var _this = this,
data = {'student_words': []}; data = {'student_words': []};
// Populate the data to be sent to the server with user's words. // Populate the data to be sent to the server with user's words.
this.wordCloudEl.find('input.input-cloud').each(function(index, value) { this.wordCloudEl.find('input.input-cloud').each(function(index, value) {
data.student_words.push($(value).val()); data.student_words.push($(value).val());
}); });
// Send the data to the server as an AJAX request. Attach a callback that will // Send the data to the server as an AJAX request. Attach a callback that will
// be fired on server's response. // be fired on server's response.
$.postWithPrefix( $.postWithPrefix(
_this.ajax_url + '/' + 'submit', $.param(data), _this.ajax_url + '/submit', $.param(data),
function(response) { function(response) {
if (response.status !== 'success') { if (response.status !== 'success') {
console.log('ERROR: ' + response.error); return;
}
return; _this.showWordCloud(response);
} }
);
_this.showWordCloud(response); }; // End-of: WordCloudMain.prototype.submitAnswer = function() {
}
); /**
}; // End-of: WordCloudMain.prototype.submitAnswer = function () { * @function showWordCloud
*
/** * @param {object} response The response from the server that contains the user's entered words
* @function showWordCloud * along with all of the top words.
* *
* @param {object} response The response from the server that contains the user's entered words * This function will set up everything for d3 and launch the draw method. Among other things,
* along with all of the top words. * iw will determine maximum word size.
* */
* This function will set up everything for d3 and launch the draw method. Among other things,
* iw will determine maximum word size.
*/
WordCloudMain.prototype.showWordCloud = function(response) { WordCloudMain.prototype.showWordCloud = function(response) {
var words, var words,
_this = this, _this = this,
...@@ -124,9 +128,9 @@ ...@@ -124,9 +128,9 @@
minSize = 10000; minSize = 10000;
scaleFactor = 1; scaleFactor = 1;
maxFontSize = 200; maxFontSize = 200;
minFontSize = 15; minFontSize = 16;
// Find the word with the maximum percentage. I.e. the most popular word. // Find the word with the maximum percentage. I.e. the most popular word.
$.each(words, function(index, word) { $.each(words, function(index, word) {
if (word.size > maxSize) { if (word.size > maxSize) {
maxSize = word.size; maxSize = word.size;
...@@ -136,11 +140,11 @@ ...@@ -136,11 +140,11 @@
} }
}); });
// Find the longest word, and calculate the scale appropriately. This is // Find the longest word, and calculate the scale appropriately. This is
// required so that even long words fit into the drawing area. // required so that even long words fit into the drawing area.
// //
// This is a fix for: if the word is very long and/or big, it is discarded by // This is a fix for: if the word is very long and/or big, it is discarded by
// for unknown reason. // for unknown reason.
$.each(words, function(index, word) { $.each(words, function(index, word) {
var tempScaleFactor = 1.0, var tempScaleFactor = 1.0,
size = ((word.size / maxSize) * maxFontSize); size = ((word.size / maxSize) * maxFontSize);
...@@ -154,136 +158,192 @@ ...@@ -154,136 +158,192 @@
} }
}); });
// Update the maximum font size based on the longest word. // Update the maximum font size based on the longest word.
maxFontSize *= scaleFactor; maxFontSize *= scaleFactor;
// Generate the word cloud. // Generate the word cloud.
d3.layout.cloud().size([this.width, this.height]) d3.layout.cloud().size([this.width, this.height])
.words(words) .words(words)
.rotate(function() { .rotate(function() {
return Math.floor((Math.random() * 2)) * 90; return Math.floor((Math.random() * 2)) * 90;
}) })
.font('Impact') .font('Impact')
.fontSize(function(d) { .fontSize(function(d) {
var size = (d.size / maxSize) * maxFontSize; var size = (d.size / maxSize) * maxFontSize;
size = size >= minFontSize ? size : minFontSize; size = size >= minFontSize ? size : minFontSize;
return size; return size;
}) })
.on('end', function(words, bounds) { .on('end', function(words, bounds) { // eslint-disable-line no-shadow
// Draw the word cloud. // Draw the word cloud.
_this.drawWordCloud(response, words, bounds); _this.drawWordCloud(response, words, bounds);
}) })
.start(); .start();
}; // End-of: WordCloudMain.prototype.showWordCloud = function (response) { }; // End-of: WordCloudMain.prototype.showWordCloud = function(response) {
/** /**
* @function drawWordCloud * @function drawWordCloud
* *
* This function will be called when d3 has finished initing the state for our word cloud, * This function will be called when d3 has finished initing the state for our word cloud,
* and it is ready to hand off the process to the drawing routine. Basically set up everything * and it is ready to hand off the process to the drawing routine. Basically set up everything
* necessary for the actual drwing of the words. * necessary for the actual drwing of the words.
* *
* @param {object} response The response from the server that contains the user's entered words * @param {object} response The response from the server that contains the user's entered words
* along with all of the top words. * along with all of the top words.
* *
* @param {array} words An array of objects. Each object must have two properties. One property * @param {array} words An array of objects. Each object must have two properties. One property
* is 'text' (the actual word), and the other property is 'size' which represents the number that the * is 'text' (the actual word), and the other property is 'size' which represents the number that the
* word was enetered by the students. * word was enetered by the students.
* *
* @param {array} bounds An array of two objects. First object is the top-left coordinates of the bounding * @param {array} bounds An array of two objects. First object is the top-left coordinates of the bounding
* box where all of the words fir, second object is the bottom-right coordinates of the bounding box. Each * box where all of the words fir, second object is the bottom-right coordinates of the bounding box. Each
* coordinate object contains two properties: 'x', and 'y'. * coordinate object contains two properties: 'x', and 'y'.
*/ */
WordCloudMain.prototype.drawWordCloud = function(response, words, bounds) { WordCloudMain.prototype.drawWordCloud = function(response, words, bounds) {
// Color words in different colors. // Color words in different colors.
var fill = d3.scale.category20(), var fill = d3.scale.category20(),
// Will be populated by words the user enetered. // Will be populated by words the user enetered.
studentWordsKeys = [], studentWordsKeys = [],
// Comma separated string of user enetered words. // Comma separated string of user enetered words.
studentWordsStr, studentWordsStr,
// By default we do not scale. // By default we do not scale.
scale = 1, scale = 1,
// Caсhing of DOM element // Caсhing of DOM element
cloudSectionEl = this.wordCloudEl.find('.result_cloud_section'), cloudSectionEl = this.wordCloudEl.find('.result_cloud_section'),
// Needed for caсhing of d3 group elements // Needed for caсhing of d3 group elements
groupEl; groupEl,
// If bounding rectangle is given, scale based on the bounding box of all the words. // Iterator for word cloud count for uniqueness
wcCount = 0;
// If bounding rectangle is given, scale based on the bounding box of all the words.
if (bounds) { if (bounds) {
scale = 0.5 * Math.min( scale = 0.5 * Math.min(
this.width / Math.abs(bounds[1].x - this.width / 2), this.width / Math.abs(bounds[1].x - this.width / 2),
this.width / Math.abs(bounds[0].x - this.width / 2), this.width / Math.abs(bounds[0].x - this.width / 2),
this.height / Math.abs(bounds[1].y - this.height / 2), this.height / Math.abs(bounds[1].y - this.height / 2),
this.height / Math.abs(bounds[0].y - this.height / 2) this.height / Math.abs(bounds[0].y - this.height / 2)
); );
} }
$.each(response.student_words, function(word, stat) { $.each(response.student_words, function(word, stat) {
var percent = (response.display_student_percents) ? ' ' + (Math.round(100 * (stat / response.total_count))) + '%' : ''; var percent = (response.display_student_percents) ? ' ' + (Math.round(100 * (stat / response.total_count))) + '%' : '';
studentWordsKeys.push('<strong>' + word + '</strong>' + percent); studentWordsKeys.push(HtmlUtils.interpolateHtml(
'{listStart}{startTag}{word}{endTag}{percent}{listEnd}',
{
listStart: HtmlUtils.HTML('<li>'),
startTag: HtmlUtils.HTML('<strong>'),
word: word,
endTag: HtmlUtils.HTML('</strong>'),
percent: percent,
listEnd: HtmlUtils.HTML('</li>')
}
).toString());
}); });
studentWordsStr = '' + studentWordsKeys.join(', ');
studentWordsStr = '' + studentWordsKeys.join('');
cloudSectionEl cloudSectionEl
.addClass('active') .addClass('active');
.find('.your_words').html(studentWordsStr)
.end() HtmlUtils.setHtml(
.find('.total_num_words').html(response.total_count); cloudSectionEl.find('.your_words'),
HtmlUtils.HTML(studentWordsStr)
);
HtmlUtils.setHtml(
cloudSectionEl.find('.your_words').end().find('.total_num_words'),
HtmlUtils.interpolateHtml(
gettext('{start_strong}{total}{end_strong} words submitted in total.'),
{
start_strong: HtmlUtils.HTML('<strong>'),
end_strong: HtmlUtils.HTML('</strong>'),
total: response.total_count
}
)
);
$(cloudSectionEl.attr('id') + ' .word_cloud').empty(); $(cloudSectionEl.attr('id') + ' .word_cloud').empty();
// Actual drawing of word cloud. // Actual drawing of word cloud.
groupEl = d3.select('#' + cloudSectionEl.attr('id') + ' .word_cloud').append('svg') groupEl = d3.select('#' + cloudSectionEl.attr('id') + ' .word_cloud').append('svg')
.attr('width', this.width) .attr('width', this.width)
.attr('height', this.height) .attr('height', this.height)
.append('g') .append('g')
.attr('transform', 'translate(' + (0.5 * this.width) + ',' + (0.5 * this.height) + ')') .attr('transform', 'translate(' + (0.5 * this.width) + ',' + (0.5 * this.height) + ')')
.selectAll('text') .selectAll('text')
.data(words) .data(words)
.enter().append('g'); .enter()
.append('g')
.attr('data-id', function() {
wcCount = wcCount + 1;
return wcCount;
})
.attr('aria-describedby', function() {
return HtmlUtils.interpolateHtml(
gettext('text_word_{uniqueId} title_word_{uniqueId}'),
{
uniqueId: generateUniqueId(cloudSectionEl.attr('id'), $(this).data('id'))
}
);
});
groupEl groupEl
.append('title') .append('title')
.text(function(d) { .attr('id', function() {
var res = ''; return HtmlUtils.interpolateHtml(
gettext('title_word_{uniqueId}'),
$.each(response.top_words, function(index, value) { {
if (value.text === d.text) { uniqueId: generateUniqueId(cloudSectionEl.attr('id'), $(this).parent().data('id'))
res = value.percent + '%'; }
);
return; })
} .text(function(d) {
var res = '';
$.each(response.top_words, function(index, value) {
if (value.text === d.text) {
res = value.percent + '%';
return;
}
});
return res;
}); });
return res;
});
groupEl groupEl
.append('text') .append('text')
.style('font-size', function(d) { .attr('id', function() {
return d.size + 'px'; return HtmlUtils.interpolateHtml(
}) gettext('text_word_{uniqueId}'),
.style('font-family', 'Impact') {
.style('fill', function(d, i) { uniqueId: generateUniqueId(cloudSectionEl.attr('id'), $(this).parent().data('id'))
return fill(i); }
}) );
.attr('text-anchor', 'middle') })
.attr('transform', function(d) { .style('font-size', function(d) {
return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')scale(' + scale + ')'; return d.size + 'px';
}) })
.text(function(d) { .style('font-family', 'Impact')
return d.text; .style('fill', function(d, i) {
}); return fill(i);
}; // End-of: WordCloudMain.prototype.drawWordCloud = function (words, bounds) { })
.attr('text-anchor', 'middle')
.attr('transform', function(d) {
return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')scale(' + scale + ')';
})
.text(function(d) {
return d.text;
});
}; // End-of: WordCloudMain.prototype.drawWordCloud = function(words, bounds) {
return WordCloudMain; return WordCloudMain;
}); // End-of: define('WordCloudMain', [], function () { }); // End-of: define('WordCloudMain', [], function() {
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) { }(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function(requirejs, require, define) {
...@@ -37,20 +37,25 @@ class WordCloudFields(object): ...@@ -37,20 +37,25 @@ class WordCloudFields(object):
"""XFields for word cloud.""" """XFields for word cloud."""
display_name = String( display_name = String(
display_name=_("Display Name"), display_name=_("Display Name"),
help=_("Display name for this module"), help=_("The label for this word cloud on the course page."),
scope=Scope.settings, scope=Scope.settings,
default="Word cloud" default="Word cloud"
) )
instructions = String(
display_name=_("Instructions"),
help=_("Add instructions to help learners understand how to use the word cloud. Clear instructions are important, especially for learners who have accessibility requirements."), # nopep8 pylint: disable=C0301
scope=Scope.settings,
)
num_inputs = Integer( num_inputs = Integer(
display_name=_("Inputs"), display_name=_("Inputs"),
help=_("Number of text boxes available for students to input words/sentences."), help=_("The number of text boxes available for learners to add words and sentences."),
scope=Scope.settings, scope=Scope.settings,
default=5, default=5,
values={"min": 1} values={"min": 1}
) )
num_top_words = Integer( num_top_words = Integer(
display_name=_("Maximum Words"), display_name=_("Maximum Words"),
help=_("Maximum number of words to be displayed in generated word cloud."), help=_("The maximum number of words displayed in the generated word cloud."),
scope=Scope.settings, scope=Scope.settings,
default=250, default=250,
values={"min": 1} values={"min": 1}
...@@ -64,7 +69,7 @@ class WordCloudFields(object): ...@@ -64,7 +69,7 @@ class WordCloudFields(object):
# Fields for descriptor. # Fields for descriptor.
submitted = Boolean( submitted = Boolean(
help=_("Whether this student has posted words to the cloud."), help=_("Whether this learner has posted words to the cloud."),
scope=Scope.user_state, scope=Scope.user_state,
default=False default=False
) )
...@@ -74,7 +79,7 @@ class WordCloudFields(object): ...@@ -74,7 +79,7 @@ class WordCloudFields(object):
default=[] default=[]
) )
all_words = Dict( all_words = Dict(
help=_("All possible words from all students."), help=_("All possible words from all learners."),
scope=Scope.user_state_summary scope=Scope.user_state_summary
) )
top_words = Dict( top_words = Dict(
...@@ -235,11 +240,14 @@ class WordCloudModule(WordCloudFields, XModule): ...@@ -235,11 +240,14 @@ class WordCloudModule(WordCloudFields, XModule):
def get_html(self): def get_html(self):
"""Template rendering.""" """Template rendering."""
context = { context = {
'element_id': self.location.html_id(),
'element_class': self.location.category,
'ajax_url': self.system.ajax_url, 'ajax_url': self.system.ajax_url,
'display_name': self.display_name,
'display_name_default': WordCloudFields.display_name.default,
'instructions': self.instructions,
'element_class': self.location.category,
'element_id': self.location.html_id(),
'num_inputs': self.num_inputs, 'num_inputs': self.num_inputs,
'submitted': self.submitted 'submitted': self.submitted,
} }
self.content = self.system.render_template('word_cloud.html', context) self.content = self.system.render_template('word_cloud.html', context)
return self.content return self.content
......
...@@ -942,8 +942,8 @@ class SpecialExamsPageAttemptsSection(PageObject): ...@@ -942,8 +942,8 @@ class SpecialExamsPageAttemptsSection(PageObject):
Clicks the "x" to remove the Student's attempt. Clicks the "x" to remove the Student's attempt.
""" """
with self.handle_alert(confirm=True): with self.handle_alert(confirm=True):
self.q(css="a.remove-attempt").first.click() self.q(css=".remove-attempt").first.click()
self.wait_for_element_absence("a.remove-attempt", "exam attempt") self.wait_for_element_absence(".remove-attempt", "exam attempt")
class DataDownloadPage(PageObject): class DataDownloadPage(PageObject):
......
...@@ -20,7 +20,7 @@ def view_word_cloud(_step): ...@@ -20,7 +20,7 @@ def view_word_cloud(_step):
@step('I press the Save button') @step('I press the Save button')
def press_the_save_button(_step): def press_the_save_button(_step):
button_css = '.input_cloud_section input.save' button_css = '.input_cloud_section .save'
world.css_click(button_css) world.css_click(button_css)
......
...@@ -241,14 +241,16 @@ class TestWordCloud(BaseTestXmodule): ...@@ -241,14 +241,16 @@ class TestWordCloud(BaseTestXmodule):
) )
def test_word_cloud_constructor(self): def test_word_cloud_constructor(self):
"""Make sure that all parameters extracted correclty from xml""" """Make sure that all parameters extracted correctly from xml"""
fragment = self.runtime.render(self.item_descriptor, STUDENT_VIEW) fragment = self.runtime.render(self.item_descriptor, STUDENT_VIEW)
expected_context = { expected_context = {
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url, 'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url,
'display_name': self.item_descriptor.display_name,
'display_name_default': 'Word cloud',
'instructions': self.item_descriptor.instructions,
'element_class': self.item_descriptor.location.category, 'element_class': self.item_descriptor.location.category,
'element_id': self.item_descriptor.location.html_id(), 'element_id': self.item_descriptor.location.html_id(),
'num_inputs': 5, # default value 'num_inputs': 5, # default value
'submitted': False # default value 'submitted': False, # default value
} }
self.assertEqual(fragment.content, self.runtime.render_template('word_cloud.html', expected_context)) self.assertEqual(fragment.content, self.runtime.render_template('word_cloud.html', expected_context))
<%page expression_filter="h"/>
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<section <div
id="word_cloud_${element_id}" id="word_cloud_${element_id}"
class="${element_class}" class="${element_class}"
data-ajax-url="${ajax_url}" data-ajax-url="${ajax_url}"
> >
<section class="input_cloud_section"> % if display_name:
<h3 class="hd hd-3" id="word_cloud_${element_id}_heading">${display_name}</h3>
% endif
% if instructions is not None:
<div class="input_cloud_section" role="group" aria-labelledby="word_cloud_${element_id}_instructions">
% elif display_name:
<div class="input_cloud_section" role="group" aria-labelledby="word_cloud_${element_id}_heading">
% else:
<div class="input_cloud_section" role="group">
% endif
% if instructions is not None:
<div class="input_cloud_instructions" id="word_cloud_${element_id}_instructions">
${instructions}
</div>
% endif
% for row in range(num_inputs): % for row in range(num_inputs):
<input <label>
class="input-cloud" <span class="sr">${_('{num} of {total}').format(num=row+1, total=num_inputs)}</span>
${'style="display: none;"' if submitted else ''} <input
type="text" class="input-cloud"
size="40" ${'style="display: none;"' if submitted else ''}
/> type="text"
size="40"
/>
</label>
% endfor % endfor
<section class="action"> <div class="action">
<input class="save" type="button" value="${_('Save')}" /> <button class="save" type="button">${_('Save')}</button>
</section> </div>
</section> </div>
<section id="result_cloud_section_${element_id}" class="result_cloud_section"> <div id="result_cloud_section_${element_id}" class="result_cloud_section">
<h3>${_('Your words:')} <span class="your_words"></span></h3>
<h3>${_('Total number of words:')} <span class="total_num_words"></span></h3>
<div class="word_cloud"></div> <div class="word_cloud"></div>
</section> <p class="total_num_words"></p>
<p>${_('Your words were:')}</p>
<ul class="your_words"></ul>
</div>
</section> </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