ECOM-7386 Added a program progress circle to program details page
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'text!../../../templates/components/progress_circle_view.underscore',
|
||||
'text!../../../templates/components/progress_circle_segment.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
progressViewTpl,
|
||||
progressSegmentTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
x: 22,
|
||||
y: 22,
|
||||
radius: 16,
|
||||
degrees: 180,
|
||||
strokeWidth: 1.2,
|
||||
|
||||
viewTpl: _.template(progressViewTpl),
|
||||
segmentTpl: _.template(progressSegmentTpl),
|
||||
|
||||
initialize: function() {
|
||||
var progress = this.model.get('progress');
|
||||
|
||||
this.model.set({
|
||||
totalCourses: progress.completed + progress.in_progress + progress.not_started
|
||||
});
|
||||
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = $.extend({}, this.model.toJSON(), {
|
||||
circleSegments: this.getProgressSegments(),
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
radius: this.radius,
|
||||
strokeWidth: this.strokeWidth
|
||||
});
|
||||
|
||||
this.$el.html(this.viewTpl(data));
|
||||
},
|
||||
|
||||
getDegreeIncrement: function(total) {
|
||||
return 360 / total;
|
||||
},
|
||||
|
||||
getOffset: function(total) {
|
||||
return 100 - ((1 / total) * 100);
|
||||
},
|
||||
|
||||
getProgressSegments: function() {
|
||||
var progressHTML = [],
|
||||
total = this.model.get('totalCourses'),
|
||||
segmentDash = 2 * Math.PI * this.radius,
|
||||
degreeInc = this.getDegreeIncrement(total),
|
||||
data = {
|
||||
// Remove strokeWidth to show a gap between the segments
|
||||
dashArray: segmentDash - this.strokeWidth,
|
||||
degrees: this.degrees,
|
||||
offset: this.getOffset(total),
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
radius: this.radius,
|
||||
strokeWidth: this.strokeWidth
|
||||
},
|
||||
i,
|
||||
segmentData;
|
||||
|
||||
for (i = 0; i < total; i++) {
|
||||
segmentData = $.extend({}, data, {
|
||||
classList: (i >= this.model.get('progress').completed) ? 'incomplete' : 'complete',
|
||||
degrees: data.degrees + (i * degreeInc)
|
||||
});
|
||||
|
||||
// Want the incomplete segments to have no gaps
|
||||
if (segmentData.classList === 'incomplete' && (i + 1) < total) {
|
||||
segmentData.dashArray = segmentDash;
|
||||
}
|
||||
|
||||
progressHTML.push(this.segmentTpl(segmentData));
|
||||
}
|
||||
|
||||
return progressHTML.join('');
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,112 @@
|
||||
define([
|
||||
'backbone',
|
||||
'jquery',
|
||||
'edx-ui-toolkit/js/utils/spec-helpers/spec-helpers',
|
||||
'common/js/components/views/progress_circle_view'
|
||||
], function(Backbone, $, SpecHelpers, ProgressCircleView) {
|
||||
'use strict';
|
||||
|
||||
describe('Progress Circle View', function() {
|
||||
var view = null,
|
||||
context = {
|
||||
title: 'XSeries Progress',
|
||||
label: 'Earned Certificates',
|
||||
progress: {
|
||||
completed: 2,
|
||||
in_progress: 1,
|
||||
not_started: 3
|
||||
}
|
||||
},
|
||||
testCircle,
|
||||
testText,
|
||||
initView,
|
||||
getProgress,
|
||||
testProgress;
|
||||
|
||||
testCircle = function(progress) {
|
||||
var $circle = view.$('.progress-circle');
|
||||
|
||||
expect($circle.find('.complete').length).toEqual(progress.completed);
|
||||
expect($circle.find('.incomplete').length).toEqual(progress.in_progress + progress.not_started);
|
||||
};
|
||||
|
||||
testText = function(progress) {
|
||||
var $numbers = view.$('.numbers'),
|
||||
total = progress.completed + progress.in_progress + progress.not_started;
|
||||
|
||||
expect(view.$('.progress-heading').html()).toEqual('XSeries Progress');
|
||||
expect(parseInt($numbers.find('.complete').html(), 10)).toEqual(progress.completed);
|
||||
expect(parseInt($numbers.find('.total').html(), 10)).toEqual(total);
|
||||
};
|
||||
|
||||
getProgress = function(x, y, z) {
|
||||
return {
|
||||
completed: x,
|
||||
in_progress: y,
|
||||
not_started: z
|
||||
};
|
||||
};
|
||||
|
||||
testProgress = function(x, y, z) {
|
||||
var progress = getProgress(x, y, z);
|
||||
|
||||
view = initView(progress);
|
||||
view.render();
|
||||
|
||||
testCircle(progress);
|
||||
testText(progress);
|
||||
};
|
||||
|
||||
initView = function(progress) {
|
||||
var data = $.extend({}, context, {
|
||||
progress: progress
|
||||
});
|
||||
|
||||
return new ProgressCircleView({
|
||||
el: '.js-program-progress',
|
||||
model: new Backbone.Model(data)
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<div class="js-program-progress"></div>');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
view.remove();
|
||||
});
|
||||
|
||||
it('should exist', function() {
|
||||
var progress = getProgress(2, 1, 3);
|
||||
|
||||
view = initView(progress);
|
||||
view.render();
|
||||
expect(view).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render the progress circle based on the passed in model', function() {
|
||||
var progress = getProgress(2, 1, 3);
|
||||
|
||||
view = initView(progress);
|
||||
view.render();
|
||||
testCircle(progress);
|
||||
});
|
||||
|
||||
it('should render the progress text based on the passed in model', function() {
|
||||
var progress = getProgress(2, 1, 3);
|
||||
|
||||
view = initView(progress);
|
||||
view.render();
|
||||
testText(progress);
|
||||
});
|
||||
|
||||
SpecHelpers.withData({
|
||||
'should render the progress text with only completed courses': [5, 0, 0],
|
||||
'should render the progress text with only in progress courses': [0, 4, 0],
|
||||
'should render the progress circle with only not started courses': [0, 0, 5],
|
||||
'should render the progress text with no completed courses': [0, 2, 3],
|
||||
'should render the progress text with no in progress courses': [2, 0, 7],
|
||||
'should render the progress text with no not started courses': [2, 4, 0]
|
||||
}, testProgress);
|
||||
});
|
||||
});
|
||||
@@ -164,6 +164,7 @@
|
||||
'common/js/spec/components/paginated_view_spec.js',
|
||||
'common/js/spec/components/paging_header_spec.js',
|
||||
'common/js/spec/components/paging_footer_spec.js',
|
||||
'common/js/spec/components/progress_circle_view_spec.js',
|
||||
'common/js/spec/components/search_field_spec.js',
|
||||
'common/js/spec/components/view_utils_spec.js',
|
||||
'common/js/spec/utils/edx.utils.validate_spec.js'
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<circle class="<%- classList %>"
|
||||
r="<%- radius %>" cx="<%- x %>" cy="<%- y %>"
|
||||
transform="rotate(<%- degrees %>, <%- x %>, <%- y %>)"
|
||||
stroke-width="<%- strokeWidth %>"
|
||||
fill="none"
|
||||
stroke-dasharray="<%- dashArray %>"
|
||||
stroke-dashoffset="<%- offset %>">
|
||||
</circle>
|
||||
@@ -0,0 +1,15 @@
|
||||
<% if (title) { %>
|
||||
<h2 class="progress-heading"><%- title %></h2>
|
||||
<% } %>
|
||||
<div class="progress-circle-wrapper">
|
||||
<svg class="progress-circle" viewBox="0 0 44 44" aria-hidden="true">
|
||||
<circle class="js-circle bg" r="<%- radius %>" cx="<%- x %>" cy="<%- y %>" stroke-width="<%- strokeWidth %>" fill="none"></circle>
|
||||
<%= circleSegments %>
|
||||
</svg>
|
||||
<div class="progress-label">
|
||||
<div class="numbers">
|
||||
<span class="complete"><%- progress.completed %></span>/<span class="total"><%- totalCourses %></span>
|
||||
</div>
|
||||
<div class="label"><% if (label) { %><%- label %><% } %></div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user