%for section in chapter['sections']:
@@ -153,16 +159,16 @@ from django.utils.http import urlquote_plus
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
%>
%if section.due is not None:
@@ -174,7 +180,7 @@ from django.utils.http import urlquote_plus
- ${ _("Problem Scores: ") if section.graded else _("Practice Scores: ")}
%for score in section.scores:
- - ${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible)) | h}
+ - ${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}
%endfor
%else:
diff --git a/lms/templates/courseware/progress_graph.js b/lms/templates/courseware/progress_graph.js
index 104832be91..10605eec21 100644
--- a/lms/templates/courseware/progress_graph.js
+++ b/lms/templates/courseware/progress_graph.js
@@ -1,24 +1,41 @@
<%page args="grade_summary, grade_cutoffs, graph_div_id, show_grade_breakdown = True, show_grade_cutoffs = True, **kwargs"/>
<%!
- import json
- import math
+ import bleach
+ import json
+ import math
+
+ from openedx.core.djangolib.js_utils import (
+ dump_js_escaped_json, js_escaped_string
+ )
%>
$(function () {
function showTooltip(x, y, contents) {
- $('
' + contents + '
').css( {
- position: 'absolute',
- display: 'none',
- top: y + 5,
- left: x + 15,
- border: '1px solid #000',
- padding: '4px 6px',
- color: '#fff',
- 'background-color': '#333',
- opacity: 0.90
- }).appendTo("body").fadeIn(200);
- }
+ $("#tooltip").remove();
+ var $tooltip_div = $('
').css({
+ position: 'absolute',
+ display: 'none',
+ top: y + 5,
+ left: x + 15,
+ border: '1px solid #000',
+ padding: '4px 6px',
+ color: '#fff',
+ 'background-color': '#222',
+ opacity: 0.90
+ });
+ edx.HtmlUtils.setHtml(
+ $tooltip_div,
+ edx.HtmlUtils.HTML(contents)
+ );
+
+ edx.HtmlUtils.append(
+ $('body'),
+ edx.HtmlUtils.HTML($tooltip_div)
+ );
+
+ $('#tooltip').fadeIn(200);
+ }
/* -------------------------------- Grade detail bars -------------------------------- */
<%
@@ -46,17 +63,28 @@ $(function () {
'color' : colors[colorIndex]}
categoryData = categories[ section['category'] ]
-
+
+ ## Because this is Python (Mako) embedded in JavaScript, our safe linting script is
+ ## thoroughly confused. We should rewrite this file to remove Python/Mako.
+ ## safe-lint: disable=javascript-jquery-append
categoryData['data'].append( [tickIndex, section['percent']] )
- ticks.append( [tickIndex, section['label'] ] )
+
+ ## Note that some courses had stored images in the Abbreviation. We are no longer
+ ## allowing the display of such images, and remove any previously stored HTML
+ ## to prevent ugly HTML from being shown to learners.
+ ## safe-lint: disable=javascript-jquery-append
+ ticks.append( [tickIndex, bleach.clean(section['label'], tags=[], strip=True)] )
if section['category'] in detail_tooltips:
+ ## safe-lint: disable=javascript-jquery-append
detail_tooltips[ section['category'] ].append( section['detail'] )
else:
detail_tooltips[ section['category'] ] = [ section['detail'], ]
if 'mark' in section:
+ ## safe-lint: disable=javascript-jquery-append
droppedScores.append( [tickIndex, 0.05] )
+ ## safe-lint: disable=javascript-jquery-append
dropped_score_tooltips.append( section['mark']['detail'] )
tickIndex += 1
@@ -64,14 +92,14 @@ $(function () {
if section.get('prominent', False):
tickIndex += sectionSpacer
- ## ----------------------------- Grade overviewew bar ------------------------- ##
+ ## ----------------------------- Grade overview bar ------------------------- ##
tickIndex += sectionSpacer
series = categories.values()
overviewBarX = tickIndex
extraColorIndex = len(categories) #Keeping track of the next color to use for categories not in categories[]
- if show_grade_breakdown:
+ if show_grade_breakdown:
for section in grade_summary['grade_breakdown']:
if section['percent'] > 0:
if section['category'] in categories:
@@ -79,7 +107,7 @@ $(function () {
else:
color = colors[ extraColorIndex % len(colors) ]
extraColorIndex += 1
-
+ ## safe-lint: disable=javascript-jquery-append
series.append({
'label' : section['category'] + "-grade_breakdown",
'data' : [ [overviewBarX, section['percent']] ],
@@ -103,18 +131,73 @@ $(function () {
descending_grades = sorted(grade_cutoffs, key=lambda x: grade_cutoffs[x], reverse=True)
for grade in descending_grades:
percent = grade_cutoffs[grade]
+ ## safe-lint: disable=javascript-jquery-append
grade_cutoff_ticks.append( [ percent, u"{0} {1:.0%}".format(grade, percent) ] )
else:
grade_cutoff_ticks = [ ]
%>
- var series = ${ json.dumps( series ) };
- var ticks = ${ json.dumps(ticks) };
- var bottomTicks = ${ json.dumps(bottomTicks) };
- var detail_tooltips = ${ json.dumps(detail_tooltips) };
- var droppedScores = ${ json.dumps(droppedScores) };
- var grade_cutoff_ticks = ${ json.dumps(grade_cutoff_ticks) }
+ var series = ${ series | n, dump_js_escaped_json };
+ var ticks = ${ ticks | n, dump_js_escaped_json };
+ var bottomTicks = ${ bottomTicks | n, dump_js_escaped_json };
+ var detail_tooltips = ${ detail_tooltips | n, dump_js_escaped_json };
+ var droppedScores = ${ droppedScores | n, dump_js_escaped_json };
+ var grade_cutoff_ticks = ${ grade_cutoff_ticks | n, dump_js_escaped_json }
+ var yAxisTooltips={};
+
+ /*
+ series looks like:
+ [
+ {
+ color: "#600101",
+ label: "Homework",
+ data: [[1, 0.06666666666666667], [2, 1], [3.25, .53]]
+ },
+ ...
+ ]
+
+ detail_tooltips looks like:
+ {
+ "Dropped Scores": [0: "The lowest 1...:],
+ "Homework": [
+ 0: "Homework 1 -- Homework -- Question Styles 7% (1/15)",
+ 1: "Homework 2 -- Homework -- Get Social 100% (1/1)",
+ 2: "Homework Average = 53%"
+ ],
+ ...
+ }
+ */
+
+ // loop through the series and extract the matching tick and the series label
+ for (var seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
+ for (var dataIndex = 0; dataIndex < series[seriesIndex]['data'].length; dataIndex++) {
+ var tickIndex = series[seriesIndex]['data'][dataIndex][0];
+ // There may be more than one detail tooltip for a given tickIndex. If so,
+ // push the new tooltip on the existing list.
+ if (tickIndex in yAxisTooltips) {
+ yAxisTooltips[tickIndex].push(detail_tooltips[series[seriesIndex]['label']][dataIndex]);
+ } else {
+ yAxisTooltips[tickIndex] = [detail_tooltips[series[seriesIndex]['label']][dataIndex]];
+ }
+ // If this item was a dropped score, add the tooltip message about that.
+ for (var droppedIndex = 0; droppedIndex < droppedScores.length; droppedIndex++) {
+ if (tickIndex === droppedScores[droppedIndex][0]) {
+ yAxisTooltips[tickIndex].push(detail_tooltips["Dropped Scores"][droppedIndex]);
+ }
+ }
+ }
+ }
+
+ // hide the vertical axis since they are audibly lacking context
+ for (var i = 0; i < grade_cutoff_ticks.length; i++) {
+ grade_cutoff_ticks[i][1] = edx.HtmlUtils.joinHtml(
+ edx.HtmlUtils.HTML('
'),
+ grade_cutoff_ticks[i][1],
+ edx.HtmlUtils.HTML('')
+ ).text;
+ }
+
//Always be sure that one series has the xaxis set to 2, or the second xaxis labels won't show up
series.push( {label: 'Dropped Scores', data: droppedScores, points: {symbol: "cross", show: true, radius: 3}, bars: {show: false}, color: "#333"} );
@@ -128,40 +211,117 @@ $(function () {
markings.push({yaxis: {from: ascending_grades[i], to: ascending_grades[i+1]}, color: colors[(i-1) % colors.length]});
var options = {
- series: {stack: true,
- lines: {show: false, steps: false },
- bars: {show: true, barWidth: 0.8, align: 'center', lineWidth: 0, fill: .8 },},
- xaxis: {tickLength: 0, min: 0.0, max: ${tickIndex - sectionSpacer}, ticks: ticks, labelAngle: 90},
- yaxis: {ticks: grade_cutoff_ticks, min: 0.0, max: 1.0, labelWidth: 100},
- grid: { hoverable: true, clickable: true, borderWidth: 1, markings: markings },
- legend: {show: false},
+ series: {
+ stack: true,
+ lines: {
+ show: false,
+ steps: false
+ },
+ bars: {
+ show: true,
+ barWidth: 0.8,
+ align: 'center',
+ lineWidth: 0,
+ fill: .8
+ }
+ },
+ xaxis: {
+ tickLength: 0,
+ min: 0.0,
+ max: ${tickIndex - sectionSpacer | n, dump_js_escaped_json},
+ ticks: function() {
+ for (var i = 0; i < ticks.length; i++) {
+ var tickLabel = edx.HtmlUtils.joinHtml(
+ // The very last tick will be for the total, and it usually is composed of a number of different
+ // grading types. To help clarify, do NOT make the label ("Total") aria-hidden in that case.
+ edx.HtmlUtils.HTML(i < ticks.length - 1 ? '
' : ''),
+ ticks[i][1],
+ edx.HtmlUtils.HTML('')
+ );
+ var elementTooltips = yAxisTooltips[ticks[i][0]];
+ if (elementTooltips) {
+ for (var tooltipIndex = 0; tooltipIndex < elementTooltips.length; tooltipIndex++) {
+ tickLabel = edx.HtmlUtils.joinHtml(
+ tickLabel,
+ edx.HtmlUtils.HTML(''),
+ elementTooltips[tooltipIndex],
+ edx.HtmlUtils.HTML('
')
+ );
+ }
+ }
+ ticks[i][1] = tickLabel;
+ }
+ return ticks;
+ },
+ labelAngle: 90
+ },
+ yaxis: {
+ ticks: grade_cutoff_ticks,
+ min: 0.0,
+ max: 1.0,
+ labelWidth: 100
+ },
+ grid: {
+ hoverable: true,
+ clickable: true,
+ borderWidth: 1,
+ markings: markings
+ },
+ legend: {
+ show: false
+ }
};
- var $grade_detail_graph = $("#${graph_div_id}");
+ var $grade_detail_graph = $("#${graph_div_id | n, js_escaped_string}");
if ($grade_detail_graph.length > 0) {
var plot = $.plot($grade_detail_graph, series, options);
%if show_grade_breakdown:
- var o = plot.pointOffset({x: ${overviewBarX} , y: ${totalScore}});
- $grade_detail_graph.append('${"{totalscore:.0%}".format(totalscore=totalScore)}
');
+ var o = plot.pointOffset(
+ {x: ${overviewBarX | n, dump_js_escaped_json} , y: ${totalScore | n, dump_js_escaped_json}}
+ );
+
+ edx.HtmlUtils.append(
+ $grade_detail_graph,
+ edx.HtmlUtils.joinHtml(
+ // safe-lint: disable=javascript-concat-html
+ edx.HtmlUtils.HTML(''),
+ edx.HtmlUtils.HTML(''),
+ gettext('Overall Score'),
+ edx.HtmlUtils.HTML('
'),
+ '${'{totalscore:.0%}'.format(totalscore=totalScore) | n, js_escaped_string}',
+ edx.HtmlUtils.HTML('
')
+ )
+ );
+
%endif
+
+ $grade_detail_graph.find('.xAxis .tickLabel').attr('tabindex', '0').focus(function(event) {
+ var $target = $(event.target), srElements = $target.find('.sr'), srText="", i;
+ if (srElements.length > 0) {
+ for (i = 0; i < srElements.length; i++) {
+ srText += srElements[i].innerHTML;
+ }
+ // Position the tooltip slightly above the tick label.
+ showTooltip($target.offset().left - 70, $target.offset().top - 120, srText);
+ }
+ });
+
+ $grade_detail_graph.focusout(function(){
+ $("#tooltip").remove();
+ });
}
+
var previousPoint = null;
$grade_detail_graph.bind("plothover", function (event, pos, item) {
- $("#x").text(pos.x.toFixed(2));
- $("#y").text(pos.y.toFixed(2));
if (item) {
if (previousPoint != (item.dataIndex, item.seriesIndex)) {
previousPoint = (item.dataIndex, item.seriesIndex);
- $("#tooltip").remove();
-
if (item.series.label in detail_tooltips) {
var series_tooltips = detail_tooltips[item.series.label];
if (item.dataIndex < series_tooltips.length) {
- var x = item.datapoint[0].toFixed(2), y = item.datapoint[1].toFixed(2);
-
showTooltip(item.pageX, item.pageY, series_tooltips[item.dataIndex]);
}
}